Order ordinary sidebar workspaces by Created or Last prompt, preserve project and pin ordering, and keep later active or unacknowledged work visible beyond the first seven rows. Preserve reveal state through live updates and verify sorting, visibility, compatibility, and rendering behavior.
Protect relay sessions against replayed commands with authenticated Noise NK negotiation, ordered records, and identity-bound security pins. Preserve legacy admission during migration and add daemon enforcement policy.
All 21 CI checks passed. General engineering review is complete; specialist cryptographic review of the handwritten Noise implementation and static-key reuse remains required before release or deployment.
Closes#359
Workspace runtime snapshots lived inside refcounted watch targets and vanished when the last session disconnected. Keep the last observed git and forge data in daemon memory so reconnecting clients receive it before refresh completes, while watch lifetime continues to govern refresh only.
* refactor(terminal): remove the WebView terminal renderer
Native uses the native grid renderer only; web keeps xterm. The legacy
renderer setting, its diagnostics toggle, the esbuild WebView bundle and
its CI drift check are gone.
* test(terminal): target the visible terminal surface in the file paste spec
A second page opening a terminal in the same workspace adds a retained,
hidden surface to the first page, so the unscoped locator matched two
textareas and failed strict mode.
* test(server): wait for the shortstat warm instead of polling 500ms
The background warm shells out to git; on Windows CI that regularly
outlasts the 20x25ms poll and fails the test. Use the completion callback
the warm already exposes and keep the non-blocking check.
* feat(terminal): support image paste into agent TUIs, local and remote (#3725)
Codex and OpenCode receive image pastes by binding Ctrl+V (\x16) and
reading the OS clipboard of the machine they run on; no image bytes
travel through the pty. Paseo's terminal swallowed that keystroke and
only ever pasted text, so image paste silently did nothing.
- web/Electron runtime: forward \x16 when the clipboard holds no text,
keep the existing text-paste path otherwise (bash quoted-insert stays
intact)
- new terminal.clipboard.write_image RPC ships client clipboard bytes to
the daemon, which writes them onto its host clipboard (osascript /
wl-copy / xclip / Set-Clipboard) so remote clients work like local
ones; gated by server_info.features.terminalClipboardImage with a
bare-\x16 fallback for older daemons
- clipboard tools are waited on via their exit event, not stream close:
wl-copy and xclip fork a selection owner that inherits our pipes, so
waiting for stream closure hung every Linux write until timeout
- headless hosts without a clipboard get a temp-file fallback: the
daemon materializes the image under os.tmpdir() and returns its path;
the client injects it as bracket-pasted text
- all four renderers covered (web DOM, Electron, native grid, WebView
bridge); mobile Paste button enables for image clipboards and toasts
failures in all 10 locales
* fix(terminal): bind image paste to private retained files
* fix(terminal): recover interrupted image metadata safely
* fix(terminal): retire image files with background lifecycle maintenance
* fix(terminal): check live paste modes and refresh native bridge
* fix(terminal): order image paste input and isolate failures
* fix(terminal): normalize paste and release failed creation reservations
* fix(terminal): validate missing Windows launch paths before spawn
* fix(terminal): keep input live during cancellable image uploads
* test(terminal): follow streamed input-mode updates
* feat(terminal): upload dropped images through the paste flow
* feat(terminal): paste and drop images through file uploads
Terminal image paste and drop now reuse the existing upload path
(client.uploadFile -> file.upload.request -> FileUploadStore) and paste
the quoted host path through the pane's input, bracketed when the app
asked for it. This replaces the PR's daemon-side image store, RPCs,
paste permits and file lifecycle, so the daemon, protocol and client
diffs for this feature are empty.
- terminal/image-paste owns a per-pane serial queue: gesture order,
keystrokes stay live, Ctrl+C and pane/connection teardown cancel
pending pastes, and a cancelled path is never pasted.
- Clipboard text wins over an image; any image/* type is accepted up
to the composer's 50MB upload limit.
- Host paths are quoted for the host OS by path shape.
- One failure toast; i18n keeps two keys.
- Browser e2e covers two panes pasting distinct PNGs, drop, ordering,
Ctrl+C cancel and disconnect against a real daemon.
Co-authored-by: Gerard Balaoro <miogerard@yahoo.com>
* fix(terminal): translate the disconnected image upload error
* feat(terminal): report image uploads through toasts
Show a lasting "Uploading image…" toast while an image uploads, replace
it with "Image uploaded" or a failure toast that carries the reason, and
dismiss it when Ctrl+C cancels the upload. Toast show now returns a dismiss
scoped to that toast, so the pane can clear its own toast without
removing a newer one.
* build(terminal): regenerate the WebView HTML for the toast events
* fix(terminal): keep i18n out of the terminal WebView bundle
The image paste queue imported i18n for its size-limit message, which pulled
every translation into the WebView HTML. The queue now reports a typed
TerminalImageTooLargeError and the pane words it.
* refactor(terminal): route dropped files by host locality and share the drop overlay
A dropped file pastes its own path only when the host is the desktop app's
own daemon (useIsLocalDaemon); anywhere else the host cannot read the
client's disk, so the file uploads through client.uploadFile and the host
path is pasted. File type no longer plays a part: images and other files
take the same route, and clipboard images always upload because they have
no path.
The terminal pane now sits in the shared FileDropZone, so dragging files
over it shows the agent panels' "Drop files here" overlay. The emulator's
own drag listeners, blue tint and 180 ms timeout are gone.
FileDropSink.onFiles is optional so a sink can take raw files without
persisting raster images as attachments.
terminal/image-paste becomes terminal/file-paste; the queue, callbacks,
toasts and i18n keys say "file".
* test(terminal): keep the fake desktop bridge in an e2e support helper
The CI contract test forbids browser specs from installing paseoDesktop
directly; the local-host drop scenario now imports it from support.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(claude): stream and cache session import descriptors
* fix(claude): bound and schedule import reads
Reject JSONL records over 4 MiB explicitly to preserve metadata.
Limit shared reads to four globally and one per listing for fairness.
Deduplicate reads by cacheKey with independent subscriber cancellation.
Set a 45-second provider deadline before the 60-second client timeout.
Clone returned descriptors to isolate overlapping callers.
Validation: 97 focused tests passed; independent review approved.
Detected-By: user
* fix(claude): parallelize resilient session scans
Use up to four workers per listing with a shared four-read limit
and bounded FIFO queuing. Preserve cache-key deduplication,
subscriber cancellation, and candidate order.
Log and skip oversized or unreadable transcripts so healthy
sessions remain visible. Propagate programming and abort errors.
Detected-By: user
* fix(claude): surface session import read failures
Skip expected candidate read failures and return healthy sessions.
Distinguish unreadable metadata from parsed transcripts, and raise
AggregateError when every candidate is unreadable.
Propagate systemic file errors and narrow discovery error suppression.
Detected-By: user
* fix(claude): keep large sessions importable while streaming history
Preserve import metadata for transcripts containing image records above 4 MiB. Stream complete records instead of rejecting otherwise listable sessions, and cover overlapping requests across aliased provider roots under a constrained heap.
Co-authored-by: Josh Lane <joshlane@easypost.com>
* test(app): wait for active agent controls and submitted turns
Observe the mode control before testing Command Center keyboard selection. Observe a running turn before waiting for its completion, so the rewind test cannot mistake the previous idle state and end marker for the new turn.
---------
Co-authored-by: Josh Lane <joshlane@easypost.com>
Co-authored-by: Josh Lane <lane.joshlane@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): list plugin header buttons in the compact workspace menu
The compact header rewrite left plugin header buttons with no home, so
phones and narrow windows had no way to reach them. They are now rows of
the compact workspace menu, with menus and popovers as pages of its sheet,
the way workspace scripts moved there.
The header overflow and the workspace menu share one row/page builder.
Each row and page resolves its own plugin installation's runtime: the old
overflow routed every row through the first entry's host client.
Closes#7601
* test(app): wait for agent control rows before keying the Command Center
Two Command Center agent-control tests typed a query and pressed Enter or
ArrowDown immediately. The agent's mode and model rows register after the
panel opens, so on a slow runner the keys landed while only file search
had matched and the mode never changed. The CI trace for the failure shows
no Mode or Model rows at the moment of the key press. The tests now wait
until both groups are listed, which keeps what they check: the best match
wins, and a stale arrow-key selection is dropped.
* fix(app): open notification agents once in their current workspace
Keep tap ownership across Activity and React remounts, clear native responses, and resolve agent workspaces through existing navigation after startup settles.
Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
* fix(app): hydrate agent targets with the compact sidebar closed
Hold directory demand for the agent route while resolving its workspace; the compact sidebar does not supply demand before it opens.
* fix(app): keep deferred workspace opens in the layout model
Key pending tab opens by workspace so redirects and retained screens cannot lose or consume another workspace's target. Legacy open URLs seed this state at their route boundary.
* docs(app): document notification tap ownership and native replay
* fix(ci): route feature-owned Electron sources to desktop checks
* refactor(app): separate notification API from React integration
* fix(app): consume pending opens before painting the restored tab
A legacy open link showed the previously focused terminal for a frame before switching to the requested terminal. Seed and consume the pending open in layout effects so a ready target switches before paint.
* fix(e2e): focus the visible retained terminal surface
---------
Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
Manual metadata generation is a non-empty agents.metadataGeneration.providers
list, but the resolver always appended the built-in defaults and the current
agent selection after it. When a manual model failed, the prompt was retried
on providers the user never chose. Manual now resolves to exactly the
configured list; Automatic is unchanged.
Commit message and PR text generation swallowed failures without logging, so
a broken manual model was invisible. The git metadata generator now logs the
final failure with each attempt's provider, model and error, and the
structured generation loop receives the session logger for per-attempt
warnings, matching the branch name path.
The probe cycle reused the selected client's last heartbeat RTT even
after that client disconnected. A dead direct connection kept reporting
as available, so the app never switched to a reachable relay until it
was restarted. Mark the selected connection unavailable while its client
is not connected and let the existing failover pick another connection.
Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
* fix(app): give Markdown and HTML file tabs back their Preview and Source views
The built-in Markdown and HTML panels own one toolbar again: file size,
line count, save status, vim mode, cursor, and a Preview | Source control
over a body that shows the rendered preview or the editor from the same
loaded content and editor model. Plain text files keep the toolbar on top.
The host no longer renders a panel switcher. Source is the built-in panel
for text files no other built-in claims, so it is never offered beside
Markdown or HTML in the picker, the Open with menu, or Settings > Files.
A line target opens the file's built-in panel in its Source view.
* refactor(app): name the shared text file panel TextFilePanel
Markdown and HTML own a Source view; they are not Source panels. The
composite in file-panels/source/panel.tsx is now TextFilePanel, and
SourcePanel is TextFilePanel without a Preview. Names only.
Parked pane focus was persisted without its in-memory restoration record, so cold starts exposed hidden Explorer tabs and never requested the agent timeline. Restore an ordinary pane at the layout boundary, including for existing saved layouts.
* fix(server): skip Claude request-journal records when loading history
Claude Code 2.1.304+ journals each API request into the transcript as
api-request-blob records. They carry a message with a role and content,
so the history loader rendered them as assistant messages: restored
timelines gained the session's system context, including SessionStart
hook output and skill and tool listings.
Only user and assistant records are conversation turns once the system
record handling has run, so short-circuit every other type.
Closes#7102
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(claude): keep request context out of restored chats
Claude request-journal records contain message-shaped tool context. Only conversation records should enter the ordinary history mapper, after compaction and background task notifications are handled.
Co-authored-by: Kheva Mann <khevamann@users.noreply.github.com>
Co-authored-by: rogerluan <rogerluan@users.noreply.github.com>
---------
Co-authored-by: Roger Oba <rogerluan.oba@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>
Co-authored-by: Kheva Mann <khevamann@users.noreply.github.com>
Co-authored-by: rogerluan <rogerluan@users.noreply.github.com>
Popovers on Android landed one status bar below their trigger. React Native
0.86 stopped subtracting the status bar from window coordinates under
edge-to-edge, so measureInWindow, touch pageY and a full-screen Modal already
share screen-top coordinates; the old StatusBar.currentHeight compensation in
the menu engine, tooltip, context menu and sidebar long-press now
double-shifted every anchor.
Compact tabs keep their natural width and the rail scrolls as soon as they
overflow, instead of squeezing labels first and then scrolling anyway. They
drop the inline close because the long-press sheet already closes a tab and a
narrow X beside the label is easy to hit. Any scrolling rail keeps natural
widths rather than collapsing every chip to the minimum, and the rail resets
its offset when scrolling ends so tabs scrolled out of view come back.
The Explorer rail paints the sidebar surface, so its chips climb the sidebar
hover and selected tokens. Light themes paint the sidebar with surface2, which
made the surface2 active chip invisible there.
The compact Explorer closes with the same X as the compact agent list, and the
compact + menu omits Diff because compact Changes already shows diffs inline.
The Host field used to render only with two or more hosts, and Add host
only appeared on compact layouts. Single-host users had no sign that
Paseo can drive other machines from this screen. The field now always
renders and its menu always ends with Add host.
* fix(server): close stale agent status after daemon restart
Repair persisted active statuses before serving clients. Let Claude process-tree cleanup run immediately after SDK close rather than waiting on an already closed control channel.
* test(app): expect closed agent status after daemon restart
* feat(git): default review to all branch changes
Keep the selected comparison stable while agents edit and commit so reviewing the complete result does not require switching scopes or interrupt unfinished comments.
* test(git): release Windows checkout handles during cleanup
Allow transient filesystem handles from subscription teardown to close before removing the test repository.
* fix(server): batch agent reports and completion notifications
Admit agent-originated messages immediately so a child can report and finish before one recipient-owned delivery. Keep human prompts direct, retain individual provenance through replay, and complete send receipts only after provider dispatch.
* test(server): wire queued delivery into bridge integration fixture
* test(server): resolve batching CLI fixture independently of cwd
Workspace CI runs from packages/server, while the original regression was verified from the repository root. Resolve the CLI entrypoint relative to this test file in both contexts.
* test(server): run batching CLI fixture with source exports
Server integration CI builds only the client dependency stack. Match the existing CLI subprocess fixtures so the send regression exercises workspace sources without requiring compiled server output.
* test(server): await persisted batch receipts within integration deadline
The provider-start callback precedes asynchronous receipt commits. Use the same explicit integration deadline as dispatch instead of the matcher default of one second on Windows CI.
* feat(hosts): connect to saved hosts through a daemon
* fix(hosts): validate safe targets and verify encrypted relay routes
* test(hosts): include relay coverage in existing CI contracts
* fix(cli): preserve validated addresses in daemon command hints
* fix(hosts): share daemon links across isolated routed sessions
Multiplex logical sessions over the monitored daemon connection and wrap original binary bytes in routing headers. Admit channels under their authenticated parent and current grants; preserve scoped delivery and physical send completion.
* fix(hosts): bound encrypted overload and initial acquisition
Check encrypted wire capacity before the async channel converts a resource limit into a physical connection error. Bound initial routed acquisition with the existing client timeout, then enable reconnect after admission.
* fix(hosts): keep compact row actions clear of host labels
Give compact connected-host rows separate text and action widths so long names and producer labels cannot compete with primary controls.
* refactor(hosts): remove management settings UI from phase one
Keep connected-host management in the CLI and shared SDK while preserving app saved connections and plugin access.
* test(hosts): control physical write backpressure across platforms
Hold real TCP writes until assertions finish instead of assuming a paused receiver exhausts every platform receive buffer. Preserve physical send settlement, byte identity, capacity and sibling checks.
* refactor(hosts): use the ordinary client contract over channel transports
Keep queuing, permission codes and reconnection independent of transport selection. Track submitted request outcomes generically while retaining connection error codes and structured causes.
* test(e2e): keep transient developer toasts from stalling checks
Concurrent locator handlers can wait for a dismiss button another handler removed. Dismiss existing toast DOM at document lifetime, including its open shadow root, without intercepting app actions.
* Create, prompt, and monitor agents across connected hosts (#7507)
* feat(hosts): delegate agents across connected hosts
Resolve MCP host operations through in-process commands for self and the existing routed client transport for other destinations. Keep ordinary parent labels, app sessions and idle finish notifications.
* test(app): normalize subagent fixtures through snapshots
* docs(protocol): clarify same-host parent labels
* fix(hosts): resolve subagents through destination sessions
A child address identifies its destination regardless of the connection used. Reuse the existing host candidate selection for discovered routes and direct connections, and keep navigation and row actions independent of the parent transport.
* fix(hosts): keep routed destinations distinct during discovery
A routed candidate names only the origin daemon. Match it within the named destination profile so two destinations discovered through the same origin retain separate identities and connections, including existing saved routes.
* feat(hosts): prompt and query agents on selected hosts
Extend the existing MCP host operation boundary with prompt, status, activity and list operations. Local calls retain their handlers and notification owner; selected destinations use ordinary client RPCs and the existing idle finish wait.
* test(e2e): preserve toast coverage with CSS hiding
* docs(hosts): translate connected host references
Backport expo/expo#49634 to SDK 54 so retained launchers register
against the current Activity after their previous owner is destroyed.
Cover repeated recreation and replacement-before-destroy ordering with
native regression tests, and document the native rebuild requirement.
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: Ido Yana <idoyana@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* perf(workspaces): stop duplicating setup output in progress snapshots
Keep the required combined log field empty for setup clients, which display per-command output. Preserve concatenation only for legacy agent bootstrap timeline cards whose released renderers still read it directly.
* refactor(workspaces): own setup exclusively at the workspace boundary
Preserve keyed modern agent worktree creation while retiring legacy placement that selected an agent-owned setup lifecycle. Keep output in per-command workspace snapshots and retain empty strings required by existing wire schemas.
* fix(server): avoid duplicate cwd in worktree provisioning request
* test(e2e): keep developer toasts out of concurrent app assertions
Concurrent layout measurements could invoke the same Playwright locator handler before its first dismissal finished, leaving checks waiting for a removed button. Hide only the Expo toast host through test styling while retaining console error capture.
* feat(app): record Android stalls in opt-in diagnostic builds
* refactor(app): own watchdog sinks and bounded log storage
* fix(app): preserve cumulative browser profiling history
On every page load the layout mode publisher sets the workspace layout store
before its stored state has been read. Zustand persist wrote the unhydrated
defaults, and hydration merges the stored layout into memory without writing
it back, so storage held no tabs until the next layout change. Leaving the
page inside that window lost every workspace's tabs. Validated persist storage
now drops writes that arrive while its read is in flight.
* feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
* feat(website): replace the native language select with an EN/ZH menu
The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.
Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.
* docs(website): translate main's new docs and 0.12.0 release notes
Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.
* fix(server): leave plugin listing translations to the registry
The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
* docs(plugins): describe the registry's final overview translation discovery
* style(server): format manifest test
* feat(plugins): translate plugin UI in each app language
Keep translation resources and subscriptions inside each installation. Resolve host labels against the presentation locale so live language changes preserve mounted drafts and RPC state. Existing descriptor types retain string defaults for source compatibility.
* test(plugins): dismiss developer chrome in second translation client
* test(app): measure sidebar rows one at a time
After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.
* test(app): return sidebar row measuring to main
#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts 41ac23016.
* test(app): wait for the compact shell before opening compact Changes
The shell survives the breakpoint, so right after the resize the wide
Explorer could still be visible: the test skipped the Explorer toggle and
the tab then disappeared, or toggled at the wrong time. Wait for the
compact-shell marker, as the composer helpers do since #7424.
* docs(website): translate main's plugin file panel docs
Stacked PRs and PRs into next got no CI because the pull_request trigger
was limited to main. Drop the base filter from CI and desktop packaging,
and lock it with a workflow contract test.
* feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
* feat(website): replace the native language select with an EN/ZH menu
The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.
Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.
* docs(website): translate main's new docs and 0.12.0 release notes
Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.
* fix(server): leave plugin listing translations to the registry
The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
* docs(plugins): describe the registry's final overview translation discovery
* style(server): format manifest test
* test(app): measure sidebar rows one at a time
After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.
* test(app): return sidebar row measuring to main
#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts 41ac23016.
* test(app): wait for the compact shell before opening compact Changes
The shell survives the breakpoint, so right after the resize the wide
Explorer could still be visible: the test skipped the Explorer toggle and
the tab then disappeared, or toggled at the wrong time. Wait for the
compact-shell marker, as the composer helpers do since #7424.
* docs(website): translate main's plugin file panel docs
* test(app): edit the code font size after the font family remount
A committed appearance change remounts the settings screen through
AppearanceStyleBoundary, and the new form starts from the stored size.
Filling the size before that remount lost the edit in CI.
* docs(website): translate main's client/ui import docs
Follows #7474: one /client/ui entry, StreamedText instead of useRevealedText,
SettingsGroup dropped from the settings table, and the new
'UI imports in v0.13' migration section.
* test(app): wait for the previous layout's file panel picker to leave
After a resize the old layout's picker animates out while the new one is
already mounted, so the Use button matched two elements and the strict
locator failed. Wait for the shell's layout and a single picker first.
Preview links used the workspace root even when the document was nested. Carry a separate relative-path base through the file-link resolver so clicks and hover paths agree while chat keeps workspace-relative resolution.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(files): report document MIME types for binary files
File reads now carry the document's MIME type for binary files, so a file panel can
hand the bytes to a viewer that needs the real type (application/pdf).
* feat(plugins): file panels
Plugins contribute file panels with addFilePanel. A file tab hosts the panels that claim the
file: the built-in Markdown, HTML and image panels, plugin panels, and Source, Paseo's editor,
which claims every text file and is always the last candidate. The panel is the tab body;
the bar shows the document's status and a switcher between the file's panels (side by side
up to three, two on a phone, a menu beyond that).
Paseo asks which panel opens a file only when a plugin panel is among several candidates,
once per open, and remembers the answer per extension or exact filename when told to. Open
with in the tab menu and the switcher change the tab's panel; Always use remembers it.
Opening a file at a line switches the tab to Source.
Panels read and write the host's document, so dirty state, autosave and conflicts stay
Paseo's. DocumentFrame shows bytes documents such as PDFs in the browser's viewer.
Replaces the file tab's Preview/Source mode toggle.
* feat(file-panels): choose and forget file panels in Settings > Files
Settings > Files lists each extension or filename where a plugin panel competes with
another candidate, or that has a remembered panel. Each row chooses the panel that opens
it, or Ask every time; Ask again forgets the choice. Source appears as a candidate for
text files. Panels no connected host offers can be forgotten but not chosen.
* docs(plugins): file panel examples and author reference
Adds the pdf-panel and markdown-panel examples, the File panels section of the plugin
reference, and an e2e spec that installs both examples, opens a PDF in the browser
viewer, and edits Markdown through Paseo's document across the switcher, Source,
autosave and conflicts.
* feat(file-panels): panels read and write through a file handle
The file tab stats the file and mounts its panel without reading it. Every
panel, built-in or plugin, gets a PluginFile handle: readText, readBytes, url,
version-checked writeText/writeBytes (PluginFileConflictError unless force),
and watch. useFileText, useFileBytes and useFileUrl read through it, and
Loading joins @getpaseo/plugin/client/ui. Panels own their loading state.
The host document pipeline, the explorer preview cache and the preview
lifecycle are deleted. The daemon classifies a file version from a bounded
sniff and writes base64 content as bytes behind fileByteWrites.
A deleted file keeps its panel mounted; a read confirms the deletion first,
so a remove-and-rewrite is not reported as one. Native Source decodes with
builtins and highlights in idle-time slices, the lines around the opening
line first, so a large file no longer blocks the JS thread.
* fix(file-panels): Settings offers Source where the picker does, and review follow-ups
- Settings > Files offers Source on every key except those a built-in panel of
another kind (Image) claims, so a plugin that alone claims a text extension
shows the same choices as the picker.
- Delete the unused isSourceFilePanel.
- The Markdown example warns with the file name when its unmount write fails,
rather than dropping the edit silently.
- Document that PluginFile stat fields are read at render time and that watch
or the read hooks are how a panel learns they changed.
* refactor(plugin): consolidate the client UI entrypoint
* fix(cli): resolve agent archive through the daemon, not a listing page
`paseo agent archive` resolved its argument against a single
`fetch_agents` response. That listing is capped at 200 entries per page
and the command never pages, so an agent past the cap could not be
archived: the CLI reported AGENT_NOT_FOUND before any request reached the
daemon, while `paseo agent ls --global` and `paseo agent inspect` still
returned it.
Resolve the argument with `fetch_agent` instead, the same authoritative
daemon lookup `agent inspect`, `agent stop` and `agent delete` use. The
already-archived and running checks read the returned record unchanged.
Closes#4909
* fix(cli): only report a missing agent as AGENT_NOT_FOUND in archive
The lookup rejection was mapped to AGENT_NOT_FOUND whatever it said, so
an ambiguous identifier, a timeout or a daemon handler failure all read
as a missing agent. The daemon reports an unknown identifier only as an
error string, so recognize that one rejection by its text and rethrow
anything else unchanged.
---------
Co-authored-by: L4XB <lukas.buck@e-mail.de>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: HTeo <hteo1337@gmail.com>
* fix(files): report document MIME types for binary files
File reads now carry the document's MIME type for binary files, so a file panel can
hand the bytes to a viewer that needs the real type (application/pdf).
* feat(plugins): file panels
Plugins contribute file panels with addFilePanel. A file tab hosts the panels that claim the
file: the built-in Markdown, HTML and image panels, plugin panels, and Source, Paseo's editor,
which claims every text file and is always the last candidate. The panel is the tab body;
the bar shows the document's status and a switcher between the file's panels (side by side
up to three, two on a phone, a menu beyond that).
Paseo asks which panel opens a file only when a plugin panel is among several candidates,
once per open, and remembers the answer per extension or exact filename when told to. Open
with in the tab menu and the switcher change the tab's panel; Always use remembers it.
Opening a file at a line switches the tab to Source.
Panels read and write the host's document, so dirty state, autosave and conflicts stay
Paseo's. DocumentFrame shows bytes documents such as PDFs in the browser's viewer.
Replaces the file tab's Preview/Source mode toggle.
* feat(file-panels): choose and forget file panels in Settings > Files
Settings > Files lists each extension or filename where a plugin panel competes with
another candidate, or that has a remembered panel. Each row chooses the panel that opens
it, or Ask every time; Ask again forgets the choice. Source appears as a candidate for
text files. Panels no connected host offers can be forgotten but not chosen.
* docs(plugins): file panel examples and author reference
Adds the pdf-panel and markdown-panel examples, the File panels section of the plugin
reference, and an e2e spec that installs both examples, opens a PDF in the browser
viewer, and edits Markdown through Paseo's document across the switcher, Source,
autosave and conflicts.
* feat(file-panels): panels read and write through a file handle
The file tab stats the file and mounts its panel without reading it. Every
panel, built-in or plugin, gets a PluginFile handle: readText, readBytes, url,
version-checked writeText/writeBytes (PluginFileConflictError unless force),
and watch. useFileText, useFileBytes and useFileUrl read through it, and
Loading joins @getpaseo/plugin/client/ui. Panels own their loading state.
The host document pipeline, the explorer preview cache and the preview
lifecycle are deleted. The daemon classifies a file version from a bounded
sniff and writes base64 content as bytes behind fileByteWrites.
A deleted file keeps its panel mounted; a read confirms the deletion first,
so a remove-and-rewrite is not reported as one. Native Source decodes with
builtins and highlights in idle-time slices, the lines around the opening
line first, so a large file no longer blocks the JS thread.
* fix(file-panels): Settings offers Source where the picker does, and review follow-ups
- Settings > Files offers Source on every key except those a built-in panel of
another kind (Image) claims, so a plugin that alone claims a text extension
shows the same choices as the picker.
- Delete the unused isSourceFilePanel.
- The Markdown example warns with the file name when its unmount write fails,
rather than dropping the edit silently.
- Document that PluginFile stat fields are read at render time and that watch
or the read hooks are how a panel learns they changed.
* fix(plugins): file events are objects, deletion is a typed error, DocumentFrame owns its fallback
- watch listeners receive { type: "changed" } | { type: "deleted" }, so later
versions can add fields without breaking them.
- PluginFileDeletedError joins PluginFileConflictError. The read hooks report a
deleted file with it, and a write over a deleted file rejects with it; Source
shows that as the deleted notice. PluginFileConflictError now means only
"changed on disk".
- DocumentFrame on Android shows Paseo's "<TYPE> files can't be shown on this
device" message for documents its WebView cannot display, so the PDF example
is useFileUrl + Loading + DocumentFrame with no platform branch.
* test(file-panels): Settings offers Source for a remembered panel no host has
* test(e2e): hide the developer error toast instead of clicking it away
The locator handler that dismissed #error-toast could match a toast that closed before its
click, then wait on it until the test timed out (agent-timeline-resume.spec.ts:45 failed every
attempt on this branch). Hiding the toast with CSS keeps it off app actions with no race.
* test(e2e): desktop browser-tabs hides developer toasts on its attached page
* fix(android): restore development builds after Expo upgrade
Port the native touch-cancellation patch to the gesture-handler API while retaining drawing order and surviving native gestures. Match the bundled Skia font scanner and Unicode paragraph construction APIs.
* test(e2e): hide the developer error toast instead of clicking it away
The locator handler that dismissed #error-toast could match a toast that closed before its
click; dispatchEvent then waited for the gone button until the test timed out. Hiding the toast
with CSS keeps it off app actions with no race.
* test(e2e): desktop browser-tabs hides developer toasts on its attached page
* test(cli): keep the Hub deploy fixture alive on malformed requests
An empty request body crashed the HTTP fixture outside its assertions. Return HTTP 400 for invalid JSON and exercise that request before the full validation and installation assertions.
* test(e2e): hide the developer error toast instead of clicking it away
The locator handler that dismissed #error-toast could match a toast that closed before its
click; dispatchEvent then waited for the gone button until the test timed out. Hiding the toast
with CSS keeps it off app actions with no race.
* test(e2e): desktop browser-tabs hides developer toasts on its attached page
* fix(server): report archived agents correctly over MCP
An archived agent is resumed back into memory whenever something reads its history:
agent-loading passes `{ purpose: "history" }` to resumeAgentFromPersistence exactly when
`record.archivedAt` is set. `list_agents` then sees it as a live ManagedAgent, which
carries no archive timestamp, so AgentDirectory's archive predicate passes it and the
stored record's timestamp never reaches the result:
- `list_agents` returns the agent with `archivedAt: null`, because
serializeSnapshotWithMetadata merges only the title from storage. A caller reconciling
ownership from that list concludes an archived agent is still running.
- a default call (`includeArchived: false`) returns it anyway.
Storage stays the single owner of the timestamp. The MCP boundary merges the stored
record the same way the WebSocket path does in Session.enrichAgentPayload, reusing the
storage read it already makes for the title, and `archivedAt` becomes a projection option
beside `title`. `list_agents` filters archives after projection, where the timestamp is
known.
Copying the timestamp onto the managed agent was the other option and is worse:
unarchiveSnapshot clears only the stored record, so a managed copy would report an
unarchived agent as archived until it was reloaded.
The wire change is additive: `archivedAt` is already nullable and optional in the
protocol, and already populated on the stored path.
Fixes#4753
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(server): keep archived agents out of discovery after history reads
Apply archive filtering after stored metadata is projected in the shared directory. Exercise archive, history hydration, MCP discovery, WebSocket discovery and unarchive through a real daemon instead of mocked manager snapshots.
Co-authored-by: Thomas Van <minhthong@gmail.com>
---------
Co-authored-by: Thomas Van <minhthong@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>
* fix(server): notify the caller when a detached child finishes
`create_agent` with a detached relationship and `notifyOnFinish` arms a finish watcher
with `requireParentOwnership: true`. At fire time that guard checks that the child is
still labelled as the caller's, but resolveCreateAgentIntent strips the parent label from
a detached child on purpose, so the check can never pass and the caller never hears the
child it created finish.
The create path now requires parent ownership only for a child created with a parent.
The guard itself is unchanged: a parented child whose label later points elsewhere stays
silent, as agent-prompt.test.ts ("detaching a child ends its parent-owned finish
notification") requires. Only the create call knows the caller asked for a detached
child, which is why the fix lives there.
Tests drive the real finish watcher from createAgentCommand, with the finish published
as the manager publishes it: a detached child notifies its caller once, and a parented
child moved to another parent does not. Requiring ownership always fails the first;
never requiring it fails the second.
Fixes#6908
* test(server): verify detached completion through agent-scoped MCP
Exercise creation, stored parent labels, provider completion, and caller delivery
with the real daemon and injected provider clients. Cover detached and parented
creation and suppression after reparenting without replacing manager methods.
Co-authored-by: Thomas Van <minhthong@gmail.com>
---------
Co-authored-by: Thomas Van <minhthong@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Stop materializing and copying the entire conversation through lifecycle hooks. Keep the deprecated timeline field empty and direct plugins to bounded timeline reads.
* Upgrade to Expo 57 and unify Explorer across layouts
* feat(app): retain Explorer across layout and appearance changes
Keep one workspace Explorer host and destination-aware navigation so folding, theme updates and compact sheets preserve the current panel. Scope pull request discovery and timeline demand to active surfaces.
* fix(app): keep Explorer resize handles stretched through the dock
The shared frame moved the divider into an absolute column, leaving its horizontal handle without height. Restore the row so pointer drags reach it, preserving the existing resize and performance contract.
* fix(app): keep Explorer drag widths out of the shared stylesheet
Carry the existing inline sizing policy into the shared frame so pointer previews do not accumulate CSS rules.
* fix(test): align isolated test runtimes with Expo 57
Hoisted React consumers need the matching DOM renderer at the repository root. Automated Metro processes must not install native DevTools into disposable homes, and development toast overlays must not cover compact actions.
Keep Explorer journeys on the shared host and synchronize setup fixtures on daemon output rather than a one-second file-marker window. A delayed real child start reproduces the readiness failure on every platform.
* fix(test): await retained shell layout after composer resize
The gesture host now stays mounted across breakpoints, so its presence no longer identifies compact layout. Report the current layout on the retained host and synchronize composer journeys with that state.
* fix(app): preserve browser interactions in retained workspace hosts
Initially disabled gesture hosts still registered pointer and wheel listeners, while hover cards used window positions inside a workspace-local sheet portal. Respect initial gesture enablement and position cards relative to their measured floating host.
Keep developer-toast dismissal independent of pointer input and replacement toast visibility so modal, hover, and focus checks retain their input state.
* fix(test): share developer toast handling with Electron browser runs
The standalone Electron browser runner bypassed the browser fixture, allowing developer errors to cover Settings. Use the same non-pointer dismissal in both runners while retaining their console logs.
Offer Auto the same explicit resume action as Bypass and apply the saved mode before releasing plan approval. Ordinary Implement keeps selecting Accept File Edits.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Recognize Android certificate-path validation failures before the generic not-found classification, so Direct connection no longer tells users to debug DNS for an untrusted certificate. Keep the existing TLS copy and technical details.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>