Commit Graph
100 Commits
Author SHA1 Message Date
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
paseo-bot[bot] 20a887f4cc Show an agent's own model in the composer when the catalog lacks it (#5796)
The composer fell back to the catalog's default model whenever the agent's
model was missing from the loaded model list, so a Codex subagent started
on a model the catalog did not list yet was labeled as the default model,
with that model's thinking options.

Fixes #5791

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-01 00:00:16 +02:00
paseo-bot[bot]andodoo-mvds a7c9c05bef Start new workspaces off origin/main at the remote tip (#5788)
* fix(server): start new worktrees at the remote tip of an origin/* base

Branching off a remote-tracking base such as origin/main used whatever
the repository had cached. Nothing on the branch-off path fetched, and the
background fetch only runs while a workspace of that repository is
observed, so a new workspace could start hours or weeks behind the remote.

Fetch just the base branch from its remote before git worktree add. The
refspec is passed on the command line so remote.<name>.fetch is untouched,
and a failed fetch falls back to the cached ref so creation still works
offline.

Co-authored-by: odoo-mvds <mv@droggol.be>

* fix(server): fall back to the cached base before the create request times out

A remote that accepts the connection and never answers held the base-ref
fetch for its 120s cap, past the 60s client request timeout, so
paseo worktree create failed and the worktree appeared later. Cap the
fetch at 15s so creation falls back to the cached ref in time.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: odoo-mvds <mv@droggol.be>
2026-09-30 22:59:33 +02:00
paseo-bot[bot] 7a64eef491 Show the command on Codex terminal rows (#5774)
* fix(codex): label terminal writes with the command Codex started

Codex app-server reports the PTY process id on the commandExecution item,
and no longer sends the legacy exec_command_end event that carried it as
text. Record the process-to-command label from the item so terminal
writes show which command they went to.

* ci: rerun after website e2e server start timeout

* fix(codex): unwrap the shell from terminal labels like shell rows

Terminal labels now use the same command normalization as the shell row,
so a Linux wrapper such as /usr/bin/zsh -lc 'python3 -i' labels the
terminal as python3 -i.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 20:50:24 +02:00
paseo-bot[bot] d070db15b3 fix(acp): report the agent's error when session setup is rejected (#5765)
ACP agents reject mode, model, thinking, and feature writes as plain
JSON-RPC error objects. Those calls skipped the request error
normalization, so schedule runs and metadata generation recorded
"[object Object]" instead of the agent's message.

Refs #4666

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 18:19:05 +02:00
paseo-bot[bot]andArietidsZ 4417d7c47a fix(pi): give Pi's built-in MCP the agent's MCP servers (#5762)
Pi 0.99 ships MCP as a built-in extension. Paseo only recognized
pi-mcp-adapter's /mcp command, so a Pi agent without the adapter started
without Paseo's MCP servers. Pi 0.99 also rejects the adapter's
--mcp-config flag, so the servers are now registered with
pi.registerMcpServer from Paseo's per-session extension. Sessions that
load pi-mcp-adapter keep the --mcp-config path.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: ArietidsZ <69024461+ArietidsZ@users.noreply.github.com>
2026-09-30 16:08:18 +02:00
paseo-bot[bot] 8cccc1f6fa fix(codex): answer each approval callback that shares one item (#5758)
Codex sends several command approval requests for one item when it
approves subcommands separately, telling them apart by approvalId. The
permission id was derived from the item id alone, so the second request
replaced the first and answering it left the first request unanswered.
Key the permission on approvalId when Codex sends one.

Closes #5615

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 14:09:56 +02:00
paseo-bot[bot] b1a3b0772d fix(claude): skip user and project hooks for internal agents (#5750)
Branch-name, commit message, and pull request text generation run as
internal Claude agents in the workspace, so a project Stop hook that
pushes HEAD published the placeholder branch before the rename.

Closes #5742

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 12:10:14 +02:00
paseo-bot[bot] 5599f9e567 fix(cli): fail agent open for an agent the daemon does not have (#5727)
paseo agent open launched Desktop with whatever string it was given,
printed "opened" and exited 0, so a bad ID moved the user to a broken
"Agent not found" tab and the calling script saw success. Look the agent
up on the daemon first, the way inspect and mode do, and open Desktop on
the ID the daemon resolved.

Closes #5721

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 02:55:44 +02:00
paseo-bot[bot] 4e9a4582b7 fix(app): stop connecting to a host removed during its first probe (#5722)
Closes #4815

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-30 02:12:03 +02:00
paseo-bot[bot]andBae Seongmin 53ee9cd993 fix(codex): settle declined commands and file edits as failed (#5717)
Codex completes a command or file change with status "declined" when its
approval is denied. The shared status normalizer did not know that word
and fell back to "running", so the denied command kept a spinner after
the turn ended.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
2026-09-30 00:06:21 +02:00
paseo-bot[bot]andBae Seongmin 767b0e970b Rewind legacy Codex threads on Codex versions without thread/rollback (#5711)
Codex 0.156 removed thread/rollback. Rewinding a legacy-history thread
forked it and then called rollback on the fork, so the rewind failed and
left an unused fork behind. Read the Codex version from the initialize
userAgent and, when rollback is gone, fork before the target turn the
same way paginated threads already do. Older servers keep fork plus
rollback.

Closes #5616

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
2026-09-29 22:59:00 +02:00
paseo-bot[bot]andMatt Cowger d3c902e9fa fix(opencode): fill the context meter during v2 turns (#5710)
OpenCode v2 sessions never reported context used or the model's context
limit, so the composer meter was an empty ring during a turn and vanished
when it ended. Each finished step now reports its context against the
selected model's limit.

Closes #5701

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Matt Cowger <matt@cowger.us>
2026-09-29 22:10:27 +02:00
paseo-bot[bot] 5de45e2086 fix(desktop): capture the annotated element when the app is zoomed (#5703)
The browser pane measures the selected element in page CSS pixels, but
capturePage takes device-independent pixels. Paseo's zoom carries into the
in-app browser, so at any zoom other than 100% the screenshot attached to
an annotation was offset and undersized. Scale the region by the guest's
zoom factor before capturing.

Closes #5690

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 20:08:05 +02:00
paseo-bot[bot] 5764863f63 fix(opencode): show v2 patch edits as edit diffs (#5696)
OpenCode 2 reports GPT models' apply_patch tool calls under the name
patch. The OpenCode tool-call parser only recognized apply_patch, so
these calls fell through to an unknown tool card with the raw patch
text. Map patch payloads the same way as apply_patch.

Closes #5643

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 19:15:39 +02:00
paseo-bot[bot] a8f5d969b8 fix(codex): keep the default effort after a daemon restart (#5686)
A Codex agent created without a thinking option ran on the configured
default effort until the daemon restarted. The resumed session never
resolved the defaults, so it showed thinking auto and sent turns with no
effort. Resolve unset model and effort for a resumed thread the same way
a new thread does.

Closes #5673

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 17:53:43 +02:00
paseo-bot[bot] 23c4404b95 fix(codex): keep output schema fields named like schema keywords (#5684)
The Codex output schema normalizer walked every key as if it held a
schema, so a field named "properties" (or a literal default, enum, or
const value shaped like an object schema) was rewritten with invented
required and additionalProperties entries. Codex then rejected the turn
with invalid_json_schema. Walk only the keywords that hold schemas.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 16:49:03 +02:00
paseo-bot[bot] 2838922ade fix(opencode): show the question text and typed answer in v2 question cards (#5679)
OpenCode v2 sends each question as a form field whose title is the short
header, whose description is the question, and whose custom flag allows a
typed answer. The mapper used the field key as the header and the title as
the question, and dropped custom, so the card showed only the header and
had no answer field.

Closes #5667

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 16:00:31 +02:00
paseo-bot[bot]and3ae3ae aff476b01b fix(codex): insert custom prompt arguments literally (#5669)
Custom prompt expansion replaced each placeholder kind in its own pass,
so text inserted by an earlier pass was expanded again ($ARGUMENTS
containing "$1"), and named values went through String.replace, which
reads "$&" and friends as replacement patterns. Match every placeholder
in one pass and insert values from a callback.

Closes #5613

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-09-29 13:51:43 +02:00
paseo-bot[bot] 87818812ad Stop the desktop app from launching a process every second when no local daemon runs (#5636)
* fix(desktop): stop launching the CLI every second when no local daemon runs

The app polls the local daemon status once a second until it learns a
server id, and a stopped daemon has none, so the poll never ends. Each
poll ran `paseo daemon status` in a new process, which on Windows is
Paseo.exe and shows the busy cursor about once a second while the app
sits idle. The status handler now answers "stopped" in-process when the
home has no running daemon, which is what the CLI reports in that case.

* fix(desktop): report an errored daemon when its state file cannot be read

Reading the daemon instance record ran before the try in
resolveDesktopDaemonStatus, so an unreadable paseo.pid made the status
IPC reject instead of returning the errored status the CLI failure path
produced.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 11:19:19 +02:00
paseo-bot[bot] b9ac1d4982 fix(app): add a Back to plugins link to plugin settings screens on desktop (#5620)
On the wide settings layout, a plugin settings screen showed only its title
and the plugin's content, so getting back to the Plugins page meant finding
Plugins in the sidebar again. The compact layout already had a Back header
and project settings already had a desktop "Back to projects" link. Plugin
settings screens now render a "Back to plugins" link on desktop the same
way, including when the screen is offline or unavailable.

Closes #5518

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 09:10:21 +02:00
paseo-bot[bot] d91171954b fix(opencode): keep v2 edit text in completed edit details (#5609)
OpenCode v2's edit tool takes `path` instead of `filePath`, so the
OpenCode edit schema missed it and the shared fallback dropped the
camelCase oldString/newString. Completed edits reached the timeline as
{type: "edit", filePath} with nothing to diff.

Closes #5607

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 08:01:59 +02:00
paseo-bot[bot]andDaye f232d23e9a fix(client): let SDK callers choose what a send does to an active turn (#5605)
PaseoAgentSendOptions was a hand-copied subset of the daemon client's
SendMessageOptions and dropped activeTurnBehavior, so
agents.ref(id).send(text, { activeTurnBehavior: "steer" }) and run() failed
to typecheck even though the value is forwarded to the daemon. The public
type is now the options type send() passes through, so the two cannot
drift again.

Closes #4555

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Daye <2077152374@qq.com>
2026-09-29 06:53:14 +02:00
paseo-bot[bot] 68e722ec2e fix(app): request a chat outline only for a timeline the daemon has served (#5602)
A new agent's draft pane rendered the chat outline with its local tab or
draft id, so the web app asked the daemon for that id's prompt index before
the agent existed and the daemon logged Agent not found. The outline now
waits until the viewed timeline has an epoch, which only a daemon-served
agent timeline has.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 06:16:30 +02:00
paseo-bot[bot] 3b41183606 fix(app): start the context meter ring at twelve o'clock on web (#5598)
The ring's quarter-turn lived in a Unistyles style on the react-native-svg
root. On web, Unistyles only turns styles into classes for the React Native
components its Babel plugin rewrites, so the Svg received an empty style and
the arc started at three o'clock. The rotation now lives on the progress
circle as an SVG transform, which renders the same on web and native.

Closes #5596

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 04:57:36 +02:00
paseo-bot[bot]andIvan Malison 690f5298cb fix(app): keep draft composers off the agent command list before a project and model are chosen (#5594)
A draft composer passed no command config when it was missing a project
or a provider, which is what a running agent's composer passes. The `/`
menu then listed commands by the draft key, got "Agent not found", and
showed /clear and /exit. Drafts now always report a command target
(ready, needs-project, needs-provider), and an incomplete draft asks for
the missing choice instead of querying.

Closes #5516

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
2026-09-29 04:08:31 +02:00
paseo-bot[bot] 68938b668e fix(skills): make the paseo-plugin composer pill example match the plugin SDK (#5591)
The example passed title, Component, and onPress straight to
addComposerPill and treated its return value as a remover function, so it
failed to typecheck against @getpaseo/plugin. It also followed agents with
agents.subscribe, a local listener that never delivers agents that exist
before the plugin loads. The example now uses the button descriptor and an
owned list subscription, like the plugin reference and local-plugin
example, and a test typechecks it against the SDK.

Closes #5517

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 02:51:54 +02:00
paseo-bot[bot] 560e478ef5 Wait for the connection before starting a file upload (#5585)
An upload started while the client was reconnecting, such as right after
returning from the Android file picker, queued its request but sent the
file frames at once. The frames were dropped and the upload failed with
"Connection changed during file upload". The upload now waits on the
same connection queue as other requests before it sends anything.

Closes #5512

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 01:05:46 +02:00
paseo-bot[bot] 849a876bce Keep completed plugin-provider sessions error-free across a plugin reload (#5579)
Removing a plugin provider closed its runtime and published a runtime
failure for every open session, so an idle session whose turn had already
completed gained a "Provider connection closed" error. Only a turn that
is still in flight is interrupted now; an idle session goes stale and its
next prompt reopens it from persistence.

Closes #5535

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-28 22:55:07 +02:00
paseo-bot[bot] f4ba16a0b3 fix(app): keep line breaks in streamed replies when a chunk ends in indentation (#5577)
While a reply streams, presentation reparses only the growing last Markdown
block: the cached block text plus the new chunk. splitMarkdownBlocks drops a
trailing whitespace-only line, and only trailing "\n"s were added back, so a
reply that paused on "]\n   " cached the block as "]". The next chunk " B -->"
then continued the previous line, joining two lines of a code block. Codex
streams a line break and the next line's indent as separate deltas, so its
Mermaid diagrams hit this and failed to render. History reparses the whole
text, which is why a reload showed the reply correctly.

Keep every trailing blank line the split drops on the last block.

Closes #5456

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-28 22:09:16 +02:00
paseo-bot[bot] 9d010211a2 Archive a custom Codex provider's session when its agent is archived (#5572)
A provider that extends Codex runs through the registry's client wrapper,
which forwarded session listing and import but not native archive or
unarchive. Archiving such an agent left its Codex thread unarchived, so
the session stayed in Import session and could be imported again. The
wrapper now forwards both, the same as stock Codex.

Closes #5454

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-28 21:14:58 +02:00
paseo-bot[bot]andMohamed Boudra 05874e289a Keep relative timestamps current in agent, schedule, and import-session rows (#5340)
* Keep relative timestamps current in agent, schedule, and import-session rows

Agent list rows, schedule rows, and import-session rows formatted their
"5m ago" labels once at render, so an idle row kept its first label until
something else re-rendered it. They now use useTimeAgo, which rides the
same shared ticker the sidebar and desktop tabs already use.

Closes #4625

* Say "just now" for the whole first minute so fresh labels stay honest

The shared ticker wakes once a minute, so a seconds label could only go
stale. The prose and compact formatters now share one age description,
and the schedule spec anchors its clock to the schedule's creation time.

* Run every cleanup in the schedule relative-time spec and name its steps

* Keep relative labels current with focused subscriptions

* Update usage card after relative-time hook rename

* Anchor import-session age spec to fixture time

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-09-28 18:00:54 +01:00
b85b44aea1 Show a host's new version after its daemon restarts (#5351)
* Show a host's new version after its daemon restarts

The app wrote a host's server info into the session store only when a
client was first mounted, and a reconnect reuses the same client, so
after a daemon upgrade or restart the host page and version checks kept
the old version until the app restarted.

The host runtime now projects the client's current server info into the
session store on every connection change, keeping the last known value
while disconnected. The React mount effect that duplicated this write is
removed.

Closes #5236

Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com>

* Keep a caller-owned test daemon home across startup retries

A failed start removed the home even with cleanup disabled, so a retry on a
taken port came back with a new serverId. The host restart spec now drives a
restartable host daemon helper that owns its port, home, and teardown.

* Remove only the test daemon dirs a failed start created

A failed attempt now removes the temp home and static dirs it created and
leaves caller-supplied ones, so cleanup: false no longer leaks per retry.

* Use function declarations in the test daemon helpers

* test(app): cover host versions across daemon disconnect and reconnect

The host settings badge retains its last known version during a transport failure; the help menu should show the replacement version after reconnect without changing another host.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-09-28 17:40:41 +01:00
paseo-bot[bot]andMohamed Boudra 7bb7d4ed08 Open a subagent in the split pane it was opened from (#5451)
* fix(app): open a subagent in the pane it was opened from

Opening a subagent from an agent's composer placed the new tab in the
focused pane. Clicking the subagent row does not focus its pane, so in a
split view the subagent landed in whichever pane last had focus.

Place an implicit main-location open that has a parent tab in that tab's
pane.

Refs #5433

* test(app): cover subagent split-pane placement in browser

* test(app): use browser repro for subagent pane placement

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-09-28 17:27:37 +01:00
paseo-bot[bot]andMohamed Boudra 8de52a3c99 Report a background send_agent_prompt's started turn as running (#5386)
* Report a background send_agent_prompt's started turn as running

The background branch read the target's status right after dispatch,
before the provider acknowledged the turn, so an idle target read as
idle. Wait for the run to start first, as create_agent and the app's
send path already do.

* Check the caller still gets the finish notification after a background send

* Acknowledge the held turn start explicitly in the send_agent_prompt test

* Cover immediate completion after background turn start

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-09-28 16:54:22 +01:00
paseo-bot[bot] dffde6ae8b fix(codex): read custom prompts from the provider's own CODEX_HOME (#5450)
A provider that extends codex and sets CODEX_HOME in its env runs Codex
against that home, but the daemon listed, expanded, and fell back to
skills from its own CODEX_HOME or ~/.codex. The / menu offered another
home's prompts and never the provider's own, and invoking one of the
provider's prompts sent the raw slash text to Codex.

Resolve the Codex home from the same environment the app-server is
launched with, and give it to the session.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-28 16:49:02 +01:00
paseo-bot[bot]andMohamed Boudra a43c8d888f Show the Cursor icon for terminal profiles that run cursor-agent (#5379)
* Show the Cursor icon for terminal profiles that run cursor-agent

The Cursor CLI binary is cursor-agent, so a profile created in settings
with that command matched no known icon and fell back to the generic
terminal icon. Map cursor-agent to the cursor icon while keeping
command matching exact.

Closes #4702

* Verify Cursor terminal profile icons in real browser

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-09-28 16:48:20 +01:00
paseo-bot[bot] 827178df90 Keep a custom Codex provider and Paseo tools after rewinding
Rewound Codex forks receive the same provider and runtime MCP config as thread start and resume. Covers paginated and legacy rewind paths.\n\nCloses #4542\nCloses #3205\n\nCo-authored-by: zbaibg <zbai29@wisc.edu>
2026-09-28 16:29:25 +01:00
paseo-bot[bot] fffd76e8fb List a custom Codex provider's sessions in Import session (#5446)
A provider that extends codex with its own OPENAI_BASE_URL starts its
threads under its own Codex model provider id. Codex's thread/list returns
only the model provider its config selects unless the request names
others, and the import scan named none, so those sessions were missing
from the import list. After switching the default provider in the Codex
config, stock Codex listed them instead and imported them onto itself.

Ask Codex for the custom provider's own threads when listing its
importable sessions. Stock Codex keeps its current listing.

Closes #5381

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 17:15:53 +02:00
paseo-bot[bot] cb9654a65c fix(server): keep the daemon running when daemon.log cannot be written (#5445)
A failed write to daemon.log (for example ENOSPC on a full disk) made the
supervisor's rotating log stream emit an unhandled 'error' event, which
killed the supervisor; the worker then exited on lost supervisor liveness.

The supervisor now treats daemon.log as best effort: a failed stream is
reported once on stderr, dropped, and reopened on the next log line, so
logging resumes once the file is writable again.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 15:56:42 +02:00
paseo-bot[bot]andqinkangdeid 3dc17b7f30 fix(acp): keep the daemon running when an ACP agent cannot start (#5439)
Opening an archived ACP agent whose worktree was removed spawned the
provider in the missing directory. The session spawn had no listener for
the child's error event, so Node's ENOENT became an uncaught exception
and the daemon restarted, then crashed again when the client retried.

The session spawn now rejects initialization on a spawn error, the same
way the provider probe already did, and both share one helper.

Closes #5435

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: qinkangdeid <qinkangdeidd@gmail.com>
2026-09-26 15:10:44 +02:00
paseo-bot[bot] 76a9781ba5 Read Claude history from the provider's own config directory (#5437)
A provider that extends claude and sets CLAUDE_CONFIG_DIR in its env
writes transcripts and settings.json under that directory, but the
daemon looked them up under its own CLAUDE_CONFIG_DIR or ~/.claude.
Agents on that provider reopened with an empty timeline after a daemon
restart, their sessions were missing from the import list, and models
from its settings.json were missing from the catalog.

Resolve the config directory from the same environment the provider is
launched with, and drop the test-only configDir client option.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 13:57:27 +02:00
paseo-bot[bot] a272a22d7e fix(opencode): hide user messages OpenCode marks synthetic (#5434)
OpenCode adds text to user messages on its own behalf (continue prompts after
compaction or a subtask, MCP resource contents, plugin prompts) and flags that
text `synthetic: true`. Its own UI hides it. Paseo built user bubbles from every
text part, so reopening an OpenCode chat showed those messages as if the user
had typed them, and appended synthetic text to the user's own message.

Closes #5425

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 13:24:18 +02:00
paseo-bot[bot] 26f227bed9 fix(pi): keep a chat rewind after the daemon restarts (#5432)
Pi's navigateTree without a summary only moves the session's leaf in
memory and appends nothing to the session file. Pi reopens a session at
the file's last entry, so after a daemon restart the resumed chat showed
the messages the rewind had removed.

After navigating, the Paseo extension now appends a paseo_rewind custom
entry on the new branch. Custom entries carry no model context, and the
file's last entry now sits on the rewound branch, so Pi reopens there.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 11:58:43 +02:00
paseo-bot[bot] aa3ffaeb6c Refresh project skills in the / menu after the checkout's branch changes (#5415)
Draft slash commands are cached per provider and checkout with no expiry, so a skill from the previous branch stayed listed (or a new branch's skill stayed hidden) until the app restarted. A pushed checkout status that reports a different branch now drops that checkout's cached draft commands, and the next / menu fetches them again.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 11:06:22 +02:00
paseo-bot[bot] 513f2a9ea2 List a generic ACP agent's slash commands in a new agent's / menu (#5411)
* List a generic ACP agent's slash commands in a new agent's / menu

Generic ACP agents advertise slash commands with available_commands_update
after session/new. The new-agent / menu opens a short-lived session and asks
for its commands right after session/new, so it always got an empty list,
which the app keeps for the draft. Wait for the first batch, bounded by the
existing initial-commands timeout, as the Cursor, Kiro, and TRAE clients do.

Closes #4759

* Keep the generic ACP command test independent of scheduling

* Wait for agent state writes before removing MCP test temp dirs

The blocking send_agent_prompt timeout test removed its temp dir while
AgentManager background tasks and AgentStorage writes were still in
flight, which intermittently failed cleanup with ENOTEMPTY on Linux CI.
Flush both before removing the dir in the tests that use real storage.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-26 10:19:39 +02:00
paseo-bot[bot] 8cd989529e Notify a caller once when it prompts a child that is still running (#5407)
* Notify a caller once when it prompts a child that is still running

create_agent and a background send_agent_prompt each armed their own
finish notification for the same child and caller. When the second
prompt replaces the child's running turn, the child never goes idle in
between, so its one finish fired both and the caller got the same
notification twice. Arming again for a child and caller now replaces
the earlier notification.

Closes #5397

* Declare the notification test helper as a function

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-25 17:02:10 +02:00
paseo-bot[bot] 04c3e003f8 Keep model picker rows free of nested buttons on web (#5404)
* Keep the model row's profile action out of the row button on web

On web the model row renders as a <button>, and the create/edit profile
action inside it rendered a second <button>, so React logged "<button>
cannot be a descendant of <button>" for every row. The action now sits
beside the row button inside the same hover boundary, positioned over a
slot the row reserves, so layout and hover reveal are unchanged.

Closes #5385

* Check the create-profile action leaves the composer's model unchanged

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-25 16:19:28 +02:00