Archiving removes an agent from the active directory without deleting the conversation. Preserve its local composer draft there too, and clear it only on explicit entity removal.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
At widths beyond the chat's max width, the latest turn's footer sat at the
chat's left padding instead of under the message column, while running and
after it ended. Earlier footers were aligned.
#8358 wrapped the web viewport's live auxiliary (the latest turn footer and
pending permissions) in a measuring div with no style. That div is a block
box, so the footer row's alignSelf: center no longer applied. Give it the
same flex column style as the stream rows.
Fixes#9081
The completed turn footer wrapped "Worked for 26s" onto two lines at full
chat width in builds before #9007, which sized the duration to whichever
label had more characters. A label with fewer characters can paint wider,
so the duration wrapped inside the narrower timestamp's width. #9007 gives
both labels the full label column; this locks that behaviour with a browser
test using real layout.
The browser test project could not load anything that reaches the toast
context: react-native-web has no ToastAndroid, and native ESM rejects the
named import. A react-native test shim re-exports react-native-web and adds
it. The lucide test stub gains the icons the message module imports.
* fix(app): keep one hover card open while scanning sidebar workspaces
Sweeping the pointer down the workspace list showed cards about three
frames late, stacked two or three at once, and left some open after the
pointer moved on.
The card was positioned through an async chain (measureInWindow, then
onLayout, then a position effect, then the portal's passive mount), so it
painted at -9999 first. While unplaced, its safe-zone bridge covered
everything above the trigger, so upward pointer moves kept cancelling its
close timer. The previous card also stayed for the close grace period
plus its fade while the next one opened.
Now each overlay layer holds one open card, and opening another trigger
closes it immediately. The card reads its trigger and overlay rects in a
layout effect inside the portal, so the first painted frame is already
placed. The grace timer still covers jitter and the gap onto the card.
* test(app): name the hover scan workspaces so the resting row's card is identified
The runtime already identifies connections through another host as routed, but both diagnostic fields fell through to unknown. Preserve that distinction in the report.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(protocol): define streamed diff revisions and resolve tag bases
* feat(server): stream shared diff revisions with paced file bodies
* feat(client): observe streamed diffs with atomic revision updates
* feat(app): show the Changes tree while diff bodies stream
* fix(server): retain the legacy structured diff payload budget
* fix(diff): preserve revision ordering and readable files
* fix(diff): select renamed patches and read dangling symlinks
* fix(diff): preserve nested checkouts and ambiguous patch paths
Only regular files and symlinks are readable untracked entries. Prefer patch metadata to ambiguous unquoted headers so paths containing a prefix delimiter keep their hunks.
* fix(diff): preserve BOM text and complete file replacements
Decode each string without consuming its leading BOM. File-type replacements emit multiple patch sections for one listed path, so retain all matching hunks. Assert the optional rename metadata in the existing commit-file journey.
* fix(diff): highlight symlinks from their stored target text
Working tree source context must match the symlink blob emitted by Git rather than the contents reached by following the link. Otherwise highlight runs do not cover the patch line and encoding fails the revision.
* fix(diff): tolerate disappearing highlight sources
Source context is optional after Git has produced the patch. Missing worktree link metadata must preserve the existing hunk-based highlighting fallback instead of failing the revision.
* fix(diff): batch cold reads and refresh logical comparison inputs
Acquire bounded pages of tracked patches lazily after Begin while retaining per-file highlighting and awaited frame delivery. Keep legacy preferred-base selection live across revisions and include submodule Git state in worktree cache identity.
* fix(diff): distinguish gitlink directories from replacements
A raw old gitlink mode also occurs when a submodule becomes a regular file. Query nested Git state only for a current directory, preserving normal file identity for replacements.
* fix(diff): invalidate timestamp-preserving file replacements
Size and modification time do not identify live file contents after a preserving copy. Include filesystem change time and file identity for tracked and untracked reuse, and verify live revisions agree with fresh reads.
* fix(diff): align highlight tokens with normalized patch lines
Git can normalize CRLF worktrees while highlighting reads their original line endings. Match tokens to the emitted patch text and trim only a normalization CR before encoding runs; mismatched source tokens are not attached.
* fix(diff): refresh equivalent worktree paths across platforms
Explicit refreshes must match filesystem-equivalent roots, including Windows path aliases. Use the existing realpath-aware matcher, budget real Git completion waits consistently, and assert optional status metadata in the filesystem commit-file journey.
* fix(diff): validate Git inputs and resolve refresh roots
Reuse now accounts for effective attributes, configuration, and canonical worktree blobs as well as filesystem state. Explicit refresh resolves repository identity like subscription creation, and callers await that resolution before acknowledging the command.
* fix(diff): keep non-Git workspace refresh successful
Diff invalidation has no target repository for an ordinary folder. Treat that expected absence as a no-op while preserving other root-resolution errors.
* fix(diff): observe scoped Git interpretation settings
Classify each metadata root with its worktree and common scopes, retaining both when the directories coincide. Shared attributes affect all checkouts, while config.worktree belongs to the owning checkout. Real subscriptions verify automatic binary and hunk-context refreshes in main and linked worktrees.
* fix(diff): observe interpretation inputs for pinned comparisons
Immutable commit objects still depend on mutable Git configuration and attributes. Declare those metadata inputs independently of ref, index, and worktree mutability, while keeping unrelated endpoint events out of pinned comparisons.
* fix(diff): observe interpretation independently of comparison endpoints
Commit comparisons still read worktree attributes. Watch those inputs independently, checking effective Git interpretation before invalidating fixed contents. Retain paths hidden by whitespace filtering so attribute changes can expose them. Forward binary frames through the branch-loading test interceptor.
* fix(client): close abandoned browser connections immediately
When the client gave up on a connection (liveness timeout, connect
timeout, reconnect, subscription failure) it closed the WebSocket with
1001. Browsers, Electron and Node's global WebSocket accept only 1000 or
3000-4999 and throw InvalidAccessError otherwise; the throw was
swallowed and the reference dropped, so the physical socket stayed open
and the daemon kept streaming into it until its 45 s lease expired.
The WebSocket adapter is the only code that calls WebSocket.close, so it
now sends 1000 for codes the API rejects and trims reasons to the
123-byte limit. Callers keep their reasons, and Node's `ws` sends the
same frame as a browser, so Node tests now catch this class of bug.
* test(client): drop timing-dependent hello count from abandoned-close test
Handshake retries after close are owned and tested by the relay's handshake-lifetime test; this test only checks that the abandoned socket closes.
A missing runtime timestamp does not establish connection history. Report it as unknown while preserving supplied timestamps.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Read the submitted form value while hydration state is still catching up with text entered before its input listener. Cover the missed input event without relying on startup timing.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The server-rendered trigger accepted clicks before its handler existed. Tie its enabled state to hydration and cover delayed scripts so an early click cannot silently disappear.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): preserve host error details in desktop logs
Bind host context through the existing client logger and emit structured text so desktop console capture retains the error record, including for adopted probe clients.
* fix(app): retain discovered host identity and nested error causes
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Consume host connection statuses in History instead of a version-only dependency that the React Compiler removes. Keep saved sessions reachable when the host connects after the screen mounts.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Cancel handshake work at the channel close transition instead of waiting for a socket acknowledgment from an unreachable peer.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The upgrade journey still expected protocol 5 after the worker moved to 6, stopping every main CLI run before adoption coverage. Keep obsolete versions 0 through 4 and exercise newer-worker rejection with version 7.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(desktop): fill controlled form fields before submission
Write text controls through native DOM setters so page-level value tracking sees the input event as a change. Cover state updates, submission, replacement and clearing through the real browser tool journey.
* refactor(desktop): select native field setters with explicit branches
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Reopening a chat lists its commands, which resumes the agent and starts
a Claude process with no prompt. Claude only sends init with a turn, so
the runtime's background-work state stayed unknown and idle cleanup
vetoed it on every sweep: the process stayed resident until the daemon
restarted.
A process that has not been sent a prompt and has not reported init has
run no turn and owns no background work, so it can stop. Older CLIs
that ran a turn without a roster are still kept.
Fixes#9017
Co-authored-by: Brandon Elliott <923787+Brandon168@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Pointer submission blurs the editor before sending and clears retained focus intent. Restore focus through the existing default send action for mouse-driven layouts, while keeping touch keyboard dismissal intact.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): fill short collapsed chat histories
Collapsed whole turns can leave the initial viewport short even when older history exists. Continue paging through measured pages before revealing a fresh chat, while preserving visible history and the reading anchor.
* fix(app): settle padded and stalled chat history
Measure the native history edge using the same content area as viewport filling, and bound child history requests so retry can start after a stalled fetch.
* fix(app): adapt viewport filling to the current timeline
* fix(app): resume history filling after switching tabs
* fix(app): make a folded turn's summary line its own chat row
The summary line of a collapsed turn was drawn inside its prompt's row, so the
row gap landed below the summary and the prompt touched it. Turn collapse now
emits a `turn_summary` row between the host and the reply, and one spacing
rule covers every prompt: 16 above whatever answers it (reply, summary row or
tool row). The summary row spaces like the work rows it stands for, 4 above the
reply and flush under a tool host.
This removes the per-row summary lookup, the host revision tracking and the
fragments in the view. A fold reuses a turn's previous summary row object while
its work and open state are unchanged, so mounted rows do not re-render.
* fix(app): keep the turn summary row at its host's timeline position
The chat outline resolves the active prompt from the timeline position of
the row under the reading line. The summary row had none, so the outline lost
its mark while the row sat there. The row now carries its host's position and
is replaced when that position lands.
* fix(app): keep the reader on a folding turn's host, not its summary row
When an older page completes the oldest loaded turn and it folds, the reading
position moves from the hidden rows to the turn's host row, as on main, so
the prompt and its summary row both land in view. The pagination helper now
finds the summary line in the row under the prompt.
* fix(app): keep turn footer timing within the chat width
Let visible timing text determine its height within the available chat width instead of sizing an absolute overlay by character count. Keep actions anchored when duration and timestamp labels swap.
* fix(app): keep footer timestamp hover target stable
* fix(app): reserve both wrapped timing label heights
* Serialize Codex app-server startup by state directory
* Keep Codex session constructor unchanged
* Type Codex startup failures and inject process launch port
* fix(codex): retain startup diagnostics emitted after exit
Classify failed initialization after stdio closes so the bounded SQLite retry sees the complete stderr. Keep running-session exit handling immediate. The regression delivers the SQLite diagnostic between exit and close.
* test(codex): inject history test process through the public dependency
The history fixture still replaced the old private spawn method, so provider launch preparation tried to find a real CLI in CI. Inject the same process interface as other provider tests and use an invalid command as a guard against accidental real launches.
---------
Co-authored-by: Michael Wu <michaelmwu@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <1246619+colonelpanic8@users.noreply.github.com>
* feat(plugin): expose stable sidebar entity data across hosts
Collections return interned references and preserve their arrays when membership and order stay unchanged. Entity selectors share one hook with identity defaults for full snapshots and shallow defaults for selections. The app caches host projections and shares underlying store and host subscriptions. Browser render counts exercise the real session store and plugin provider without memoized rows. Terminal hooks consume the collection source port; daemon-wide terminal discovery is the separate terminal phase.
* feat(plugin): let users select a sidebar view
An exclusive view replaces the sidebar content while the host retains navigation, callouts, and footer. The device remembers the selection, unavailable views fall back to the default, and throwing views provide recovery. Device-scoped plugin settings persist locally with schema validation and revision conflicts. Web E2E and Android tablet QA cover selection, preserved chrome, disabled-view fallback, and error recovery.
* feat(plugin): add sidebar view examples and ref actions
Keep sidebar layout and gestures inside the plugin while routing entity handles to their owning hosts. Examples cover empty projects, pinned workspaces, task sections, nested agents and device-local label folders.
* feat(plugin): observe terminals across connected hosts
Share host-wide terminal observations across plugin surfaces and gate them on terminalCollection. Keep directory observations compatible, and carry creation time in worker v6 state so v5 terminals can drain with an unknown timestamp.
* fix(app): preserve linked resources when pasting prompts
Rich clipboard links carried their destination only in HTML, while the composer let textarea default paste insert the plain label. Convert linked fragments to Markdown at ingestion and replace the selected draft range through the existing editor command.
* fix(app): keep pasted links undoable while drafting offline
Use the browser edit command for converted paste so selection replacement participates in Undo and normal input publication. Keep complete Markdown clipboard text when it already carries the link destinations, and limit connection gating to image uploads.
* fix(app): preserve copied Markdown with normalized links
Copy renders normalized URLs in HTML while retaining original Markdown in plain text. Compare parsed destinations using the same Markdown parser so pasting complete responses keeps tables and formatting intact.
* fix(app): retain external linked tables when pasting prompts
Recovering missing link destinations must retain table rows and columns. Use the existing table converter and keep cell delimiters and line breaks inside their cells.
* test(app): name rich clipboard paste arguments
* fix(app): preserve pasted links around rowless tables
The table converter requires a first row. Convert rowless table content separately so empty markup cannot abort recovery of surrounding destinations.
* test(app): wait for workspace restore before undo teardown
The directory appears before git finishes recreating the worktree. Wait for the host to publish its active workspace before cleanup removes the source repository.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(desktop): run browser_evaluate on pages whose CSP omits 'unsafe-eval'
* test(desktop): browser_evaluate on pages that allow and forbid eval
* test(desktop): e2e opens a page under a CSP without 'unsafe-eval' and evaluates on it
* fix(desktop): preserve page globals in browser evaluation
Define the supplied function outside every wrapper binding so strict-policy evaluation keeps the original page scope.
Co-authored-by: Abdulhakeem Mustapha <abdulhakeemmustapha@gmail.com>
---------
Co-authored-by: Abdulhakeem Mustapha <abdulhakeemmustapha@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): keep the model selected by the provider
A live model setter can retain or resolve a different model than the requested ID. Read its effective selection before persisting manager state so an ignored selection cannot overwrite the actual model. Preserve explicit preference resets.
* fix(server): preserve completed model selections
Return the effective selection from the provider mutation so a separate status request cannot reject an already completed switch or leave configured and runtime models inconsistent.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Snapshot refresh completion does not mean both workspaces have joined the repository fetch target. Wait for observation setup before releasing the first fetch, and retain delayed checkout resolution to exercise that ordering.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(desktop): browser_logs reports the HTTP status of each request
* test(desktop): browser_logs reports the status the browser recorded
* test(desktop): e2e checks browser_logs reports a 404 and a 200
* docs(browser): browser_logs also reports the HTTP status
* fix(server): preserve the selected model when importing sessions
Import preparation resolved a new-session catalog default and sent it as an explicit resume override. Keep import selections with the resumed provider and persist its reported model for subsequent reloads.
* fix(codex): allow the first reply after importing a session
Resolve collaboration settings again when default model and effort are applied so imported sessions send a complete first-turn request.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* M1: terminal input-sink bridge, upload button in tab-bar slot and key bar, picked-file routing, space-separated pasted paths
* M2: terminal dictation: mic in tab-bar slot and key bar, recording pill and overlay, per-terminal recording lifetime incl. pending mic acquisition, Cmd+D on macOS by keyboard focus, i18n
* M2: review fixes: retry attempt guard, cancellable native pending start, measured terminal slot width, compact-web Cmd+D for terminal dictation
* fix(terminal): separate uploaded file paths when pasting
Keep the remote file-drop regression and its test coverage from the contributor change, without introducing new terminal controls. Each uploaded path needs a trailing separator because adjacent quoted paths are one shell word.
Co-authored-by: Sebastian Arena <sebastian@walkofcode.io>
---------
Co-authored-by: Sebastian Arena <sebastian@walkofcode.io>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(server): give agents their workspace id as PASEO_WORKSPACE_ID
Agents got PASEO_AGENT_ID and PASEO_AGENT_CWD but not their workspace id, so an
agent couldn't rename or archive its own workspace, or name it to create_agent,
when several workspaces share its cwd. The launch env now sets
PASEO_WORKSPACE_ID whenever the agent has a workspace, including on resume after
a daemon restart, where the id now comes from the stored record. OpenCode treats
it as a per-session key, so it doesn't force a dedicated server.
Refs #5098
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(server): keep the workspace when resuming an agent by handle
resume_agent_request matched the stored agent but dropped its workspace id, so
the resumed agent had no PASEO_WORKSPACE_ID and no workspace. Pass the matched
record's workspace id into the resume.
resumeAgentFromPersistence now resolves the workspace once and gives it to both
the launch env and the registered agent, so a caller that omits it no longer
erases the stored value on the next snapshot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(server): exercise current daemon creation and permission contracts
The workspace resume regression shares this suite with creation checks that still waited for legacy status frames and permission listeners without an owned subscription. Verify the returned error snapshot and acquire event demand so the complete suite can validate workspace identity changes.
Co-authored-by: Darrell <DT@Terastar.biz>
---------
Co-authored-by: Darrell <DT@Terastar.biz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): keep Mermaid controls usable in inline previews
* test(app): verify Mermaid controls through user journeys
Keep source-toggle mechanics in diagram helpers and compare preview geometry with its screen and controls instead of requiring exact pixel heights.
Co-authored-by: luke <luopeihuan@gmail.com>
---------
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Land a turn's reply, completed footer, and tool summary in one frame
The stream handler closed turn liveness as soon as turn_completed arrived,
while the final reply waited in the frame-coalesced reducer queue. For one
commit the turn was finished with no reply: the footer vanished and the
timeline jumped twice, and tool turns folded into their summary later.
The viewed timeline owner now takes stream events whole: a turn boundary
commits the queued content before liveness changes. Running and completed
footers share one fixed content height so the phase switch moves nothing.
* Document the turn-boundary commit and stop racing the re-pin in the turn-open test
The turn-open test scrolled the received message into view with a
programmatic scroll, which leaves the chat following its end. Pressing
the turn summary swaps its icon, and that DOM change re-pins the chat to
the end between pointerdown and pointerup. The summary had moved 14 px,
which left the pointer 2 px inside the button; with the completed footer
now 2 px taller, it moved 16 px and the click landed on the reply below.
The message's fade and Show more are already on screen at the end, so
the test no longer scrolls.
Document-wide layout shifts could fail a stable file-search panel. Count only shifts attributed to the panel or its descendants, and exercise both unrelated and in-panel movement in the existing browser journey.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): show loading and errors in the composer command menu
Rows are single lines with a kind icon, the matched part of the name, the
argument hint, the description and a source tag. The detail card is gone.
The menu used to stay hidden while commands loaded and then show
"Error: ..." over a list whose options Enter could still pick, so a failed
load plus Enter archived the agent through a hidden /exit. Loading and
error are now status rows with no options; the error row names the reason
and offers Retry. Loading appears after 300ms so a warm cache paints the
results first, as before.
An empty query used to select /exit. Provider commands now come first, so
the bottom row the input selects is a provider command.
useAutocomplete derives the selection during render. When results replace
the loading row, the selection is valid in the same render, so the popover
stays mounted at its anchor instead of remounting.
Claude Code's `__`-prefixed commands (/__remote-workflow) only run in its
own server-launched sessions, so the Claude provider no longer lists them.
* fix(app): never default the command menu to a Paseo action
An arrow-key choice was tied to the query it was made for, so returning to
that query brought it back: arrow to /exit on `/`, type `com`, delete it, and
Enter archived the agent. A choice now clears whenever the query changes.
An empty query still selected /exit when the agent had no provider or plugin
commands, because Paseo's actions were the only entries. With nothing else to
list, an empty query now lists nothing and waits for a search; typing `/ex`
still finds /exit.
* fix(app): sort empty-query commands without toSorted
Hermes has no Array.prototype.toSorted, so typing `/` threw "undefined is
not a function" on iOS and Android and the app showed its error screen.
Browsers and Node have it, which is why web and unit tests passed.
* test(app): cover the stale command choice in Playwright and wait out the loading row on devices
The renderHook test mounted React under JSDOM, which docs/testing.md forbids.
The Playwright test drives the same sequence in a real browser and fails
without the reset in useAutocomplete: Enter chose the stale /exit.
The composer-keyboard harnesses treated a visible popover as a ready list.
The popover now shows a loading row first, and a cold Claude command list
takes several seconds, so both harnesses wait for the rows.
* fix(app): size the command menu loading spinner to the row icons
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(app): distinguish branch loading from empty results
Preserve branch query pending and error states through the switcher so first open does not report missing branches before the response. Show request failures with a retry action.
* fix(app): keep branch retry progress and keyboard focus
Verify failed cached refresh and keyboard retry through the real browser and daemon. Keep focus in the footer while Retry is disabled so repeated Enter cannot select a cached branch.
* fix(app): preserve picker interaction when retrying branch loads
Use the existing close-and-reopen retry behavior with clear feedback rather than introducing an action that competes with native list keyboard ownership.
* fix(app): preserve workspace tabs and acknowledge deliberate agent focus
Workspace navigation no longer chooses an attention agent. Preserve repeated focus actions so an already selected chat can be acknowledged, and stop consuming attention on departure.
* fix(app): retain deliberate focus until fresh agent data arrives
Connection readiness permits a pending focus acknowledgement without creating a new focus action. Keep that intent across client changes and wait for the current timeline synchronization generation before consuming it.
* fix(app): preserve reconnect feedback during attention acknowledgement
* fix(terminal): run terminals when process identity cannot be read
On some Windows machines Windows PowerShell cannot run Get-CimInstance, or
cannot be started at all. The terminal worker refused to start without its own
birth time and command line, so those machines had no terminals.
Process identity only authorizes signals. The worker now starts anyway and
records its identity as unavailable, and programs without a birth time. Cleanup
never signals what it cannot identify. It asks such a worker to shut down over
the socket the secret authenticates, and reports survivors as unconfirmed.
So records of crashed workers do not block launches, the process table falls
back to process.kill(pid, 0) when the query fails. When a live pid cannot be
identified, a refused connection on the worker's socket marks it as another
process, because a worker listens until it exits.
* fix(terminal): ask unidentified workers to shut down on every cleanup
Retries of an unconfirmed cleanup and startup cleanup run without a grace
period, which skipped the shutdown request. For a worker whose identity is
unavailable that request is the only way to stop it, so it is now always sent.
A gone program recorded without a birth time is no longer walked through the
process table: the table was unreadable when it started, and the walk only kept
the cleanup unconfirmed.
Also test the socket probe on every platform, restore a test of the daemon
starting when the worker cannot, and keep one pid liveness helper.
* test(terminal): fail the worker launch the same way on every platform
Windows opens a directory sitting at the launcher's log path, so the
worker started there and the #8810 test never saw a failed launch. An
invalid protocol override makes the worker exit before it writes its
record on every platform.
* fix(terminal): identify workers through their recorded sockets
* fix(terminal): authenticate legacy sockets before forced cleanup
* test(terminal): exercise closing records without process queries
* test(terminal): identify stale lifecycle records by their sockets
* refactor(terminal): drop the unreleased unavailable-identity record shape
No release wrote identity: { unavailable }; it existed only between
commits of this change. Records keep reading the identity workers up to
0.15.1 wrote, and nothing consults it.
* fix(app): space the turn footer like the rest of the timeline
The footer sat 13px under the last line of text while the text sat about
21px under a tool or thinking row, so the footer looked glued to the
message. The message drops its bottom padding when a footer follows, so the
footer's top margin alone sets that gap; 20 makes the centre-to-centre
distance match the tool-to-text gap.
* fix(app): show the Changes base only on Paseo worktrees
The header rendered "branch -> base" whenever checkout status carried a
baseRef, and the daemon sends one for every git checkout, so plain
checkouts showed "-> main" with no way to change it. Only Paseo worktrees
store a base, so only they show it. The arrow is extra muted so it reads as
a separator, and the base no longer shrinks, so a long branch name truncates
instead of the base.
* fix(app): let a long Changes base truncate instead of overflowing
* feat(server): add an in-memory store for agent background work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(claude): report background tasks as background work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(server): track agent background work in the agent manager
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(protocol): add agent background work messages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(server): serve and push agent background work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): expose agent background work on agent handles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(sdk): document agent background work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(claude): cover background shells in background work end to end
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): resync background work subscriptions on every snapshot
subscribe() now lists the agent's background work each time the event
observation is established, so the listener starts with the current list
and catches up after a reconnect. A list answered after a newer update
is dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(server): answer background work lists without loading dormant agents
Background work lives only in memory, so a stored agent that is not
loaded has none. The list handler now answers [] from storage instead of
resuming the agent, and still rejects unknown, internal, and archived
agents. A new test pins that old apps never receive
agent.background_work.update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(server): read Claude background tasks defensively
The user's claude binary can differ from the SDK typings, so a task's
description is read as unknown and an empty or missing one becomes null.
Drop the monitor kind: Claude Monitor watches report as local_bash, so
kind is "shell" or "other" today. The store now copies only the wire
fields of each input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(server): prove internal background work was emitted before checking globals
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(sdk): describe background work resync and feature gating
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(claude): cover background work reported by a retired query
The daemon stops reading a Claude query it no longer owns, so work that a
retired process still had queued cannot reach the background work list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): let background work subscribers handle a failed list read
When the list read that follows each subscription snapshot failed, the
error was only logged and the caller got neither the list nor a failure.
subscribe() now takes an optional onError that receives it; without one
the error is still logged. The read is not retried, since the next
reconnect reads the list again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(agents): show background work as active
Keep background activity visible after a foreground turn ends while preserving independent send and interrupt behavior.
* fix(agents): protect background work subscriptions
Keep internal work out of public updates and restore observation after a subscription failure.
* feat(agents): show Codex and OpenCode background work
Keep provider-owned shell activity visible after a turn ends while leaving foreground turn controls independent.
* feat(agents): stop individual background work
Wait for provider confirmation so stopping one background item leaves sibling work and foreground turn controls independent.
* refactor(agents): name background stop parameters
* test(website): isolate browser tests from live metadata
* test(agents): clarify background stop journeys
* fix(sdk): report background work subscription failures
Report setup failures so subscribers can replace an observation that the host released.
* fix(app): restore background work after reconnect
Wait for the host handshake before selecting the retained client so an offline pane starts observing when the connection returns.
* fix(agents): retain background shells across rewind
Preserve known live shell ownership when conversation history is rewound, and revoke it when the provider runtime exits.
* fix(website): launch test seeder across platforms
Invoke Wrangler through Node so local website test startup does not depend on an executable npm command shim.
* feat(agents): clarify background task details and tracks
Keep launch descriptions and commands distinct so background work remains identifiable, and allow composer tracks to scroll when space is limited.
* fix(agents): preserve active background work during archive
Keep background-running children out of bulk cleanup and warn before an individual archive. Preserve first taps on composer tracks while the keyboard is open.
* fix(codex): retain background terminals across rewind
Keep live terminal inventories attached to their originating threads so a fork does not hide or misroute their stop actions.
* fix(codex): scope background terminal identities to threads
Prevent commands on separate threads with the same provider process ID from sharing a stop target.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Zero in-flight fetches also describes observation that has not started. Wait for the first fetch to start and settle, and exercise delayed watcher setup so missing callbacks cannot silently skip the test events.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): archive a workspace from its sidebar row and undo it
Hovering a workspace row on desktop shows an archive button next to the
row menu. Archiving shows a toast with Undo and a Cmd/Ctrl+Z shortcut so a
mis-click costs one keystroke instead of a trip through History.
The daemon serializes archive and undo per workspace across every
connection. An undo that lands while an archive is still tearing down
aborts it at the next checkpoint and restores only the agents that archive
took down; deleting the worktree directory is the point of no return, after
which undo recreates the worktree.
* fix(server,app): close archive and undo races found in review
- An archive request now claims the workspace's archive queue before
looking up the workspace, so an undo sent right behind it cancels it
instead of running first and losing to the archive.
- A repeat archive of an already archived workspace no longer wipes the
agents the earlier archive saved for undo.
- Undo no longer brings back an agent that was archived before the
workspace archive and only loaded to read its history.
- Each local archive or undo request owns its own intent token, so a
late response to an earlier request cannot release a newer request's
hold on the row. An archive canceled by another client's undo puts the
row back.
- The undo failure toast shows translated copy; the cause goes to the log.
* fix(app): keep a workspace's layout when another client restores it during the undo window
The undo window purged tabs and layout whenever it closed after a
finished archive. If another client restored the workspace before this
client's toast closed, the restored workspace lost its saved
arrangement. The archive now commits only if the workspace is still gone
when the window closes.
* fix(app): show the archived toast's Undo as a button
* fix(app): keep a workspace's agent tabs when it is opened while its undo runs
* fix(app): keep a row whose undo is running when a directory snapshot omits it
* fix(server): retain archived parent provenance on detach
* fix(server): publish stored child detach updates on parent archive
Keep subscribed clients current when an unloaded child survives its parent archive. Narrow the contributor change to the missing update; archived-parent provenance remains a separate feature.
Co-authored-by: luke <luopeihuan@gmail.com>
* test(server): clean up the detach fixture static directory
Keep the test daemon static directory inside the fixture root so cleanup removes all test-owned directories.
* fix(server): retain runtime settings in stored agent updates
Parent archive publishes stored survivors through the manager. Preserve their saved runtime model and thinking settings so the update matches stored fetches and subscription filters.
---------
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): tighten the touch composer toolbar onto the text column
On touch, the toolbar's icon-only glyphs were 20pt muted outlines at a 1.5px
stroke in loosely spaced slots, and the row sat on a flat -6 margin, so the
attach + and the trailing control floated inside the text column. Touch now
places 44pt slots edge to edge, draws icon-only glyphs at 24pt with a 2px
foreground stroke, keeps label-paired glyphs at 20pt, and runs the context
ring at 26pt with a 3px stroke. The row overhangs into the composer padding
by each end control's ink inset (composerToolbarOverhang), so the + ink and
the trailing glyph or send/stop disc sit on the text column's edges.
Pointer sizing is unchanged.
The folded model control now drops its thinking label before the model
label truncates, and the model label truncates on a word boundary
("Ten second…") instead of leaving a space before the ellipsis. Both are
decided from measured widths. The web reports layout widths rounded to whole
pixels, so a tie counts as not fitting.
* fix(app): measure replacement model labels instead of hiding them
The folded model label and TailEllipsisText kept their measuring views mounted
when the label text changed, while discarding the cached widths. A retained
measuring node whose size does not change emits no new layout event on web or
native, so after switching to a model whose label (or a shortened prefix of it)
has the same width, the widths stayed unknown and the label stayed transparent.
Keying each measurer by what it measures remounts it, so every width is
measured again.
Also move the label width sweep into a named helper so the test body states
the expected label order.
* feat(app): space the touch composer toolbar by what it paints
- Lay each control out at its painted width, 20pt apart, with a disc-sized circle that is both its frame and its fully rounded pressed highlight. Pickers are 36pt pills.
- Match the header icons: 20pt glyphs at 1.5px, an 18pt context ring at 2px.
- Always end the row in a disc (voice mode, send or stop) so it does not shift, nested into the composer's corner, with a concentric 26pt corner radius on touch.
- Stop the picker row's clip from flattening the picker highlight's left edge.
- Budget the density ladder on laid-out widths.
* fix(app): disable the stand-in send disc and keep form model fields in their box
- An empty new chat's stand-in Send is disabled until there is content, as in fullscreen.
- A custom model selector trigger, such as the schedule form's field, drops the toolbar slot's margins.