Commit Graph
100 Commits
Author SHA1 Message Date
paseo-bot[bot] 40f0393064 Record shortcuts using the keyboard layout character (#6652)
* fix(app): record shortcuts using the keyboard layout character

Shortcut capture stored the QWERTY position while dispatch matched the logical character, so a Dvorak B became Cmd+N. Use the existing logical key mappings for capture and preserve code fallback for named and rewritten Option keys.

* fix(app): keep named shortcut keys on their supported codes

Character capture must not advertise unsupported named keys. Restrict the logical lookup to printable mappings so keypad Enter keeps its existing unsupported behavior.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-12 01:58:11 +02:00
paseo-bot[bot] 166aabd57b Keep subprocess regression tests independent of terminal colors (#6648)
* test(server): isolate subprocess regressions from terminal colors

Inherited NO_COLOR and FORCE_COLOR make Node emit an unrelated warning. It contaminates argv-fixture stderr and fails the closed-output supervisor fixture before its pipe handlers are installed. Clear both settings only in these fixture subprocesses, preserving their output and lifecycle assertions.

* test(server): exercise conflicting colors regardless of parent environment

Run the closed-output and spaced-executable regressions with explicit conflicting color inputs as well as the inherited environment, so removing fixture isolation fails on ordinary CI environments too.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-12 00:47:28 +02:00
b852ae920b fix(desktop): show the Paseo window icon on Linux (#6645)
Packaged windows read resources/icon.png, but Linux packages only
installed desktop-entry icons. Include the runtime PNG and verify its
bytes in the existing packaged artifact smoke test.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Pantic <panta@talentkit.io>
Co-authored-by: Max Höhl <43786929+whme@users.noreply.github.com>
2026-10-11 23:57:52 +02:00
paseo-bot[bot]andX b3693b79d1 Find hidden agent plan files in workspace suggestions (#6641)
* fix(server): find hidden agent plan files in workspace suggestions

Workspace autocomplete skipped the editor's plan directory while traversing
other known agent directories. Include it in the existing discovery policy
and cover basename and partial-path queries through the daemon client.

Co-authored-by: X <X@16x.org>

* test(server): give plan suggestion queries explicit expectations

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: X <X@16x.org>
2026-10-11 21:54:46 +02:00
0458988825 Connect the self-hosted web app through standard-port proxies (#6638)
* fix(server): give the web UI connection hint an explicit port (#1)

injectConnectionHint built `listen` straight from the Host header, which
carries no port when the client used the scheme's default — exactly what a
browser sends to a proxy terminating TLS on 443. The app parses `listen` with
parseHostPort, which requires an explicit port, so connectionFromListen
returned null and bootstrapInitialConnectionHint bailed before probing,
silently. Boot then fell through to the localhost:6767 fallback, which is the
viewer's own machine rather than the daemon, and the app landed on the welcome
screen with no host at all.

Reaching the daemon directly never hit this: http://host:6767 puts the port in
the Host header. It only appears behind a reverse proxy on 443 or 80, such as a
Tailscale Ingress, and only in a browser profile with no host already stored.

Spell the scheme default out when the header omits it, leaving the IPv6 bracket
form intact.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix(server): compute the web bootstrap port before its connection hint

Keep the default-port fix for reverse proxy bootstrap within the repository branching style.

Co-authored-by: Sian Lerk Lau <kiawin@gmail.com>

---------

Co-authored-by: Sian Lerk Lau <kiawin@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-10-11 20:51:51 +02:00
paseo-bot[bot] 4bd31e1a2e test(cli): accept Codex catalogs without branded model IDs (#6633)
The provider regression test rejected valid GPT catalogs solely because no ID contained codex. Keep the structural output checks without requiring that upstream branding substring.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 19:47:10 +02:00
paseo-bot[bot]andOkan YILDIRIM 1939ee79ed Accept daemon settings in the published editor schema (#6621)
* fix(config): accept daemon settings in the published editor schema

The website schema omitted accepted configuration fields, so editors
rejected valid browser, profile, plugin and metadata settings. Regenerate
from the daemon schema and guard the published artifact against drift.

Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>

* fix(config): preserve proxy help when regenerating the schema

Keep the existing editor descriptions in the source schema so regeneration retains them. The new regression fails when the proxy descriptions disappear.

* test(usage): create SQLite store fixtures in one transaction

The Windows plugin suite exceeded its five-second test deadline while creating and exercising the first OMP store fixture. Commit the fixture as a unit rather than flushing each inserted row separately; preserve the read-only discovery assertions.

* test(config): show structured schema validation errors

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com>
2026-10-11 18:50:03 +02:00
paseo-bot[bot]andBudi 8d9e7d3ad3 Confirm remote permission approvals before reporting success (#6607)
* fix(cli): await permission resolution before closing transport

* test(cli): verify permission confirmation over real connections

Exercise permission commands through a real WebSocket protocol peer, keeping responses pending until explicit resolution. This catches success before acknowledgement without replacing the client connector or process output. Retain the contributor permission fix and verify both commands, batches, and unconfirmed responses.

Co-authored-by: Budi <11062155+budivoogt@users.noreply.github.com>

* test(cli): catch permission batches completing after one reply

Hold the second acknowledgement until a WebSocket round trip proves the first reply reached the client. A first-reply-only mutation must fail both batch tests.

* test(cli): inject permission clients into command regression tests

* test(cli): use timer helper for permission continuations

* style(cli): format permission regression tests with oxfmt

---------

Co-authored-by: Budi <11062155+budivoogt@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 18:03:43 +02:00
paseo-bot[bot]and见安 114389383d Keep files starting with two dots usable (#6604)
* fix(file-explorer): distinguish parent traversal from dot-prefixed names

* test(file-explorer): cover dot-prefixed paths through daemon RPC

* test(plugins): allow slower session readiness in provider fixtures

The event helper exhausted 200 iterations before a valid delayed session
became ready. Use an elapsed-time deadline while preserving real timers
for watchdog tests, and retain a delayed-start regression with launch
flag assertions.

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>

---------

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 16:51:00 +02:00
paseo-bot[bot]andIvan Malison b412374f0c Run React tests when the shell uses NODE_ENV=production (#6572)
* fix(tests): run React tests from production shells

Vitest preserves an inherited NODE_ENV, selecting React without its test APIs. Pin the environment before dependency resolution and worker startup in both test entry points.

Co-authored-by: Ivan Malison <IvanMalison@gmail.com>

* test(app): expect zero edits for a pure staged rename

The rename patch correction on main preserves unchanged content. Keep the discard journey intact and update its obsolete added-line assertion.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
2026-10-11 11:52:50 +02:00
paseo-bot[bot]andYu Han a3f75aa9e4 Show removed SQL comments in Git diffs (#6569)
Hunk content resembling patch headers was discarded before line parsing. Metadata is already ignored before the first hunk, so preserve every added and removed line once a hunk starts.

Closes #6564

Co-authored-by: Yu Han <73627805+Mar-garet@users.noreply.github.com>
2026-10-11 11:03:15 +02:00
paseo-bot[bot]and见安 46b1284177 Release files when HTTP downloads are canceled (#6567)
* fix(server): close canceled HTTP download streams

* test(server): verify canceled downloads release real file descriptors

Exercise the reported HTTP cancellation path without replacing shared Node methods, and include download regressions in the standard integration suite.

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>

* test(server): detect canceled download leaks through symlinked paths

Compare descriptor targets with the resolved file path and exercise a symlinked download directory so the regression cannot pass by missing the open file.

---------

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 09:57:29 +02:00
paseo-bot[bot]and见安 a5dba3c992 Show checkout patch lines with external Git diff commands configured (#6562)
* fix(server): parse Git patches without external diff commands

* test(server): keep diff command checks aligned with parsed patches

The external diff regression fix intentionally disables helper commands.
Keep the existing large-file batching assertions aligned with that command
while preserving their patch, omission, and concurrency checks.

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>

---------

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 08:58:07 +02:00
paseo-bot[bot] dd0350bc2b Show actual edits when a file is renamed in Changes (#6559)
* fix(server): show actual edits when a file is renamed

Path-limited patch requests discarded the detected rename source, so Git reported the destination as a new file. Include both literal paths and select the destination patch to preserve the rename and avoid duplicating changed copy sources.

* fix(server): preserve small copy patches beside oversized sources

Filter rename and copy output before enforcing the bounded request budget, so a modified source cannot hide its small copy patch.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 07:55:09 +02:00
paseo-bot[bot] cfb18f16d4 fix(app): find starting refs by change request descriptions (#6558)
Preserve returned change request bodies as search-only option text so client filtering keeps description matches. Keep title and ID matches ranked ahead of secondary text.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 06:53:37 +02:00
paseo-bot[bot] 7f9b65d464 test(server): keep commit fixture authors independent of Git config (#6552)
Command-scoped user.name overrides the fixture repository configuration and breaks its author assertion. Supply the fixture author when creating commits and exercise the override with real Git.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 05:46:39 +02:00
paseo-bot[bot] 7e66657bf0 fix(agent): preserve reasoning order in activity transcripts (#6546)
Format projected text entries in sequence instead of grouping replies ahead of their preceding reasoning. Timeline projection already owns fragment merging.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 05:00:02 +02:00
paseo-bot[bot] 446749102e test(server): make commit signing checks independent of global format (#6540)
The failing signer uses gpg.program, which Git bypasses when a global SSH signing format is inherited. Select OpenPGP in the commit and squash-merge test repositories so both checks exercise their configured failing signer.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 03:46:59 +02:00
paseo-bot[bot] e61009d5fb fix(server): display diffs with custom Git presentation settings (#6536)
Request uncolored patches with explicit a/ and b/ prefixes before parsing. Mnemonic paths do not match checkout entries, and forced ANSI colors hide patch headers and hunks.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 03:00:33 +02:00
ee0d901efb Run each scheduled slot once without skipping the next occurrence (#6532)
* fix(server): claim scheduled slots atomically

* fix(server): skip elapsed schedule slots

* test(server): isolate overlapping tick runner

* test(schedule): reproduce duplicate cron dispatch across overlapping ticks

* fix(schedule): preserve final slots and recovery after failed outcome writes

Historical running records are not proof of a live execution after an outcome write fails. Keep live-run exclusion in the existing in-memory guard while the persisted cursor prevents duplicate slot claims. Final allowed runs do not need a future cron occurrence; recover interrupted final runs and preserve cadence if their limit changes during execution.

---------

Co-authored-by: Keith Kowal (paseo-dev) <keithpk+paseo-dev@kowal.me>
Co-authored-by: keithpk <keith@kowal.me>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 02:19:11 +02:00
paseo-bot[bot]andLorenzo Fiore d5513472ed Keep words intact in followed agent logs (#6528)
* fix(cli): keep streamed message fragments together in logs --follow

`paseo logs` without `-f` fetches the timeline projected, so
`projectTimelineRows` merges consecutive assistant and reasoning fragments by
exact concatenation before the transcript is rendered. Follow mode never got
there: it called `formatAgentActivityTranscript([item])` per arriving event, so
every fragment was projected alone and printed on its own line. Words broke at
fragment boundaries, a reader could not tell an author newline from a transport
one, and `[Thought]` was reprinted for each reasoning chunk.

Follow mode now keeps a fragment chain open using the same merge rule the
projection uses (same kind, same `turnId`, matching `messageId` when the event
carries one) and writes the text as it arrives instead of one line per event.
Only trailing whitespace is held back, so a message still ends trimmed the way
the one-shot transcript ends it, and the chain closes when another kind of item
arrives or the follow stops.

* test(cli): place daemon follow regression in its owning suite

* fix(cli): preserve filtered log boundaries and run the follow regression in CI

* test(cli): wait for a completed log line before terminating follow

---------

Co-authored-by: Lorenzo Fiore <fiorelorenzo.fl@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 00:55:14 +02:00
paseo-bot[bot] 1228c01a10 Find GitLab merge requests and issues by number (#6525)
* fix(gitlab): find merge requests and issues by number

GitLab text search matches titles and descriptions but not item numbers. Use repository-scoped IID filtering for numeric queries, keeping open-only results and the existing text search behavior.

* fix(gitlab): retain text matches in number searches

Supplement title and description search with the open-item number lookup, rather than replacing it. Prioritize exact numbers, deduplicate results, and preserve each list limit.

* refactor(gitlab): name list helper arguments

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-11 00:03:10 +02:00
563c014dc1 Open assistant links to non-ASCII filenames (#6520)
* fix(app): decode percent-encoded assistant file links

markdown-it percent-encodes non-ASCII and space characters in link
hrefs, so a link like [x](docs/reports/报告.md) arrives as
docs/reports/%E6%8A%A5%E5%91%8A.md. The file:// and bare absolute-path
branches already run safeDecodeURIComponent, but the workspace-relative,
path:line, Windows, and home-relative branches did not, so clicking the
link tried to open the still-encoded name and failed with ENOENT.

Decode the path portion before the relative-path heuristics and in the
inline absolute/Windows branches so all href forms behave the same.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(app): decode hrefs only, never raw inline-code paths

Raw inline-code and linkified tokens reach the same parser as markdown
hrefs. Decoding unconditionally would send a real file named
my%20file.md to my file.md. Gate every decode on AssistantHrefParseOptions.decodeHref — set by the resolver only when the token
came from source.href — and extract parseWindowsFileLink to keep the
entry point under the complexity limit. Adds parse-level and
resolver-level regression tests for literal %20 filenames.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(app): keep raw absolute paths out of URL parsing

new URL() percent-encodes non-ASCII, so a raw inline-code path like
/tmp/开户.md became /tmp/%E5%BC%80%E6%88%B7.md when decodeHref was
false. Hrefs still go through URL parsing (normalizes dot segments
before decode); raw paths split the # fragment manually instead.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(app): keep encoded and literal file links distinct

Preserve inline-code provenance for Windows paths and key cached file lookups by their resolved query, path, and line range. Reproduce the reported non-ASCII Markdown link through the renderer and filesystem, with browser coverage for clicking it.

Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>

Co-authored-by: dengjiawen <dengjiawen@hortorgames.com>

* test(app): assert file content after opening the markdown link

Co-authored-by: sunshinehomeyu-blip <267551119+sunshinehomeyu-blip@users.noreply.github.com>

* fix(app): preserve encoded line markers in filenames

---------

Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: dengjiawen <dengjiawen@hortorgames.com>
Co-authored-by: sunshinehomeyu-blip <267551119+sunshinehomeyu-blip@users.noreply.github.com>
2026-10-10 22:00:00 +02:00
paseo-bot[bot] d0728cd1ed fix(opencode): preserve tool validation errors in v2 (#6519)
Failed bridge requests discarded the daemon error body before OpenCode could show the validation explanation to the agent. Preserve that message and status while retaining the HTTP fallback.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 21:01:07 +02:00
paseo-bot[bot]and见安 0ba4d2526e Honor timestamps when reading and following agent logs (#6514)
* fix(cli): honor --since in agent logs

* test(cli): verify logs cutoff in the daemon-owned suite

Keep the real CLI and WebSocket regression while placing daemon fixtures
in the server suite, as the CI ownership contract requires. Invoke the
source CLI so the test runs with the server suite's dependency build.

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>

* test(server): use a portable loader URL for the logs regression

---------

Co-authored-by: 见安 <73627805+Mar-garet@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 19:48:42 +02:00
paseo-bot[bot]andJason 7868f5caf6 Keep email addresses intact when sending with Enter (#6471)
* fix: prevent file mention autocomplete from triggering on emails

Skip @ when preceded by a word character or +, and reject angle
brackets in mention queries so email addresses no longer open the
file autocomplete or steal Enter.

Closes #1364

* test(app): verify emails send intact without file completion

The word-character and plus guard tests previously passed because their queries included trailing spaces. Exercise each guard with the cursor at the end of the email, and cover Enter in the browser alongside normal file completion.

Co-authored-by: Jason <mildreins@gmail.com>

---------

Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 17:00:08 +02:00
paseo-bot[bot]andJason 0147edaaac Restore repository selection for CLI worktree commands (#6467)
* fix(cli): pass cwd to worktree ls and archive

worktree ls/archive called getPaseoWorktreeList({}) which fails because
the daemon requires cwd or repoRoot. Default to process.cwd(), accept
optional --cwd, and cover both with unit and CLI help tests.

Closes #2010

* test(cli): reproduce worktree listing without repository scope

* fix(cli): resolve local worktree paths from the calling directory

Local instance paths belong to the CLI working directory. Explicit endpoint paths remain daemon-relative, matching the project command convention. Cover both through the real daemon.

* refactor(cli): make repository directory selection explicit

---------

Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 15:53:10 +02:00
paseo-bot[bot]and2takumax-chura bd78e5efa4 Show committed changes for existing-branch worktrees (#6462)
* fix: resolve real base branch for worktrees checked out from an existing branch

When a worktree is created by checking out an already-existing branch,
the stored baseRefName was set to the branch's own name instead of the
repository's default branch. The diff panel then diffs the branch
against itself, always showing no changes.

Fixes #1873

* fix: fall back to the current branch when no default branch is found

Addresses Greptile review feedback on #1874: resolveRepositoryDefaultBranch
returns null for local-only repos with no origin/HEAD and no local
main/master (e.g. a repo whose trunk is "develop"), which previously
fell back to storing the checked-out branch as its own base — the
exact self-diff bug this PR fixes. Fall back to whichever branch is
currently checked out in the main repo instead.

* fix(server): preserve existing-branch diffs through workspace restore

Carry the resolved comparison base from the worktree creator into the workspace record. Resolve it for occupied branches too, so a fresh suffixed checkout still displays the feature's committed changes.

Co-authored-by: 2takumax-chura <takuma.okayama@churadata.okinawa>

* fix(server): skip deleted default refs when creating worktrees

---------

Co-authored-by: 2takumax-chura <takuma.okayama@churadata.okinawa>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 15:18:05 +02:00
e81d5d02b1 Keep terminal CJK text unchanged after returning to a workspace (#6452)
* fix(app): restore terminal surface after tab hide so CJK cells redraw

Retained panels use display:none. Coming back remasured without treating
that as a surface restore, so Hangul kept 2-cell slots while glyphs painted
at ~1 cell (extra gap). Reload WebGL if the context was lost and force a
refresh when the host grows from zero.

Closes #3434

* fix(server): keep wide-char continuation cells out of terminal snapshots

Wide chars (CJK) occupy two xterm cells, and the second cell reports no
chars. The snapshot capture collapsed that empty continuation to " ", so
every restored Hangul syllable took three columns instead of two. The
injected space was real: selecting and copying the restored row carried
it into the clipboard.

Capture the continuation as "" and have renderTerminalSnapshotToAnsi
skip it. No schema change; snapshots from older daemons still replay
verbatim.

* fix(app): restore retained terminals on presentation

Co-authored-by: seob717 <dev.seob717@gmail.com>

* fix(terminal): limit restoration changes to confirmed CJK corruption

Keep the independently reproduced snapshot fix. Surface recovery behavior remains unverified and belongs with the broader viewport report. Dispose the replay terminal after the regression test.

---------

Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: seob717 <dev.seob717@gmail.com>
Co-authored-by: liujin0506 <liujin0506@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 13:27:04 +02:00
paseo-bot[bot] b63a325be3 Let Android devices without a camera install Paseo from Google Play (#6339)
* fix(android): declare the camera optional so camera-less devices can install

Requesting CAMERA makes Android treat android.hardware.camera as a required
feature, so Google Play marks devices without a camera as incompatible.
Pairing works without QR scanning, so declare the camera features optional.

Fixes #6303

* test(app): resolve the manifest with the Expo CLI's prebuild packages

Load @expo/prebuild-config and @expo/config-plugins from @expo/cli instead
of the older hoisted copies, and set the F-Droid flag explicitly for each
build profile case.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 09:54:15 +02:00
paseo-bot[bot] 99fc204c55 fix(opencode): browser screenshots reach OpenCode v2 agents (#6330)
* fix(opencode): return browser screenshots to OpenCode v2 agents

The v2 bridge plugin passed a Paseo tool's MCP content straight to
OpenCode. OpenCode v2 tool content is text or a file, so the image block
from browser_screenshot was not valid output: the tool call never
completed and the turn failed.

Convert image blocks to file content with a data URI and keep text
blocks as they are.

Closes #6318

* fix(opencode): show v2 tool images as images, not base64 output

With the bridge now returning screenshots to OpenCode v2 as data URI
file content, the v2 timeline copied each file's URI into the tool
call's output, so the screenshot card showed the whole base64 string.

Keep data URI images out of the tool output, leave an "[image]"
placeholder, and emit each image after the tool call as image markdown,
the way Claude and Codex tool images are shown. History replay uses the
same mapping.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-08 01:01:21 +02:00
paseo-bot[bot] 0fc6bf0c94 fix(schedule): keep firing after a run that could not be recorded (#6334)
* fix(schedule): keep firing after a run that could not be recorded

A schedule whose running-run write failed stayed marked as running in
memory, so every later tick skipped it and run-once reported it as
already running until the daemon restarted. The running mark is now
cleared whenever the run ends, including when it could not be recorded,
and the next tick retries the due run.

* test(schedule): skip the unwritable-directory test as root

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 22:55:19 +02:00
paseo-bot[bot] 1ae979d3d0 Subagents that finish while the app is disconnected no longer stay "working" (#6316)
* fix(app): catch up provider subagents after the update feed reconnects

Subagent updates are not replayed across a reconnect, so a child that
finished while the app was disconnected stayed "working" until one
subagent was opened and its parent was listed again. Re-list every
parent the app holds once the update feed resubscribes.

Closes #6109

* fix(app): resync a subagent parent even when its first list failed

A parent's subagent track lists the parent once on mount. When that list
failed because the app was disconnected, the parent was never tracked,
so the reconnect resync skipped it and children that later arrived via
live updates stayed stale after the next drop. Track the parent when its
list is requested instead of when it succeeds.

* test(website): expect the plugin directory links now that they are on

1b88f1612 turned PLUGINS_LINKED on, but the e2e test still asserted the
directory was unlinked, failing the typecheck job on main and every PR.
Assert the navigation, footer, and Browse plugins links point at /plugins.
The header check now targets the navigation landmark the homepage renders;
the banner locator matched nothing, so it only passed while asserting zero.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 18:30:20 +02:00
paseo-bot[bot] d46164ce0d fix(claude): keep long-running tool calls out of the subagents track (#6308)
Claude Code sends a tool_progress heartbeat every 30 s while a tool is
still running, with that tool's own id as parent_tool_use_id. The Claude
adapter routed any frame with a parent id to the sidechain tracker, so
a main-thread call that ran past 30 s (MCP, WebFetch, WebSearch) became
a "Claude subagent" row with a child action per heartbeat that never
completes, and its card was relabeled Task.

A heartbeat now names no sidechain, so it never reaches subagent
routing.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 17:17:17 +02:00
paseo-bot[bot] ee6deda079 Show a Claude agent's history when its transcript is under another project folder (#6301)
* fix(claude): load history from a transcript under another project folder

Claude Code resumes a session by id from any working directory and keeps
writing to the transcript in the project folder where the session started.
When the agent's cwd encodes to a different folder (a moved project, a
migrated daemon, a Windows binary under WSL), the agent kept working but
reopened with an empty timeline. Look the transcript up by session id
across the config dir's project folders when it is not where cwd points.

Refs #4540

* fix(claude): report a failed project folder listing instead of an absent transcript

Take the transcript lookup's inputs as one object, and rethrow read errors other than a missing projects folder so the history load logs them as a failure.

* test(claude): skip the unreadable projects folder case when running as root

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 16:09:34 +02:00
8ddeb79c8d Claude agents stay idle while background helpers work, and send or Stop no longer kills them (#6295)
* fix(claude): open autonomous turns only when Claude starts a turn

Paseo guessed from frame shape whether the main session had started work
on its own, and only a result could close the turn that guess opened. A
background subagent's task frames, its own background jobs'
task_notification and rate limit events all arrive with no parent tool
use id, so they looked like main-session work and opened a turn no result
would ever close. The agent then reported "running" while its main
session sat idle, and the app's message queue never drained.

Claude brackets every main-session turn itself: system/init opens it and
exactly one result closes it, whether a prompt, a wake-up after background
work, a slash command or an API error started it. Background subagents
never emit either on the main stream. An autonomous turn now opens only on
that init, and only when no foreground turn is open. The frame heuristic
is removed.

- Paseo launches the CLI with CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1,
  added as an overlay in buildSdkEnv so the SDK and the usage probe share
  one env object. An "idle" session state closes an autonomous turn as a
  safety net, never a foreground one. "running" opens nothing: Claude Code
  2.1.280 stays "running" for as long as any background subagent is
  alive. State frames never reach the timeline.
- An init clears a leftover pendingInterruptAbort. A new turn has begun,
  so the interrupted one is over, and a stale flag would swallow the new
  turn's result.

Tests that opened autonomous turns from bare frames now send the init
Claude sends. New coverage: helper frames never open a turn while the main
session is idle, a wake-up turn opens on init and closes on its result, a
folded steer keeps one turn while a turn Claude starts after the result
gets its own, an idle closes a turn no result closed, session state never
opens a turn, and the env flag reaches the SDK launch.

docs/agent-lifecycle.md records that only system/init and result bracket
a main-session turn, since the code cannot tell a reader why frame shape
fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(claude): interrupt Claude only while its main turn runs, and spare helpers

Every Stop, and every send that replaces a running turn (paseo send, MCP
send_agent_prompt, voice, the app's queue drain, Send now), ends in
query.interrupt(). Without the SDK's perTaskStopAffordance declaration
that call kills every running background subagent, even when the main
session is idle and there is nothing of ours to stop.

- Declare perTaskStopAffordance, so an interrupt aborts only the main
  turn, like Esc in Claude Code. Claude stops a helper itself with its
  TaskStop tool.
- Track whether a main-session turn is in flight from Claude's own
  brackets: set by system/init and by a pushed message's
  command_lifecycle "started" (/compact works for seconds before its
  init), cleared by the result or an idle session state.
- Call query.interrupt() and set pendingInterruptAbort only when that
  turn is in flight, or the cancelled turn's own message has already been
  started. Otherwise withdraw our not-yet-started messages with
  cancel_async_message instead: an idle interrupt reaches only helpers,
  and with no result to follow it the flag would linger.
- Track the foreground prompt with the queued steers until Claude starts
  it, so Stop withdraws a message still waiting in the queue. A turn
  stopped while the Claude process is still starting never sends it.
- Accept command_lifecycle "cancelled" as terminal, so a cancelled steer
  no longer waits to be withdrawn or keeps denying permission cards.

Interrupt tests now wait for the pump to read the init before stopping,
since that is when Paseo learns a turn is in flight. New coverage: a stop
before Claude starts the message withdraws it without interrupting, a
stop while only helpers run never reaches Claude, a stop mid-turn
interrupts once and drops the aborted result, a new turn clears a flag
no result will consume, the SDK option is passed, and a cancelled steer
is not withdrawn again.

docs/providers.md described the old unconditional interrupt; it now states
when the Claude adapter interrupts and why it declares the affordance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(claude): keep a helper spawned in the background out of a turn's cancel

When a turn is canceled, Paseo terminalizes the subagents that were
running in its foreground and skips the backgrounded ones, which outlive
it. It only learned a child was backgrounded from task_updated's
patch.is_backgrounded, which Claude sends when a foreground task moves to
the background later. A child spawned in the background carries the flag
on task_started instead, and a resumed subagent always does, so the
track marked those helpers "canceled" while they kept working.

Read is_backgrounded from task_started as well. docs/agent-lifecycle.md
said task_updated was the only signal; it now names both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): drain queued messages as steers

The composer queue drains when an agent goes idle. It sent each message
with no activeTurnBehavior, which the daemon treats as "interrupt": if
the agent had just started a turn of its own (a Claude wake-up after
background work finished), the drained message stopped that turn and
replaced it.

Send drained messages with activeTurnBehavior "steer". A drain that races
such a turn folds into it; with no turn running the daemon starts a
normal one, as before. "steer" is an existing protocol value, so older
daemons need nothing new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(claude): cover background helpers against real Claude

Every other test in this file tells Claude "Do not use a background
task", so a message sent or a Stop pressed while a backgrounded subagent
runs was never exercised. Four real-Claude scenarios cover it:

- "a background subagent keeps running after a message is sent while it
  works": one helper, the main turn ends, the parent is not reported
  running, a message in that window is answered, and the helper is never
  read as canceled and completes.
- "a helper's own nested background job does not phantom-run the parent
  or kill the helper": a helper starts its own background Bash job, then
  keeps working in the foreground. The parent stays idle across the
  nested job's completion (sampled for 30 s around a 5 s job), a message
  sent in that window is answered, and the helper reports CHILD_DONE.
- "the app's default steer survives a backgrounded helper while the main
  session works": the app's default send behavior (steer) does not kill
  a helper when it interrupts the main session's own turn.
- "Stop cancels the main session's own turn but spares its background
  helper": the same for Stop.

The wake-up turn Claude starts after a helper finishes lands a moment
after the helper reads completed (0.1 to 5 s in recorded traces), and the
daemon announces it as a pushed "running" agent_update, not as a
turn_started stream event. The checks poll for it and read the final idle
once the wake-up settles. waitForFinish can return a stale "idle" right
after a send, and the push stream can miss a message's final text when it
resolves, so the tests read fetchAgentTimeline like the rest of the file.
Helper status comes from agent.provider_subagents.list, since agent_stream
carries no provider_subagent events.

The scenarios need the previous commits together. Run against the frame
heuristic this branch removes, the nested-job test fails: the parent
flips to running once the nested job finishes and stays there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(claude): interrupt a stopped prompt Claude has already taken

Stop before Claude announces a prompt started withdraws it with
cancel_async_message instead of interrupting. Claude can dequeue a
message for its next turn before announcing it, and withdrawing it is
then a no-op that reports cancelled: false, so the stopped prompt kept
running. Interrupt whenever a message could not be withdrawn.

Co-authored-by: Isak Persson <isak.a.persson@gmail.com>

* fix(claude): withdraw a stopped prompt only once

When the withdrawal of a stopped prompt failed, the interrupt path
withdrew a second time, which could cancel a prompt sent while the first
withdrawal was in flight. Withdraw once and interrupt on failure.

The real-Claude steer scenario now waits for the background helper to
exist before steering, so it proves the helper survives the steer.

Co-authored-by: Isak Persson <isak.a.persson@gmail.com>

---------

Co-authored-by: Isak Persson <isak.a.persson@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-07 14:08:48 +02:00
paseo-bot[bot] c90587e5b7 Keep a plugin search term typed while the website page is still loading (#6293)
* test(website): reproduce plugin search dropping a term typed before hydration

Typing in the plugin search box before the page's scripts run leaves the
term in the box but not in React state, so Enter searches without it.

* fix(website): keep a plugin search term typed before the page loads

Text typed into the plugin search box before hydration stayed in the box
but not in component state, so Enter searched without it and the clear
button never appeared. Adopt the box's value once the page hydrates.

Closes #6286

* test(website): cover live browse search typed before the page loads

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 12:49:37 +02:00
paseo-bot[bot] 28f7528588 docs: fix the Tailscale Serve command and log rotation defaults (#6284)
The web UI guide gave the pre-1.52 `tailscale serve https / <target>`
form, which current Tailscale rejects with "invalid number of
arguments". The configuration page said logs keep 2 files in total,
while the daemon keeps 3 rotated files next to the active one.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 09:51:17 +02:00
paseo-bot[bot] f13bb2136c fix(server): keep accented names on downloaded files (#6281)
A file name that fits in Latin-1, like café.txt, went out as a raw byte in
Content-Disposition filename= with no filename* parameter, and Chromium saved
the file under a name taken from the URL (download.txt). Build the header so
every non-ASCII name carries filename*=UTF-8''… with an ASCII fallback.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 08:57:29 +02:00
paseo-bot[bot] 2323ee6844 Keep reporting a Linux daemon as running after its VM resumes from a host sleep (#6279)
* Keep reporting a Linux daemon as running after its VM resumes from a host sleep

A VM paused while its host sleeps keeps its uptime, but its wall clock
catches up on resume. The boot instant derived from the two then lands
after the running supervisor's lock, so daemon status reported it as
stopped and daemon stop released its lock without stopping it.

The lock now records Linux's boot id, and a lock that carries one is
judged by it. Locks without one keep the wall-clock check.

Closes #6019

* Give the OMP idle-deadline test a deadline a slow runner cannot pass before the first wait

With a 1 ms deadline, the gate's first check, made synchronously when the
turn ends, could already be past it on a loaded Windows runner. The turn
then failed before the test's first scheduler wait, and the test timed out.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 07:51:12 +02:00
1e00f452ea Download files whose names have non-ASCII characters (#6278)
* fix(server): encode Unicode download filenames in Content-Disposition

* test(server): cover Korean download filenames

* test(server): validate download token and agent ID formats

* fix(server): set the download header through Express instead of a new dependency

The download route already runs on an Express response, and res.attachment()
builds Content-Disposition with the same content-disposition encoder that
Express ships. Use it and drop the direct content-disposition dependency and
its type package. The header values the download tests assert are unchanged.

Co-authored-by: jasonhnd <3293256+jasonhnd@users.noreply.github.com>

---------

Co-authored-by: Dillon <dillonzq@outlook.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: jasonhnd <3293256+jasonhnd@users.noreply.github.com>
2026-10-07 06:59:53 +02:00
paseo-bot[bot] a162e1075f fix(acp): apply configured models the agent does not advertise (#6273)
* fix(acp): apply configured models the agent does not advertise

A model added through a provider's models or additionalModels was listed
in the picker, but an ACP session only accepted ids the agent advertised
in session/new, so the agent silently ran on its default model. The
registry now passes configured model ids to the session, which applies
them through session/set_model. Unconfigured, unadvertised ids are still
skipped with a warning.

* fix(acp): send configured models when the agent advertises none

Also covers the resume path in the regression test.

* test(acp): keep configured-model tests in the ACP agent suite

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 06:15:19 +02:00
paseo-bot[bot] 8f1091da64 Let a Claude agent leave Auto mode when Claude Code uses Bedrock or Vertex (#6272)
* fix(claude): let an agent leave Auto mode when Claude Code uses Bedrock or Vertex

Changing the mode of a Claude agent whose process is not running starts the
process first. It was started in the agent's current mode, so an agent in Auto
on Bedrock or Vertex failed the Auto check before the new mode applied, and
every mode change and message failed the same way.

Start the process in the requested mode instead.

Closes #5878

* fix(claude): relaunch a query whose mode change Claude Code rejected

A mode change that starts the query now launches it in the requested mode. If
Claude Code then rejects the mode, the agent keeps its previous mode, so mark
that query for relaunch instead of reusing it in the rejected mode.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 04:55:01 +02:00
paseo-bot[bot] dc785bb29a Keep both subagent cards labeled when Claude launches two background agents together (#6268)
* fix(server): keep a subagent card labeled when two background agents launch together

The daemon's stream coalescer kept only the last update per tool call
inside its 60 ms window. When Claude launches two background Agent calls
in one message, the first child's task_started arrives a few ms after the
second call starts streaming, so its labeled card is buffered and then
overwritten by the unlabeled completed Agent update. The coalescer now
folds updates with the same merge the timeline projection applies, so a
labeled detail is not replaced by an unknown one.

Closes #6252

* refactor(server): compute the merged tool call before building the coalescer entry

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-07 02:54:09 +02:00
paseo-bot[bot] 2ecf3ba783 Keep an archived agent archived when a send to it fails (#6255)
* Keep an archived agent archived when a send to it fails

Sending a message to an archived agent unarchives it before loading the
session. When loading failed, for example because the agent's working
directory was removed, the agent stayed unarchived. The send now restores
the original archive timestamp when it fails, matching resume_agent.

Closes #6245

* Restore the archive only when the send cannot load the agent

Concurrent sends share one load, so a failed load leaves no session for
the restore to close. A failure after the load no longer re-archives an
agent another send may already be running.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 21:56:27 +02:00
paseo-bot[bot] 05637a7f05 Show a Claude subagent's auto-mode report as markdown instead of a raw SubagentHandback card (#6248)
* Show a Claude subagent's handback report as its final message

Since Claude Code 2.1.271, a subagent in auto mode delivers its report
through a SubagentHandback tool call instead of its final text. Paseo
showed that call as a generic tool card with the report as escaped JSON.
Live sidechain frames and replayed history now map the call to the
subagent's final assistant message and hide its acknowledgement.

Closes #6241

* Keep a handback report's leading whitespace and hide its acknowledgement by call id

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 20:54:16 +02:00
paseo-bot[bot] a9fef4879a Show an archived ACP agent's history after its worktree is removed (#6228)
* Show an archived ACP agent's history after its worktree is removed

Opening an archived ACP agent loads its history by resuming the provider
session for reading. The provider process was spawned in the agent's
stored working directory, so once that directory was removed (for
example when its worktree workspace was archived) the spawn failed with
ENOENT and the chat opened empty.

A history load now starts the provider process from the home directory
when the working directory is gone, and still names the original
directory in session/load. An interactive resume keeps requiring the
directory.

Closes #5449

* Only treat a missing working directory as gone for a history load

A stat failure other than a missing path, such as a permission error,
now reaches the caller instead of moving the provider to the home
directory.

* Start the history process from home when the stored path is not a directory

A file left where an archived agent's worktree was made the history load
spawn in it and fail with ENOTDIR.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 18:54:59 +02:00
paseo-bot[bot]andDillon 48059aeae3 Show the PR panel's state, activity verbs, and thread badges in the app language (#6225)
* fix(app): show PR panel state, activity verbs, and thread badges in the app language

The PR panel's state label, the verb after each activity author, and the
Resolved and Outdated thread badges were English literals, so they stayed
English in every other language. The state and verb translations already
existed and were unused; the badges get new keys in every locale.

Closes #6214

* fix(app): translate every PR panel verb and state, and phrase them after the author

The zh-CN state and review labels still held English ("Open", "Draft",
"已 review"), and the Spanish and Portuguese verbs were adjectives that
do not read after an author's name. The translation test now checks
each verb and state on its own, so one English label fails it.

Co-authored-by: Dillon <dillonzq@outlook.com>

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Dillon <dillonzq@outlook.com>
2026-10-06 18:03:10 +02:00
paseo-bot[bot] 56d5680742 fix(highlight): keep Astro expressions colored through nested template strings (#6219)
An Astro { } expression containing a template string inside ${} closed at
the first } in that nested string, leaving the rest of the expression
uncolored. Astro and Vue now share one scanner for embedded JavaScript,
which already skips template substitutions.

Closes #6209

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 17:03:17 +02:00
paseo-bot[bot] f5c55dfc55 fix(omp): reopened chats show a compaction row instead of "Unsupported history record" (#6215)
* fix(omp): reopened chats show a compaction row instead of "Unsupported history record"

OMP stores a compaction as a top-level session entry. History replay sent
it to the unknown-record fallback, so after a daemon restart the chat
showed a "Custom message" row reading "[compaction] Unsupported history
record". Replay now maps the entry to a completed compaction row.

Closes #6206

* test(omp): assert on the replayed custom-message rows directly

* test(omp): match only the unsupported-record row after restart

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 16:25:59 +02:00
paseo-bot[bot]andOuyang Xingyuan a700d7875c Stop the provider when archiving an ACP agent that never answers close (#6213)
* fix(acp): terminate the provider when close requests never return

Archive and delete wait on ACPAgentSession.close(). cancel() and
unstable_closeSession() had no timeout. The ACP SDK does not reject a
pending request when the provider never answers, so close() never
reached terminateProcess. The agent was already marked archived and the
provider process stayed alive.

Both calls now use the same 2s withTimeout as probe close. The existing
catch blocks still continue into terminateProcess.

Example:
- Input: archive an ACP session whose cancel and closeSession never settle
- Before: close() waits forever and the provider process stays alive
- After: both calls time out and terminateProcess runs

* test(acp): archive close terminates a real provider that never answers session/close

Spawn a real ACP provider process over stdio that advertises session
close and never replies to it, then close the session the way archive
does. Without the request bound, close() never returns and the provider
keeps running.

Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>

* test(acp): move the close wait and provider cleanup into helpers

Clear the race timer once close settles, and dispose the provider
process and temp directory in afterEach.

---------

Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 14:53:06 +02:00
c301d6e8aa Reinstall local speech models whose extraction was interrupted (#6210)
* fix(speech): publish models only after extraction completes

* fix(speech): reinstall local speech models whose extraction was interrupted

The daemon decided whether a local speech model was installed with its own
check in speech-runtime.ts: every required file exists and is non-empty. A
model whose extraction was stopped part-way passed that check, so the daemon
never called the downloader again and dictation aborted on the truncated
file on every start.

The models module now owns "is this model installed", and the runtime asks
it. A model whose archive is still in .downloads did not finish installing,
since the archive is removed only after the model is in place, so it is
reinstalled from that archive. Extraction goes to a directory beside the
archive and moves into place once complete.

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>

* fix(speech): remove the model archive before moving the model into place

* docs(speech): describe when the model archive is removed

---------

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 14:03:50 +02:00
paseo-bot[bot] b7c8be4081 Show the PR panel's Activity and Add to chat labels in the app language (#6208)
* fix(app): show PR panel activity and add-to-chat labels in the app language

The PR panel's Activity header, its empty state, every "Add to chat"
button, "Add all to chat", "Adding...", and the comment and thread menu
labels were written in the components, so they stayed English in every
other language. The sidebar status group rows also appended an English
" group" to their accessibility label. They now come from the
translation files, with copy for all nine supported languages.

Closes #6203

* test(app): check the new PR panel labels are translated in every language

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 12:55:44 +02:00
paseo-bot[bot] 2974d7c960 fix(desktop): keep browser_type text out of the focused Paseo composer (#6207)
browser_type committed its text with CDP Input.insertText on the tab's
debugger. Chromium commits that text into the focused element of the
whole Paseo window, so when the user was typing in an agent composer
and the browser tab did not hold focus, the agent's text landed in the
composer instead of the page.

Insert the text with the tab's own webContents.insertText, which
commits into the tab's focused element regardless of window focus.

Fixes #6182

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 11:59:59 +02:00
a7f7405c04 fix(highlight): highlight Vue (.vue) files (#6205)
* fix(highlight): add syntax highlighting support for Vue (.vue) files

* fix(highlight): use Vue-aware mixed-language parsing for .vue SFCs

* fix(highlight): make Vue interpolation boundary quote-aware and brace-balanced

The interpolation scanner matched the closing '}}' with a plain
indexOf, so expressions containing '}}' inside strings or nested
object literals ({{ { a: 1 }}}) cut the expression short. Find the
closing delimiter only at brace depth 0, skipping strings, comments,
and regexes like the existing block scanners do.

* fix(highlight): satisfy lint complexity limits in vue parser

Split nestedLanguage into templateOverlay/directiveOverlay/scriptOverlay
and drop the unused findClosingBrace helper. Behavior unchanged.

* fix(highlight): keep an empty {{}} from breaking Vue highlighting

An empty interpolation gave the expression parser a zero-length range,
which Lezer rejects, so the whole file fell back to plain text.

Co-authored-by: Davin <128177533+Davnn1@users.noreply.github.com>

* fix(highlight): color quoted Vue directive values and nested template strings

A quoted directive value went to the expression parser with its quotes,
so it parsed as one string. A template string nested inside ${} ended at
its inner backtick, so a }} inside it closed the interpolation early.

Co-authored-by: Davin <128177533+Davnn1@users.noreply.github.com>

---------

Co-authored-by: davnn <davinvin744@gmail.com>
Co-authored-by: Davnn1 <128177533+Davnn1@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 11:00:38 +02:00
paseo-bot[bot] 2dbfd1e0f8 fix(omp): reopened chats no longer show extra "Custom message" rows (#6198)
After a daemon restart, an OMP chat replays from its session file. Three
kinds of entries that the live timeline never shows were replayed as
collapsed "Custom message" rows:

- the rule-violation reminder OMP adds after a tool call matches one of
  its rules, a developer message that opens with
  <system-reminder reason="rule_violation" ...>, while only the bare
  <system-reminder> tag was hidden
- the ttsr_injection entry OMP records alongside that reminder
- the fileMention message an @-mentioned file adds, and the other OMP
  message roles the live timeline drops

Closes #6131

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 10:02:12 +02:00
paseo-bot[bot] 81a5e5bc61 Show the PR checks summary, sidebar status, and "Worked for" line in the app language (#6196)
* fix(app): translate the PR checks summary, sidebar status labels, and "Worked for" line

These were written in the components, so they stayed English in every
other language. They now come from the translation files, with copy for
all nine supported languages.

Closes #6190

* fix(app): rebuild translated labels when the app language changes

The checks summary, sidebar status groups, and turn footer now take the
translation function, so their memoized labels rebuild on a language
switch. Russian one-check group labels use singular wording.

* test(app): compare compact footer targets at sub-pixel precision

The browser reports the 44pt footer buttons as 43.99999 wide on some
runs, which fails the finger-size check on main as well.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 09:19:52 +02:00
paseo-bot[bot] 0255738ee7 Copy only the highlighted text when a selection starts below an image (#6195)
* fix(app): leave out an image when a copied selection starts below it

A drag that starts in the gap below an image in an assistant reply
anchored at the start of the image's rendered internals, ahead of the
hidden img, so the copy included the image's Markdown although only the
text after it was highlighted. A selection that starts inside an image
and ends past it now starts after the image.

Closes #6187

* fix(app): keep a failed image when a copy starts in its error text

A failed image shows its error message as text inside the image frame. A
selection that starts in that text selected part of the image, so it keeps
the image's Markdown. Only a selection that starts inside the frame before
any of its text starts after the image.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 08:03:57 +02:00
paseo-bot[bot] e3db7c3397 fix(opencode): agent goes idle after a v2 tool permission is denied (#6188)
* fix(opencode): agent goes idle after a v2 tool permission is denied

OpenCode 2.0.20 ends the execution when a tool permission is rejected and
reports the interruption with reason "shutdown", the same reason it uses for
an execution it will resume after a server restart. The turn tracker only
treated "shutdown" as final after a dismissed question, so a denied tool
permission left the agent running with nothing pending, and Stop waited on a
turn that could never complete.

Treat any denied request owned by this session as ending the turn when the
following interruption reports "shutdown". Denials of child-session requests
do not interrupt the parent execution, so they are excluded.

* test(opencode): denying a child session's permission keeps the v2 turn resumable

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 06:08:14 +02:00
paseo-bot[bot] fe10c40b9a fix(app): keep a hover card open when a pressed button disables itself (#6185)
Clicking Refresh on a usage card in the context window details closed the
card within the close grace, with the pointer still on it. Refresh disables
itself while the request is pending, which drops the click's focus to the
document, and the hover card treated any focus leaving the card as a reason
to close.

Only keyboard focus keeps a hover card open, so only losing keyboard focus
now closes it. Whether focus is keyboard focus is read when it arrives,
because the element losing focus no longer matches :focus-visible during
focusout.

Closes #6176

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 05:03:59 +02:00
paseo-bot[bot] 950e1f8f41 Keep an image's alt text and source when copying a selection from an assistant reply (#6181)
* fix(app): copy an assistant image as its Markdown, with alt text

Selecting across an image in an assistant reply copied the rendered img,
which has no alt text and, for data and workspace images, points at a
preview URL. The image frame now carries its Markdown source and alt text
the same way other Markdown elements declare their copy form.

Closes #6163

* fix(app): copy an image selected on its own as its Markdown

A selection inside the rendered image left the image's copy attributes
on the frame outside the range, so nothing was copied as Markdown.

* test(app): name the image-only selection in the copy spec

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 02:07:01 +02:00
paseo-bot[bot] 7a30305503 fix(opencode): agent goes idle after a v2 question is dismissed (#6177)
* fix(opencode): end the v2 turn when the user dismisses a question

OpenCode ends the execution when a question is dismissed but reports the
interruption with the reason "shutdown", which the turn tracker keeps open
waiting for a resume that never comes. The agent stayed running, and Stop,
reload and send were refused. A question dismissed from Paseo now lets that
interruption end the turn as canceled.

* test(opencode): cover the next message after a dismissed v2 question

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 01:06:55 +02:00
paseo-bot[bot] f02cc16bb4 fix(app): close hover cards when the pointer leaves a clicked row (#6167)
* fix(app): close hover cards when the pointer leaves a clicked trigger

A hover card stayed open while any focus sat inside its trigger or content,
so clicking a sidebar workspace row (which focuses the row's button) pinned
the card after the pointer moved away, until Escape or a click elsewhere.
Only keyboard-visible focus now keeps the card open, which preserves the
keyboard entry path added with the shared hover card.

* fix(app): keep a hover card open when keyboard focus arrives during its close delay

Recheck keyboard focus when the close timer fires, and move the hover card e2e steps into sidebar helpers.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 23:06:34 +02:00
paseo-bot[bot]andNurshot 2f0cb2f54b Show the Codex speed bolt in yellow while Fast or Ultrafast is selected (#6152)
* fix(app): fill the speed icon for accelerated tiers

* fix(app): highlight the Codex speed bolt when Fast or Ultrafast is selected

Codex speed moved from the Fast toggle to a Speed selector, and the
composer only colored toggle features, so the bolt stayed muted for
every speed. A select feature is now active when its selected option is
not the default, and the Codex speed takes the same yellow highlight as
the Fast toggle, on the desktop toolbar and in the compact sheet.

Closes #5939

Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com>

* test(app): assert the Codex speed bolt is yellow on desktop and compact

---------

Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 21:10:24 +01:00
paseo-bot[bot] 4ed13fadb6 fix(app): keep list numbers when a copy crosses a code block nested in an item (#6158)
A selection starting after an item's marker demotes that item to a paragraph.
When the item holds a code block, Turndown's HTML re-parse splits the paragraph
into several siblings, and counting siblings numbered the next item too high
("4. Done." instead of "2. Done."). Each item now carries its number before
the re-parse.

Closes #6140

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 21:52:51 +02:00
paseo-bot[bot] 18412604dd fix(app): copy only the code when a selection touches the block beside it (#6138)
* fix(app): copy a code block without its fence when the drag starts beside it

A drag that starts or ends in the gap beside a code block anchors the
selection at the edge of the neighbouring block. Only the code is
highlighted, but the range crossed the block boundary, so the copy took
the Markdown path and wrapped the code in its fence.

A selection now crosses a code block only when it selects content
outside it. Message rows the range touches without selecting anything
are dropped, and a selection whose content all lies inside one code
block copies as that code.

* fix(app): keep a selected image when copying it beside a code block

An image selected next to a code block has no text, so the selection
counted it as empty and copied only the code. Count images as selected
content so the copy keeps the image and the fence, as it did before.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 19:21:46 +02:00
paseo-bot[bot]andMohamed Boudra d826d8109f fix(muse): load models whose effort tiers carry descriptions (#6021)
* fix(muse): load models whose effort tiers carry descriptions

Muse 1.4.2 sends reasoningEffortVariants as described tiers
({ tier, description? }) and the complete effort list on variants, as
the bundled MSP types declare. The catalog schema had the two shapes
swapped, so any model with a described tier failed the whole model/list
parse: the model picker stayed empty and agent creation failed.

Parse both fields as declared, offer the efforts from variants, and
show Muse's tier descriptions on the matching options.

* fix(muse): leave effort descriptions out until the pickers show them

* fix(muse): keep unused catalog metadata outside validation

Described effort tiers are presentation metadata, not selectable efforts. Validating their shape blocks both discovery and session startup even though Paseo never consumes them. Normalize the complete variants list at the wire boundary and cover session readiness and selecting max.

* fix(muse): align consumed session and event wire fields

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-10-05 16:12:12 +01:00
paseo-bot[bot]andMohamed Boudra 62f79e88bb fix(omp): a prompt OMP 18.3 rejects after acknowledging it now fails with OMP's error (#6115)
* fix(omp): fail the turn when OMP 18.3 rejects a prompt after acknowledging it

OMP 18.3 acknowledges a prompt, then reports a rejection such as a missing
API key only as a second failed response frame for the same request id. The
JSONL RPC transport dropped any response with no pending request, so the
turn stayed running with no error.

The transport now publishes a response nothing is waiting for like any
other frame, and the OMP runtime reads a failed prompt response as the
prompt_result error later OMP releases send.

* fix(omp): drop prompt results held for an OMP process that was replaced

Request ids restart with each OMP process, so a rejection the old process
reported after its prompt timed out could fail a prompt on the new process
with the same id. Move the real-OMP rejection case into the existing OMP
real E2E suite.

* test(omp): verify late-response routing at the transport boundary

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-10-05 15:27:26 +01:00
paseo-bot[bot] 9da676d709 Keep the daemon running when nobody reads its output (#6117)
* fix(server): keep the daemon running when its stdout reader is gone

`paseo daemon run` in the foreground hands its stdout and stderr to the
supervisor. When the program that started it closed its end, as
`paseo daemon run | head -1` does, the supervisor's next write failed
with EPIPE and nothing handled the error. The supervisor exited 1, the
worker lost its parent and exited too, and the CLI reported "Daemon
failed to start".

The supervisor now drops output nobody reads and keeps supervising the
worker. daemon.log still records everything. Other stdout and stderr
errors surface as before.

Closes #6114

* test(server): fail on unexpected daemon.log read errors and check stderr reaches the log

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 15:57:59 +02:00
paseo-bot[bot] dfb8a1a2bc fix(omp): an OMP run with a message Paseo does not render now finishes instead of staying running (#6112)
* fix(omp): finish the turn when OMP's run contains a developer message

OMP 18.5 injects hidden developer messages into a run, such as the
rule-violation reminder after a tool call matches a built-in rule. The
agent_end frame then failed schema validation and was dropped, so the
turn stayed running until it was cancelled.

Closes #6104

* fix(omp): parse every OMP message role so agent_end is never dropped

OMP's RPC AgentMessage also defines pythonExecution, hookMessage,
branchSummary, compactionSummary and fileMention. A prompt that
@-mentions a file puts a fileMention message in agent_end, the frame
failed validation and was dropped, and the turn stayed running.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 15:15:10 +02:00
paseo-bot[bot] 8ced059b4d fix(omp): fail the turn when OMP rejects a prompt before its agent runs (#6110)
OMP reports a prompt it rejects before running the agent, such as one
with no API key for the selected model, as a prompt_result with
agentInvoked false, status error, and the error message. Paseo read only
agentInvoked and ended the turn as completed, so the agent went idle with
no reply and no error. The turn now fails with OMP's message.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 12:03:18 +02:00
paseo-bot[bot] da9fae172d fix(claude): keep a subagent's card labeled after a daemon restart (#6100)
After a restart the parent's timeline is rebuilt from Claude Code's
transcript, and the Task/Agent call went through the generic tool
mapper, which has no subagent branch, so the card replayed as a bare
"Agent" with detail type "unknown". Live, the card is built from the
task protocol's declaration instead.

Replay already reads each Task call's type and description from the
parent transcript for the subagent track. Label the replayed cards
from those same facts, through the builder the live path uses.

The shared tool-call parser is left alone: a completed sub_agent card
from it would replace the action log a foreground child's card
carries live.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 10:10:42 +02:00
paseo-bot[bot] f24bb523e8 Exit CLI commands quietly when their output is no longer read (#6097)
* fix(cli): exit quietly when stdout's reader is gone

When the program that launched the CLI closed its end of stdout, the
output write failed with EPIPE and nothing handled the error. Node
printed an unhandled 'error' event and exited 1, and the desktop app's
CLI passthrough could raise Electron's main-process error dialog.

runCli now exits with code 0 on EPIPE from stdout. Other stdout errors
still surface as before.

Closes #5982

* fix(cli): keep running when stdout's reader is gone

Exiting on EPIPE ended a command's own lifecycle: `paseo daemon run`
writes its ready line and then supervises the daemon child, so exiting
there left the child running without its parent. Drop output nobody
reads instead, and let every command finish with its own exit code.

* fix(cli): stop following when stdout's reader is gone

With EPIPE ignored, `paseo agent logs --follow` and `paseo agent attach`
kept running after the program reading their output closed it, until
interrupted. Both now wait through one helper that stops on SIGINT,
SIGTERM, or EPIPE on stdout, so a closed reader stops them at their next
line of output the same way Ctrl+C does.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 09:18:15 +02:00
paseo-bot[bot] cf2b3f4bb3 Name a linked global install as the reason daemon self-update is refused (#6095)
* fix(server): name a linked global install as the reason self-update is refused

npm 7 and later print no link flag in `npm ls --json --long`, so a global
install made with `npm install -g <dir>` or `npm link` was never detected as
linked and self-update reported that the daemon was not running from the npm
global install. Detect the link from the install directory being a symlink,
the same way npm decides it.

Closes #6086

* fix(server): fail the self-update inspection when the install path can't be read

An unreadable install path now surfaces as the update error instead of being
treated as a normal install. Tests build real install directories for both
the linked and the normal case.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 08:04:00 +02:00
a50e14b69e fix(app): keep the centered new workspace form above the keyboard on tablets (#6093)
* fix(app): keep the centered new workspace form usable with the keyboard open

The centered tablet dock translated the whole form by the full keyboard
shift and reserved the keyboard twice to keep its top below the header.
On short tablet viewports such as an unfolded foldable, that left about
20pt for the setup fields and composer, which then overlapped.

Translate the centered form only by the part of the keyboard that
reaches it, and size it to fit above the keyboard.

Fixes #6012

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app): rest the centered new workspace form on the keyboard

The keyboard shift excludes the bottom safe area, but the keyboard also
covers it. Measuring the centered form's clearance and capacity down to
the screen edge left the form one navigation-bar height under the
keyboard, hiding Import session on Android tablets.

Measure both above the bottom safe area so the form ends at the
keyboard's top edge.

Fixes #6012

Co-authored-by: Ivan Malison <IvanMalison@gmail.com>

---------

Co-authored-by: Ivan Malison <IvanMalison@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-05 07:21:35 +02:00
paseo-bot[bot]androyenheart 7b43f21940 fix(app): let Remote SSH connections authenticate to a password-protected daemon (#6081)
Co-authored-by: royenheart <royenheart@outlook.com>
2026-10-05 06:02:33 +02:00
paseo-bot[bot] 36e6ccd6c7 fix(claude): show plan mode when Claude enters it on its own (#6074)
When Claude Code calls its EnterPlanMode tool, it reports the new mode
through a system status message with permissionMode "plan". The Claude
provider only read the compacting status from those messages, so the
session kept the previous mode and never emitted mode_changed: the app,
`paseo inspect`, and the stored agent record all kept showing the old
mode.

The provider now records the mode carried by a status message and emits
mode_changed when it differs, sharing the bookkeeping with the init
message so the plan-approval resume mode stays the mode Claude left.

Closes #6063

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-05 03:04:07 +02:00
paseo-bot[bot] 2187124642 Answer an ACP agent's open permission request when its turn fails (#6045)
* fix(acp): answer open permission requests when a turn fails

When an ACP prompt failed while one of its session/request_permission
requests was still open, Paseo stopped showing the request but never
answered it, so agents that refuse new prompts until the request is
settled stayed stuck until the agent was reloaded.

A turn that ends without completing now answers its open permission
requests with cancelled, as interrupt and close already do.

Closes #6040

* test(acp): cover permission requests left open by a cancelled turn

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 23:31:39 +01:00
paseo-bot[bot] b3ed7363c6 fix(claude): keep a background subagent's card labeled after its child streams (#6067)
Claude Code answers a background Agent call right away, then streams the
child's frames. Each frame re-emitted the parent's card from the sidechain
tracker after the tool-input cache had dropped the call, replacing the
settled, labeled card with a bare running "Task". The tracker now emits the
parent card only while the parent's tool call is still open.

Closes #6059

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 22:07:57 +02:00
paseo-bot[bot] 97083dd736 fix(pi,omp): open sessions whose model was removed (#6061)
* fix(pi,omp): open sessions whose model was removed

Importing or reopening a Pi or OMP session failed with
`Model "<id>" not found` when the session's model was no longer
configured, because the resume launch always passed `--model`.

Resume now launches on the session's own model, which Pi and OMP
replace with their default when it is gone, and switches to the
requested model afterwards only when it differs and is available.

Closes #6039

* fix(pi,omp): fall back only when the requested model is unavailable

Check the provider's model list before switching on resume, so a
failed switch to an available model reaches the caller instead of
silently keeping the session's model.

* fix(pi,omp): match a requested provider:id model on resume

Compare the parsed provider and model id against the session and
available models, so a reference written as provider:id switches
instead of being treated as unavailable.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 17:16:14 +02:00
paseo-bot[bot] 05b074764d fix(cli): report an unknown agent ID as AGENT_NOT_FOUND (#6053)
* fix(server): report an unknown agent as not found to CLI commands

fetch_agent_request answered an unknown agent ID with an error string, so
DaemonClient.fetchAgent threw instead of returning null. Every CLI command
that looks up one agent checks for null to report AGENT_NOT_FOUND, so
delete, stop, inspect, logs and the rest reported their generic failure
code and skipped the "paseo ls" hint.

The daemon now answers an unknown agent with a null agent and no error.
An empty or ambiguous identifier is still an error.

* test(cli): expect the not-found message agent logs now prints

* test(cli): move the unknown agent ID cases into the delete and stop suites

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 14:55:22 +02:00
paseo-bot[bot] 5285b7e502 fix(server): keep a stuck directory search from blocking every other search (#6041)
Directory search shares two filesystem slots across all searches. Searches
over one tree walk it in the same order, so two Add Project searches that
reach the same folder whose read never returns held both slots, and every
later directory search queued behind them: command center file search,
@-file autocomplete, and file-link lookups in unrelated folders all hung
until the read returned.

Searches waiting on the same path now share its pending filesystem call, so
one stuck folder holds one slot and the other stays free.

Refs #4879

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 13:11:40 +02:00
409b8f9312 fix(server): keep a new worktree open when its paseo.json cannot be parsed (#6036)
* fix(server): keep workspaces available after setup config errors

* refactor(server): drop the archive dependency from worktree setup

Setup no longer archives a workspace when it fails, so the setup
workflows no longer take archiveWorkspaceRecord.

Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>

* test(server): stop passing the removed archive dependency to the worktree workflow

* test(server): check paseo.json survives without comparing line endings

Git checks the fixture out with CRLF on Windows, so comparing its exact
contents failed there.

---------

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 12:20:44 +02:00
paseo-bot[bot]andLeandro Ardissone ed48655a77 fix(server): stop directory search from taking every filesystem thread (#6029)
Each pending fs call holds one of libuv's four shared threadpool threads,
and a read on a hung mount, a protected macOS folder, or a huge network
directory does not return. Add Project starts a new search per keystroke
and nothing cancels the old ones, so four stuck searches took every
thread: logging stopped and `paseo project create` timed out until the
read returned or the daemon was killed.

Directory search now runs its filesystem calls through one shared limit
of two, leaving the rest of the pool to the daemon.

Refs #4879

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Leandro Ardissone <59826+lardissone@users.noreply.github.com>
2026-10-04 10:18:19 +02:00
paseo-bot[bot]and3ae3ae 8216e86ecb fix(cli): delete archived agents with agent delete --all and --cwd (#6027)
* fix(cli): delete archived agents in bulk commands

* test(cli): cover archived agents in bulk delete from the CLI suite

The daemon-backed test imported server test utilities from a CLI test,
which the CI contract in scripts/ci-workflow.test.mjs forbids. Cover
--all and --cwd against archived agents in the runDeleteCommand unit
test instead.

* fix(cli): read every history page when bulk-deleting agents

agent delete --all and --cwd read one page of fetch_agents, so agents
past the first 200 were skipped, and fetch_agents leaves out saved
agents whose provider is unavailable. List agents from the paged agent
history, which the History view uses, so bulk delete covers both.

* fix(cli): delete an agent by ID without listing history

---------

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 07:55:21 +02:00
paseo-bot[bot] 79a3942393 fix(server): mark Claude terminals as needing input when Claude goes idle (#6022)
Claude Code reports the kind of notification in `notification_type`, but
the hook parser only read `matcher` and `reason`, which Claude never sends
in Notification input. Every idle_prompt notification resolved to nothing,
so a Claude terminal waiting for the user never showed as needing input.

Closes #5845

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 06:10:19 +02:00
paseo-bot[bot] 4869214bcb fix(cli): confirm daemon restart when provider probes are slow (#6009)
paseo daemon restart gave each daemon status read one second. Status probes
every provider binary, which takes longer than that on machines with several
providers installed, so restart kept retrying until its deadline and reported
RESTART_NOT_CONFIRMED after the worker had already been replaced.

The status read now gets the remaining restart deadline. Connecting to the
replacement keeps its one-second bound, so polling a worker that is not up
yet stays responsive.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-04 01:58:22 +02:00
paseo-bot[bot] ed87679c67 fix(nix): ship the OpenCode v2 bridge plugin in the daemon closure (#5988)
The Nix package copies only what scripts/trace-daemon.mjs lists. The list
named the v1 bridge bundle but not opencode/v2/bridge-plugin.bundle.mjs,
so creating an OpenCode v2 agent from the Nix package failed with
"Bundled OpenCode bridge plugin artifact is missing".

List every bridge bundle under opencode/, and add a dist test that
installs the traced closure the way nix/package.nix does and loads the
bridge plugin for each OpenCode version from it.

Closes #5926

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-03 23:16:11 +01:00
paseo-bot[bot]andScott Woolsey Biggart c7759713ec fix(server): publish workspace script status to every client (#6007)
Starting or stopping a script from one client sent script_status_update only
to that client, so other clients viewing the workspace kept the old status
until they reloaded. Script status is daemon state, so the WebSocket server
now publishes it to every session, and each session delivers it only to its
script_status_update subscribers. Scripts started over MCP use the same path.

Closes #5856

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Scott Woolsey Biggart <297695+swbiggart@users.noreply.github.com>
2026-10-03 23:58:17 +02:00
paseo-bot[bot]andhanhnt2 749fe3e5ae fix(server): keep the local speech library path out of agent environments (#5987)
The supervisor prepended the sherpa-onnx platform package directory to the
daemon worker's loader variable (PATH, LD_LIBRARY_PATH, DYLD_LIBRARY_PATH),
so every agent and terminal inherited it. In packaged desktop builds that
directory resolves inside app.asar, and on Windows Git Bash truncates the
PATH it hands to native executables at that entry.

The local speech worker already sets the loader variable on its own
environment when it is forked, so the daemon worker does not need it.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: hanhnt2 <56497031+hanh9898@users.noreply.github.com>
2026-10-03 22:04:28 +02:00
paseo-bot[bot] 0d05584d04 fix(omp): open agent history that contains a failed browser tool call (#5985)
* fix(omp): load history that contains a failed Paseo browser tool result

Paseo browser tools return a structured { code, message, retryable } error in
the tool result details. The OMP result schema passed details.error through
unvalidated while the history mapper treated it as a string, so one failed
browser call threw and the whole agent timeline failed to load.

Closes #5949

* fix(omp): keep the browser tool's structured error in the tool result

Model details.error as a string or a structured { message, ... } error
instead of dropping non-string values, so the expanded tool row keeps
the error code and retryable flag, and read the failure text from the
structured message.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-03 19:23:11 +02:00
paseo-bot[bot]andilteoood 5375f43a05 feat(plugins): expose agent closed lifecycle event (#5971)
* feat(plugins): expose agent closed lifecycle event

Emit the event when a live agent reaches its closed state so plugins can release agent-scoped resources. Keep archive and close notifications distinct.

Co-authored-by: ilteoood <6383527+ilteoood@users.noreply.github.com>

* fix(plugins): finish close hooks during daemon shutdown

Wait briefly for pending lifecycle events before stopping plugins so agent close hooks can release their resources during shutdown.

Co-authored-by: ilteoood <6383527+ilteoood@users.noreply.github.com>

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: ilteoood <6383527+ilteoood@users.noreply.github.com>
2026-10-03 11:36:19 +01:00
paseo-bot[bot] e10f6d2e2c Let plugin client code import packages that declare only main (#5838)
The plugin compiler builds client code with esbuild's neutral platform,
which reads no package.json entry fields, so any dependency without an
exports map failed with Could not resolve. Read module and main for the
client target, as the server target already does through the node
platform.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 14:02:49 +02:00
95774ba71f Show each Paseo skill once in Codex (#5827)
* fix(skills): install one Codex-discoverable orchestration copy

* fix(skills): keep installed skills when agent skill roots are symlinked together

Retiring the old Codex copy deleted the skills just installed when
~/.codex/skills resolved to ~/.agents/skills or ~/.claude/skills (or the
reverse), and status reported drift forever. The Codex skill directory is
now treated as a leftover only when it is a separate directory from the
shared and Claude copies.

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>

* test(e2e): expect agent skills in the shared and Claude roots only

The skills settings spec still expected every skill in ~/.codex/skills.
It now checks the two install roots and that ~/.codex/skills holds no
Paseo skill, which fails against the previous installer.

---------

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 12:04:05 +02:00
paseo-bot[bot] 06213e49bf Complete every Tintinweb subagent in a grouped completion (#5826)
Tintinweb batches completions that land close together into one
subagent-notification, with the first agent at the top level and the
rest in details.others. The adapter only read the top level, so every
other agent in the group stayed running in the Subagents track, live
and after a reload.

Closes #5822

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 10:11:05 +02:00
paseo-bot[bot]andGabriel Giordano d30e99c858 Log why a resumed Claude agent has no history (#5818)
When a resumed Claude agent's transcript was missing, history loading
returned without a log line, and when reading or parsing the transcript
threw, the error was swallowed. Either way the agent opened with an
empty timeline and daemon.log said nothing. A missing transcript is now
logged at info with the session id, cwd and the path that was checked,
and a read or parse failure is logged as a warning with the error.

Closes #5438

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Giordano <hello@gabrielgiordano.com>
2026-10-01 06:56:46 +02:00
paseo-bot[bot] 15f1431416 Let bundlers resolve the published @getpaseo/relay package (#5815)
The relay exports map sent the import and default conditions to src/*.ts,
which the published package does not include, so any bundler resolving
@getpaseo/client failed with 'Could not resolve "@getpaseo/relay/e2ee"'.
Every condition now resolves to dist, like @getpaseo/protocol and
@getpaseo/client. The client build now builds relay first, and the Metro
workaround for compiling relay source is removed.

Fixes #4730

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 05:01:45 +02:00
paseo-bot[bot]and3ae3ae b701cb2dcd Show failed Codex image generations in the chat (#5810)
When a Codex image request fails, Codex completes the imageGeneration
item with status "failed", an empty result, and an optional failure
record. The image mapper only knew how to render a successful image, so
it returned nothing and the item vanished from the live timeline and
from reopened history. Failed items now map to a failed image_generation
tool call carrying the failure details.

Closes #5619

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: 3ae3ae <83870598+3ae3ae@users.noreply.github.com>
2026-10-01 03:57:56 +02:00
paseo-bot[bot] 4893629ff5 Show the thinking level an agent runs on in its composer (#5804)
The composer picked the thinking level from what was chosen at launch and
fell back to the model's catalog default when nothing was chosen. A Pi agent
started without a thinking level runs on Pi's own configured default, so the
composer showed Medium while the agent ran on High. The composer now prefers
the level the running agent reports when it is one of the model's options,
the same way it already prefers the running model.

Fixes #5800

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 02:55:22 +02:00
paseo-bot[bot]andYuval Marcus e0a0cf990d Ask OpenCode before running a Paseo tool (#5801)
OpenCode checks permissions for its own tools and wraps MCP tools in an
ask, but leaves plugin tools to call ctx.ask themselves. The bridge plugin
never did, so an "ask" rule for paseo_* tools never produced a prompt and
the tool ran. The bridge now asks with the tool's catalog name before
dispatching, the same shape OpenCode uses for MCP tools. With no paseo_*
rule OpenCode's default allow applies, so nothing new prompts.

Fixes #5324

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Yuval Marcus <ymarcus93@gmail.com>
2026-10-01 01:15:34 +02:00