Pi left task and direct subagent tool calls in the unknown detail shape, so clients rendered raw request and result payloads. Normalize execution calls at the provider boundary so the existing task card owns their running and terminal states.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The archive response already waits for durable archival and owned-worktree cleanup. Assert those final states directly instead of racing a separate filesystem watcher with its own fixed timeout.
Make provider catalogs compact and content-addressed so repeat launches can reuse the persisted snapshot instead of transferring it again. Remove eager global catalog loading and the stopped-agent defensive tail fetch, while keeping sequence-gap and reconnect recovery as the correctness paths. Add an opt-in production-ID Android profiling build for local startup traces.
* fix(server): pin workspace file watchers to native backends
Backend auto-detection could select an IPC watcher whose subscription lock deadlocked the Node worker pool during watcher reconfiguration. Select the host-native backend explicitly so filesystem work, logging, and relay DNS cannot be starved by that path.
* test(server): cover native watcher production boundary
Route the existing real filesystem observation test through the same subscription function used by the daemon. This proves native watcher selection and reconfiguration with actual file events instead of only testing the platform selector.
* test(server): synchronize native watcher readiness
Replace fixed startup sleeps with a real tracked-file event barrier. Native backends can resolve their subscription before the initial stream is fully drained, so the integration test now begins assertions only after an event has been delivered and refreshed.
* fix(server): measure native workspace watchers
* fix(workspaces): default new worktrees to upstream refs
Use the daemon-resolved upstream as the untouched starting ref so unpushed local commits do not leak into new workspaces. Preserve exact base identity for fork remotes while keeping local-first behavior across version drift.
* fix(workspaces): preserve exact base refs in git actions
Resolve accepted display aliases back to the stored ref before comparisons and updates. Fail closed when a non-origin remote base has no explicit mutable merge target.
* fix(workspaces): reject conflicting qualified base refs
Display aliases remain accepted for compatibility, but a qualified ref carries remote identity and must not be canonicalized to a different stored upstream.
* test(server): make upstream fixtures portable
Keep the checkout-status fixture aligned with the optional upstream field, and select main explicitly when cloning a bare test remote whose default HEAD varies by platform.
* fix(workspaces): preserve exact upstream refs
Qualified remote refs now fail closed when pruned instead of switching lineage. Metadata accepts Git-valid upstream names rather than applying a narrower character whitelist.
* fix(workspaces): preserve qualified local base refs
Local tracking branches are exact upstream identities too. Keep heuristics only for legacy unqualified metadata so origin cannot replace a stored refs/heads base.
* feat(hub): pass execution MCP servers to agents
Hub-owned execution capabilities need to reach provider sessions without becoming part of outward agent snapshots. Keep the configuration daemon-private so provider recovery continues to work.
* fix(hub): protect daemon MCP namespace
Hub execution MCP configuration must not replace the daemon-owned Paseo server. Validate only after owned-record lookup so idempotent replay remains stable while new launches fail before provider or durable state creation.
* fix(agent): reject unsupported MCP launches
* fix(agent): scope MCP support guard
* fix(agent): redact MCP metadata from wire payloads
* fix(agent): validate MCP support on restored sessions
* fix(session): rehydrate MCP config on resume
* fix(session): resume newest matching agent record
* fix(session): restore archive state after failed resume
* fix(app): preserve formatting when copying assistant selections
Browser selection serialized React Native Web layout nodes instead of the assistant message semantics. Recover semantic markup from renderer-owned metadata and reuse the rich clipboard generator used by Copy turn.
* fix(app): preserve assistant copy semantics
Carry source-only Markdown metadata through the selection copy boundary, unwrap generated code links, and cover ordered starts, hard breaks, and fence languages in Playwright. Refresh the Nix dependency hash for the added clipboard dependency.
* fix(app): preserve partial selection context
Wrap selected fragments with their shared semantic ancestor chain and enable GFM table and strikethrough conversion. Cover partial nested selections and GFM output through the browser clipboard.
* fix(app): preserve copied list and autolink text
Adjust partial ordered-list starts for omitted siblings, strip parser-generated anchors before Markdown reconstruction, and refresh the Nix dependency hash computed by CI.
* fix(app): normalize partial selection context
Normalize cloned ordered lists across block boundaries, degrade incomplete table fragments to selected text, and preserve table alignment provenance through Markdown reconstruction.
* fix(app): escape copied table delimiters
Rendered table cells no longer retain source escape characters. Escape literal pipes during DOM-to-Markdown conversion so copied tables preserve their column structure.
* fix(app): preserve authored link semantics
The rendered link boundary conflated parser-generated links with authored autolinks and discarded authored titles. Carry title semantics into the anchor and unwrap only links with explicit generated provenance.
* fix(app): preserve nested copy structure
Blank-line splitting separated loose nested lists before Markdown rendering, leaving no nesting for clipboard serialization to preserve. Derive protected separators from the Markdown block structure and keep copy metadata independent from monospace surface styling.
* fix(app): preserve selected file links
Assistant messages and rich clipboard HTML must apply the same link validation policy. A shared parser keeps file links semantic in both MIME representations while retaining markdown-it's unsafe-scheme rejection.
* fix(app): preserve partial table content
A selected body row wider than its retained header cannot be represented as GFM and is truncated when rich HTML is regenerated. Treat only those over-wide fragments, plus headerless fragments, as incomplete while preserving valid short rows.
* Improve mobile terminal typing and selection
* Address mobile terminal review feedback
* Remove dead native terminal input branch
* Replay bracketed paste in terminal preamble
* Stop per-key terminal resize claims
* Stabilize terminal resize browser test
* Use tracked terminal mode for native paste
* Restore terminal toolbar focus handling
* Adapt native terminal to active visibility state
* Stabilize native mobile terminal interactions
* Add native terminal fallback boundary
* test(app): expect explicit terminal resize reclaim
* fix(app): stabilize terminal buffer coordinates
* fix(app): restrict terminal resize ownership
* fix(terminal): gate restored input modes
* fix(app): recover stalled terminal WebViews
* chore(app): remove obsolete terminal harnesses
* fix(app): make terminal resize claims explicit
* test(app): stabilize native terminal device flows
* test(app): track absolute scrollback coordinates
* fix(app): isolate terminal WebView streams
* fix(terminal): arbitrate size ownership across clients
The daemon now distinguishes explicit ownership claims from passive geometry updates. This keeps the active client authoritative while allowing its split, viewport, and keyboard changes to resize the PTY without idle clients stealing it.
* test(server): make worker resize assertion portable
Query the worker's authoritative terminal snapshot instead of invoking a Unix-only shell command, so the resize regression exercises the same boundary on Windows and Unix.
* feat: add DeepWiki MCP server at project level
* feat(omp): poll context window usage during active turns
OMP agents previously reported context window usage only at turn
completion, unlike Claude and Codex which stream live updates. Add a
periodic poll (3s interval) of getSessionStats() that emits usage_updated
events while a turn is active, so the client context window meter
updates in real-time.
* clear context usage poll timer before scheduling next interval
* chore: remove .omp/ directory
* fix(omp): make live usage polling lifecycle-safe
OMP exposes live usage through a stats request rather than stream events. Keep polling scoped to active turns, suppress unchanged snapshots, and invalidate stale completion reads across interrupts, new turns, and session shutdown.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* perf(server): bound Git process starts
Git refresh bursts could keep creating short-lived processes at the concurrency ceiling, causing sustained process churn on constrained hosts. Apply one daemon-global start-rate and concurrency policy to every Git command while preserving the legacy concurrency environment variable.
* build(nix): update npm dependency hash
* fix(server): hold Git slots until process exit
* fix(server): enforce Git limits across process lifetime
* fix(server): align Git metrics with process exit
* Fix Git limiter test deadlocks
* Tune default Git process rate
* fix(server): include native subagents in workspace activity
Provider-native children live outside managed agent snapshots, so background work could outlive an idle parent without keeping the workspace running. Aggregate running child descriptors into the owning workspace and publish workspace updates on child lifecycle changes.
* fix(server): serialize workspace status updates
Queue overlapping descriptor rebuilds per workspace so older activity snapshots cannot overwrite newer lifecycle state. Preserve batched registry reads for multi-workspace updates.
* test(server): update workspace status fixtures
Provide the native subagent activity dependency in the shared session fixture and wait on observable git-watch output across the serialized update boundary.
* fix(server): drop queued workspace updates on subscription swap
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(acp): preserve permission choices
ACP question bridges encode answers as repeated permission options. Preserve each option through the UI and return the exact selected ID, while keeping auto-accept from choosing on the user's behalf.
* fix(acp): validate selected permission behavior
Reject inconsistent action IDs instead of allowing the action ID to override the response behavior. Move the misplaced browser spec so the current base CI contract can run.
* fix(acp): preserve invalid permission requests
Validate selected ACP actions before consuming the pending request so inconsistent client responses can be corrected and retried.
* test(acp): verify real permission questions
* fix(acp): reject empty permission action ids
* test(e2e): scope Kimi config to ACP spec
* fix(server): fail agents when provider process exits
The transport previously closed pending RPCs without terminating the active manager run, leaving the agent stuck in running state. Propagate unexpected process termination as a one-shot turn failure while suppressing it during intentional session close.
* fix(server): keep idle provider exits recoverable
A process death with no active foreground turn is not a failed turn and must not trigger turn-based lifecycle actions. Leave the session disconnected so the next prompt reconnects without publishing a false terminal event.
* fix(server): preserve provider exit recovery
Connection cleanup after a failed reconnect must not close the durable session boundary or discard AgentManager subscribers. Treat both foreground and autonomous root turns as active when propagating process death.
* test(server): make process exit harness portable
* fix(server): clear permissions after provider exit
* fix(server): preserve plan approval after provider exit
* fix(server): serialize provider reconnects
* fix(server): stop reconnects after session close
* fix(server): preserve provider subagent messages
The provider SDK defaults to forwarding only child tool activity, which leaves the nested timeline without the child response. Request the full nested transcript so provider subagent panes retain both sides of the conversation.
* fix(app): correct project status badge test imports
* fix(ci): skip unrelated required checks cleanly
Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable.
* fix(ci): gate changes to install patching script
* fix(ci): route cross-package test contracts narrowly
* fix(ci): keep routed contracts merge-blocking
* fix(server): exclude nested test utilities from builds
* fix(ci): cover app-owned native and browser domains
* refactor(ci): align tests with runtime domains
Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check.
* fix(ci): keep routing and desktop suites isolated
The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs.
* fix(ci): isolate desktop e2e runtime
* perf(test): isolate Playwright workers
* perf(test): parallelize Playwright within specs
* fix(test): keep Playwright file scheduling
* fix(test): share dynamic Metro fixture
* fix(test): preserve own-host restart fixtures
* fix(ci): preserve routed contracts after rebase
* Stabilize Playwright wheel-scroll assertions
* Add automatic plus branch maintenance workflow
* Build plus artifacts for desktop and CLI/server
* Document and summarize plus artifact workflow
* Document Apple signing and pin plus workflow actions
* Disable Linux plus desktop artifact
* Publish plus CLI packages to GitHub Packages
* Fix GitHub Packages publish paths
* Fix plus mise install summary package name
* Disable macOS x64 plus desktop artifact
* Tag plus npm packages as latest for mise
* fix(worktrees): respect selected local base ref
* chore(ci): remove fork-only plus workflow
* fix(worktrees): preserve exact branch picker refs
---------
Co-authored-by: Matt Cowger <1929548+mcowger@users.noreply.github.com>
* feat(acp): auto-accept shared permission requests
Some ACP agents expose permission requests without a native mode that can bypass them. Keep the opt-in at the shared adapter boundary so every ACP implementation gets the same per-session behavior.
* test(acp): include the shared feature in provider expectations
* fix(acp): honor unattended auto-accept intent
* fix(agent): list client features without a model
Generated titles optimized for topical brevity and could omit the requested operation or the identifier that distinguished the task. Preserve the operation, target, and strongest anchor so sidebar titles remain actionable.
The outer test deadline could expire before the archive observer's own timeout after slow Windows setup. Leave enough room for daemon and Git setup so the operation-specific failure remains authoritative.
* Reapply: Stop completed turns from appearing stuck (#2484)
* Reapply: Keep older chat history and image previews stable (#2490)
* Lock timeline layout stability with user-driven specs
Two reds reproduce the reported jank:
- new agent, first prompt: the row paints at top=100 then jumps to 144 through
authoritative hydration, because the draft tab and the agent panel each render
their own AgentStreamView for the same logical row.
- long loaded timeline, submit: scrollHeight collapses 10793 -> 9111 mid-turn,
because splitOrderedTail reclassifies measured rows into the virtualized segment
where the virtualizer falls back to estimateStreamItemHeight.
Also restores centered timeline rows: the pagination anchor wrapper introduced in
#2490 was a plain block div, which broke alignSelf: center.
* Measure the turn footer blink from when the footer appears
Bounding the window at the prompt row conflated two things: the footer arriving a
frame after the row, and the footer leaving the layout and coming back. Bounded from
the footer's first frame, the blink still reproduces, so the footer-ownership race is
real rather than a sampling artifact.
* Rename the shadowed prompt row binding in the frame recorder
* fix(desktop): allow concurrent worktree dev instances
Desktop terminals can inherit another instance's profile path and debugging flags. Scope the profile to the current dev root and select an available debugging port so instances do not share global runtime state.
* fix(desktop): expose development providers in dev builds
Desktop-managed daemons inherited no Paseo runtime mode, so dev builds constructed the production provider registry. Mark unpackaged internal launches as development while continuing to force packaged launches to production.
* fix(timeline): stabilize optimistic turn layout
Keep initial history loading dormant until user intent and preserve active-turn layout across delayed transport updates. Add painted-frame coverage for optimistic timeline transitions.
* fix(timeline): preserve optimistic prompts through hydration
Canonical replacement relied on a transport submission record that can settle before a provider echoes the user row. Keep presentation continuity owned by the unreconciled row while destructive history replacement remains governed by active transactions.
* fix(timeline): make submitted prompts canonical
Submission settlement previously depended on status updates that could arrive after the optimistic row. Persist every accepted prompt in the canonical timeline and keep the footer active until RPC and canonical acknowledgement converge.
* fix(timeline): stop older history pages repeating entries
Backward pagination counted source sequence rows, so page boundaries inside a merged projected item could return that item repeatedly. Count projected entries and advance from their display anchors instead, with server and browser regressions covering the contract.
* fix(timeline): resume agents from the latest history
Use a bounded tail probe on resume so long background runs appear atomically instead of replaying every missed page. Preserve unchanged views, reconcile local/live rows, and keep skipped history reachable through backward pagination.
* fix(timeline): reconcile turn state after missed updates
Turn completion events can be missed while an agent timeline is unsubscribed, leaving footer activity stale even after the directory reports idle. Reconcile the single-owned turn lifecycle from authoritative resume snapshots while preserving newer live evidence.
* test(timeline): cover running state across refresh
Protect the hydration boundary where directory state must restore consistent tab, composer, and footer activity. Expose the running indicator semantically so the regression checks user-visible state instead of spinner implementation details.
* fix(timeline): keep history loading geometry stable
Reserve a fixed history-start gutter regardless of pagination state so showing or hiding the loader cannot shift timeline content.
* fix(timeline): keep turn activity coherent
Derive footer, tab, and composer activity from one lifecycle and submission projection. Idle directory transitions now request an authoritative resume so missed terminal events converge without treating directory delivery order as turn authority.
* fix(tests): align checks with runtime boundaries
* fix(desktop): stop dev app surviving its terminal
Electron was detached from the desktop runner, allowing it to outlive a stopped workspace terminal with broken output pipes and retain the single-instance lock. Keep Electron owned by the runner and preserve explicit process-group cleanup for detached child trees.
* fix(timeline): preserve activity through turn handoff
Correlate lifecycle events and snapshots with daemon-owned turn identity, and keep optimistic submissions active until their canonical row arrives. This prevents stale updates and RPC timing from exposing a false idle state.
* refactor(timeline): remove submission timing state
* fix(timeline): restore prompt identity on resume
* fix(timeline): exclude attachments from canonical prompt text
Structured provider prompts can include hidden history attachments before the user's text. Persist only plain prompt blocks so authoritative user messages match what the composer displayed while provider input remains unchanged.
* refactor(agent): remove unused pending-run message identity
* fix(desktop): let Chromium allocate the debugging port
* refactor(agent): remove impossible finalized-turn tracking
* fix(timeline): load history after keyboard scrolling
Treat observed upward displacement as user intent so keyboard and scrollbar navigation can wake dormant pagination.
* test(server): preserve accepted turn stream order
The manager-owned accepted turn boundary intentionally precedes coalesced output. Assert that lifecycle event explicitly while retaining the timeline-before-terminal ordering contract.
* test(app): scope sidebar fixtures by identity
Shared E2E daemon state may contain unrelated projects. Keep stale-route coverage tied to its named missing project and drag only the two rows seeded by the reorder spec.
* fix(timeline): gate prompt revisions by client capability
* fix(timeline): finalize canonical prompt delivery
Treat provider identity as a single stored-row transition so legacy clients receive one deliverable canonical prompt. Release identity-pending rows before later foreground output or the current terminal while preserving stale-turn isolation.
* fix(server): preserve legacy prompt pagination
* fix(timeline): keep assistant block identity stable
Assign canonical block IDs during live promotion so terminal finalization cannot remount image-bearing rows.
* fix(timeline): apply covered revisions during resume append
Run the exact canonical revision pass before overlapping resume pages append newer rows, while leaving cursor advancement to the incremental path.
* fix(server): preserve queued agent transitions
Project each queued live snapshot before awaiting stored metadata so later lifecycle state cannot overwrite an earlier transition.
* fix(server): snapshot queued agent updates
Capture each live agent payload before it enters the per-agent queue so shared nested state cannot change while an earlier storage read is pending.
* docs(app): date agent turn compatibility cleanup
* refactor(timeline): mint submitted prompt identity at acceptance
Canonical prompt rows now keep one immutable wire identity while provider identity remains internal for rewind translation. This removes the unreleased same-sequence revision and legacy projection contract.
* refactor(app): remove prompt revision reconciliation
Submitted prompt rows are immutable on the wire, so resume and live reducers no longer need to apply already-covered same-sequence updates.
* test(app): cover submitted prompt rewind roundtrip
* docs(timeline): describe immutable submitted prompt identity
Provider echoes now enrich daemon-internal identity only; wire rows keep their acceptance-time identity and rewind performs the provider translation.
* test(server): validate mock rewind targets
Reject unknown provider message IDs so app rewind coverage exercises wire-to-provider identity translation.
* fix(server): validate rewind before interrupting
* fix(server): omit rewind identity for daemon prompts
* test(app): guard rewind eligibility and rejection
* fix(server): preserve prompt identity across hydration
* refactor(timeline): remove stale revision remnants
* Revert "fix(server): preserve prompt identity across hydration"
This reverts commit 6adcf7e5b0bc8601761af59b461a746cb5e395fa.
* fix(timeline): settle missed submission acknowledgements
* fix(workspaces): keep restored merged workspaces active
Auto-archive previously reacted to every merged pull request snapshot, so restoring a workspace immediately archived it again. Persist the consumed change request with the archive transition so explicit recovery remains authoritative.
* test(e2e): clean partial archive scenarios
Track workspace resources as soon as creation succeeds so later setup failures archive the worktree and remove the registered project.
* test(workspaces): pin archive latch restoration
Cover the persisted archive marker across restore in CI and make the real browser assertion observe active workspace stability after refresh.
* fix(workspaces): latch restored legacy archives
Resolve the current merged change request while restoring records created before the archive marker existed, then persist it with the unarchive mutation.
* fix(workspaces): refresh latch on restore
When an archived workspace now points at a different merged change request, record that URL during restore so the restored workspace remains active.
* test(e2e): make archive latch setup fail loudly
Remove authentication-gated skipping and register each external fixture cleanup immediately after allocation so setup failures cannot leak resources.
* test(workspaces): include archive latch in reconciliation
The Windows runner could observe the marker after creation but before its contents were written. Wait for the expected value so the test synchronizes on the behavior it asserts.
* fix(app): show worktree names in workspace hover cards
Managed worktree paths exposed Paseo's internal directory layout instead of the useful worktree name. Carry the owned worktree root through the sidebar model while preserving the full workspace path as the copy target.
* fix(workspaces): source owned worktree names from daemon
The daemon owns worktree path semantics, so expose the managed worktree slug and let the sidebar model produce a display-ready label. This keeps path parsing out of the hover-card component while preserving the full path for copying.
* fix(app): preserve worktree labels in replica cache
* feat: make relay opt-in for device pairing
* fix(relay): keep pairing state authoritative
* fix(relay): refresh pairing state across transports
* fix(onboard): honor explicit relay opt-out
* fix(relay): surface transport startup failures
* fix(pairing): gate offers for older daemons
* test(cli): rely on real pairing transport coverage
* fix(cli): report live relay without a pid file
* fix(relay): synchronize config during daemon startup
* fix(relay): preserve mutability for ignored overrides
* fix(cli): use reachable daemon for pairing
* fix(app): redirect invalid pair device routes
* fix(pairing): support remote host device pairing
Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals.
* fix(pairing): validate pairing destinations
Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state.
* fix(pairing): render QR codes across platforms
Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack.
* fix(cli): pair from persisted relay state
Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
The bulk-history cases spawned about 80 Git processes, which could exhaust the per-test timeout on loaded Windows runners. Import each linear history in one Git process while preserving the real repository behavior under test.
* feat(projects): add local icons and colors
* refactor(projects): simplify appearance handling
* fix(projects): serialize appearance saves
* test(app): update project appearance expectations
* test: fix project appearance CI expectations
* refactor(projects): organize appearance code
* fix(server): adopt main's reconcile model in project appearance updates
* fix(projects): preserve host-local appearance targets
* fix(desktop): restore Linux development startup
Ubuntu can block Electron's unprivileged user-namespace fallback while the npm-installed sandbox helper is not SUID. Disable the Chromium process sandbox only in the Linux development runner so packaged desktop policy remains unchanged.
* feat(projects): add custom project icons
Keep URL acquisition in the client so the host daemon only validates uploaded bytes. Project name and icon changes share one adaptive edit sheet.
* refactor(settings): scope projects by host
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* refactor(claude): derive subagents from the SDK task protocol
Claude Code announces subagent identity and lifecycle on the SDK stream
(task_started, task_updated, task_notification). Paseo consumed only
task_notification and reconstructed the rest from sidechain frames, in two
independent derivations that had drifted apart.
Both paths now translate into one small observation vocabulary and fold it
with one function, so identity and status are derived once. The fold is
stateless: the task protocol is edge-triggered and the store's sticky merge
already accumulates.
- Replay links a subagent through the agent-<id>.meta.json sidecar, which
carries the same Task tool_use id the live stream keys on. The agentId
text scrape stays as a fallback for sessions recorded without it.
- The sidechain tracker keeps mapping frames to timeline items but no longer
owns the descriptor when the task protocol is active, detected from the
session rather than from a version string.
- Adds replay coverage, which did not exist.
Subagent ids are unchanged, so no protocol, store, or client change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(claude): keep non-subagent tasks out of the track, open the child timeline with its prompt
Backgrounded shell commands announce as task_started with a tool_use_id but
task_type local_bash and no subagent_type, so filtering on the id alone put
`sleep 20` in the subagents track. Discriminate on task_type, falling back to
subagent_type for releases that predate it.
task_started also carries the prompt. Emitting it as the child's first timeline
item means the pane no longer opens mid-conversation, and matches replay, where
the sidechain transcript already begins with the prompt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(claude): drop status for tasks that were never declared as subagents
task_notification also fires for backgrounded shell commands and carries a
tool_use_id. Routing status off that id readmitted exactly what the declaration
filter rejected, as a descriptor with a status and no identity — rendered in the
track as a nameless "Loading..." row.
Status now routes only through the task_id map populated at declaration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(app): name a subagent row after its task, not its type
The descriptor carries the subagent type in title and the task in description.
The client collapsed them and rendered only the type, so every child in a
fan-out read "general-purpose". The task is what tells siblings apart, so it
names the row and the type moves to the muted secondary line, which the track
computed but never drew.
Providers that report no task keep the previous behavior.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(claude): label the parent's Task card from the subagent declaration
A backgrounded Task subagent emits no frames carrying parent_tool_use_id, so
the sidechain tracker never runs for one and never supplies the sub_agent
detail. Its card in the parent transcript rendered as a bare "Task".
The declaration already carries the type and the task, so the card is built
from it. For a foreground subagent the tracker still emits the same card
enriched with the child's action log; the two collapse on the shared tool-call
id, so that case is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(subagents): surface a subagent's model, effort, and cost
Adds model, effort and usage to the provider subagent descriptor, populated
from observations the child itself reports.
- model: read per assistant frame, live and on replay. Never snapshotted, since
a provider can swap models mid-flight on overload or refusal fallback, and
never inherited from the parent.
- effort: replay only. The SDK's assistant message does not carry it and it can
be silently downgraded for the chosen model, so a live value would be a guess.
- usage: task_progress reports tokens and tool count once per tool use, and
task_notification reports the terminal totals. This matters most for
backgrounded subagents, which emit no sidechain frames and would otherwise
show no sign of activity at all.
Status becomes optional on the upsert event: a report that only carries a model
or a cost says nothing about whether the child is still running, and must not
revert a finished one. Usage merges rather than replaces, because duration only
arrives at the end.
Fields are optional on the wire, so an older daemon simply omits them and the
client renders nothing rather than substituting the parent's settings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(claude): read subagent effort from hooks
Effort appears nowhere on the SDK message stream — verified by scanning every
message type at depth — and the level Paseo requests is not necessarily the one
that runs, since a model that does not support it is silently downgraded.
Hooks are the source. Every hook input carries agent_id and effort.level, the
active level for that turn after any downgrade, and agent_id is the same id
task_started calls task_id, so it routes through the table already built at
declaration. PreToolUse, PostToolUse and SubagentStop are registered purely to
observe; they record what they see and always return empty, so they cannot
affect tool execution or turn control.
Paseo's hooks merge with any the user configured rather than replacing them:
extraClaudeOptions is spread after the base options, so assigning would have
silently stopped effort being observed for anyone with their own hooks.
Verified end to end through ClaudeAgentClient against a real session: a
subagent descriptor now carries model, effort and usage together.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(subagents): show a subagent's runtime facts in its pane, and its cost on replay
The track row carried model, effort and cost; the pane carried none of it, so
opening a subagent showed only its task.
The pane gets a header strip rather than a longer tab subtitle: the subtitle
feeds the width-constrained tab strip, whose job is to name the task. Duration
appears in the pane, which has room, and stays out of the dense row.
Replay had no usage source, so a reopened agent lost its subagents' token and
tool counts permanently — descriptors are in-memory and cleared when an agent
closes. Usage is now derived from the child's own transcript, matching the live
producer exactly: total tokens are the four counters summed on the last
assistant turn (a context reading, not a cumulative sum, which would multiply
the cached prefix by the turn count), and tool uses are tool_use blocks across
assistant entries. Duration is an approximation — the transcript's first entry
is written after the task starts — and is the one field replay cannot match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(claude): record the task protocol's undocumented wire facts
The subagent sources depend on Claude Code internals the SDK types do not
explain: which announced tasks are subagents, why a status for an undeclared
task is dropped, why task ids outlive a turn, why effort is only reachable
through hooks, and why a replayed session must skip its grandchildren.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Generalize provider subagent presentation
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(git): reconcile pull requests after branch changes
Treat persisted change-request metadata as a branch-bound hint and prefer current tracking configuration when it identifies the checked-out branch.
* fix(git): preserve enterprise fork identity
Use branch-bound metadata to supplement the owner when explicit tracking points at the same head on a self-hosted remote.
* fix(git): honor local branch upstreams
Treat Git remote dot with a merge ref as explicit current tracking so stale PR metadata cannot override a local upstream.
* fix(git): own change request branch bindings
Centralize persisted hint validation and rebinding so renames preserve change request identity while switches invalidate it, including normalized legacy Enterprise owners.
* fix(git): preserve PR identity through workspace renames
Resolve the backing worktree root during rename without adding a Git call, and retain fork owner identity from explicit same-name tracking configuration.
* fix(git): trust current enterprise fork identity
Explicit tracking now derives repository identity from the current remote path, so stale checkout metadata cannot override a repointed fork.
* fix(git): recognize enterprise repository aliases
GitHub repository identity is case-insensitive. Normalize equality checks so differently cased aliases cannot turn base tracking into a fork lookup.
* fix(git): reject ownerless legacy fork matches
Owner-qualified legacy hints only belong to their historical owner-prefixed branch names. A later ownerless branch reusing the same head must resolve from current Git state instead.
* fix(git): keep fork feature lookups local
A feature branch may track the upstream base branch for ancestry even when origin is a fork. Only treat that cross-repository base ref as PR identity when the local branch name identifies the tracked head.
* fix(server): ignore OpenCode snapshot diffs when starting autonomous turns
OpenCode stamps its filesystem-snapshot diff onto the user message after
the turn goes idle. Since #2258 Paseo treats an unknown idle user
message.updated as the start of an autonomous turn, so that late metadata
write opened a turn no provider-idle boundary would ever close. The agent
stayed falsely running until the next submission interrupted it.
Reject user message.updated events carrying summary.diffs. Real prompts
never carry snapshot diffs, so genuine autonomous turns still start, and
filesystem snapshots stay enabled for undo and revert.
* fix(server): trust OpenCode busy status for autonomous activity
Mutable message records can receive metadata after execution ends. Treat the provider's busy status as the activity boundary and buffer preceding content until that boundary arrives.
* test(server): cover autonomous provider activity
* fix(server): reconcile OpenCode wakes from busy status
# Conflicts:
# packages/server/src/server/agent/providers/opencode-agent.ts
* fix(server): preserve background activity boundary after stops
* fix(server): keep idle events outside background activity
Only provider execution status proves a new activity boundary. Reconcile adopted sessions once at subscription time instead of retaining unowned idle events for a future run.
* fix(server): order adopted session reconciliation
Live runner-status events supersede an in-flight status snapshot. Treat busy and retry as one active provider state.
* test(server): remove misleading snapshot gate
---------
Co-authored-by: Des Flynn <des@desflynn.com>
* fix(opencode): prevent late aborts from canceling replacement turns
OpenCode aborts are session-scoped, so provider idle alone does not make a session safe to reuse. Keep abort retries owned by the stopping turn and require both abort settlement and provider idle before dispatching the next runner operation.
* fix(opencode): retain the stop barrier through provider idle
Keep the stopping operation authoritative until both its session-scoped abort and provider-idle evidence settle, including stale idle-session interrupts. Also keep the real drift fixture credential-allowlisted and wait for command output pipes to drain.
* test(opencode): launch Windows fixture shims safely
* fix(opencode): recover stop observation safely
* fix(opencode): preserve autonomous wake through stop
* fix(opencode): retain deferred wake completion
* fix(opencode): serialize stop boundary replay
* test(opencode): use shared real e2e lane
* fix(opencode): own stops started during replay
* fix(opencode): retry stop observation on demand
* fix(opencode): follow replacement stop boundaries
* fix(opencode): retain wakes during stop recovery
* fix(opencode): hold replay turns behind boundary
* fix(opencode): filter canceled reconnect output
* fix(opencode): bind status evidence to stop
* fix(opencode): acknowledge owned cancellation
* fix(opencode): reconcile stale busy evidence
* fix(opencode): bind replay terminals to wakes
* refactor(opencode): own the stop boundary in one explicit state
The run boundary had grown into a mutable flag bag plus a raw-event
buffer that was replayed and re-classified after the fact. Each new
interleaving needed another predicate, and the classification kept
losing autonomous wakes.
Restore stopping as a turn-state variant that owns exactly two things:
the session-scoped abort it issued (with its single owned retry) and the
canceled run's terminal. Runner operations wait on both; a failed abort
fails closed. Provider status reconciliation now ends the stop through
the same transition an SSE terminal takes.
The canceled run's residue is suppressed only up to that terminal.
Events published afterwards are a new run by construction and take the
normal live path, so a wake no longer waits for abort settlement and
cannot be dropped by a retention heuristic.
* fix(opencode): emit cancellation before adopting the next run
The acknowledgement ran off a promise combinator, so a wake adopted from
the event right after the terminal could reach subscribers before the
canceled turn's turn_canceled. Acknowledge synchronously from whichever
proof lands first instead.
* docs(opencode): attach the stop doc comment to its interface
* fix(opencode): retry the stop in progress instead of opening a second
Pressing Stop again after an abort failed built a detached boundary: its
fresh abort could succeed while the canceled turn kept waiting for an
acknowledgement, so the daemon sat out the full cancel timeout before
force-canceling. A repeated Stop now replaces the abort owned by the
stop already in progress, which is what carries the acknowledgement and
what lifts the fail-closed gate.
* fix(opencode): accumulate every abort a stop has issued
A repeated Stop replaced the stop's abort settlement instead of adding
to it, so a first request still in flight — the slow-teardown case the
cancel cap exists for — no longer held the gate. The replacement could
start while that request was live and get cancelled by it, which is the
race this boundary exists to close. Reuse now waits for every abort the
stop issued, while only the newest can hold the gate closed.
* fix(opencode): scope abort settlement to the session, not the stop
Abort is session-scoped, so its settlement has to be too. Tracking it on
the stop meant a new stop boundary silently dropped an older request
that was still in flight, and that request could still cancel whatever
run started next. The session now accumulates every abort it has issued;
the stop is left owning only the run it is stopping.
* fix(opencode): recheck the abort gate after waiting on it
A waiting prompt read the settlement once, so a Stop issued while it
waited was invisible to it: the older abort would settle, the prompt
would start, and the newer one could still cancel it. Drain until the
settlement observed is the current one.
* fix(projects): restore cross-host grouping
Random host-local project IDs replaced the remote-derived value that had also served as grouping identity. Persist a separate opaque group key and let normal boot reconciliation backfill older records without a migration.
* fix(projects): keep ambiguous clones separate
A shared remote can identify a project across hosts, but it cannot distinguish two independent clones on one host. Treat those keys as ambiguous so host-local project IDs remain separate.
* fix(projects): preserve host-local project identity
* fix(projects): strengthen grouping identity
* test(app): update project host shape contract
* fix(projects): preserve selected host identity
* fix(projects): close grouped routing gaps
* fix(projects): stabilize legacy subproject grouping
* fix(projects): preserve selected project identity
* fix(projects): isolate legacy host identity
* fix(projects): preserve symlinked bootstrap roots
* fix(projects): reconcile grouped custom names
* fix(projects): stabilize settings and path identity
* fix(projects): normalize default remote ports
* fix(projects): harden host and remote identity
* fix(projects): preserve host-scoped actions
* fix(projects): preserve remote path semantics
* fix(projects): model SCP remote semantics
* fix(projects): keep settings on routed host
* fix(projects): preserve SSH URL path semantics
* fix(projects): separate identity from presentation
* test(projects): use grouped identity in UI specs
* fix(projects): harden grouped project routing
* test(projects): expect framed settings route
* fix(projects): preserve exact placement identity
* fix(projects): harden grouping identity and routing
* fix(projects): restore project key as grouping identity
Random host-local project IDs had replaced the persisted cross-host value carried by projectKey. Keep projectId for routing while reconciliation owns and persists projectKey for grouping.
* fix(projects): prevent project key collisions
Preserve platform path semantics and generic remote transports in cross-host keys, and compare legacy checkout roots using filesystem equivalence before materializing ownership.
* test(projects): assert host-scoped project keys
* fix(projects): preserve project key compatibility
Resolve legacy workspace project references at the daemon boundary while keeping projectKey as the cross-host identity. Complete generic SSH normalization without collapsing distinct repositories.
* fix(projects): harden project reference identity
* fix(projects): retain archived reference errors
* fix(projects): scope legacy paths to hosts
* test(app): model persisted project keys
* fix(projects): retain absolute scp users
* fix(projects): resolve legacy mutation aliases
* fix(projects): preserve conservative identity boundaries
* fix(projects): preserve legacy placement identity
* test(projects): assert legacy placement identity
* test(projects): expect canonical Windows keys
* fix(projects): separate bootstrap identity fields
* fix(projects): scope legacy paths by host
* fix(projects): stabilize opaque project identity
* fix(projects): preserve remaining opaque identities
* fix(projects): classify forward-slash UNC roots
* fix(projects): preserve opaque selection keys
* fix(projects): frame host-local project keys
* fix(projects): canonicalize framed identities
* test(projects): isolate repeated remote fixtures
* fix(projects): keep project key as UI identity
* fix(projects): reconcile hydrated project references
* fix(projects): preserve archived project identity
* refactor(projects): group projects from project records, not workspaces
Cross-host grouping had been built by hanging a projectKey off every
workspace descriptor. That scoped the key to the wrong entity: the app
then had to reconstruct project identity per workspace in six modules,
which forced projectKey fallbacks into RPC arguments and a server-side
resolver that turned grouping keys back into project ids.
A workspace has a projectId. The key belongs to the project, and
grouping is a display concern, so the daemon now publishes projects
over project.list plus the existing project.update channel and the app
groups them in one place. Projects are addressed by host-local
projectId everywhere, including the settings route.
Projects persisted without a key are backfilled by ordinary boot
reconciliation, with no migration. Old daemons advertise no projectList
capability and simply do not group.
* test(projects): seed projects alongside workspaces in fixtures
Sidebar fixtures seeded workspaces without their projects, which the
daemon cannot produce now that projects arrive on their own channel, so
nothing rendered. Adds a seeding helper that supplies both.
Also drops the provisioning test for accepting a project key where a
project id belongs; that resolver was removed deliberately.
* fix(projects): keep derived project identity stable
Four gaps left by shrinking the key derivation:
Local keys used a bare path resolve, so one directory reached by a
different spelling produced a different key and reset grouped identity.
Windows CI caught this with the 8.3 and long-name forms of one temp
path. Canonicalize through the shared realpath-aware normalization.
Subproject display names lost their selected-path input, so every
subproject of a monorepo rendered under the same remote-derived name.
Reconciliation only ever backfilled a missing key, so a project whose
remote changed stayed grouped with hosts on the old remote.
Project settings kept a disconnected host selected and rendered a dead
end with no way back to an editable host.
* fix(projects): read scheme remotes as URLs, not scp form
An `ssh://user@host:22/org/repo` remote matched the scp-form pattern
first, because `[^@]+` happily eats `ssh://git`, so the port landed in
the path and the project keyed as `remote:host/22/org/repo`. Two
spellings of one remote therefore never grouped.
Try scp form only when there is no scheme, and drop protocol-default
ports so an explicitly written `:22` matches the same remote without it.
* fix: reject oversized checkout diffs
* fix(diff): keep large previews from disconnecting clients
The physical socket limit is an OOM backstop, not a frame-size contract. Bound structured diff construction incrementally and report oversized previews without closing the client's socket.
* fix(app): preserve oversized diff state
* fix(diff): stay within relay frame limit
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* test(server): make stderr timeout coverage deterministic
The timeout assertion raced Windows child-process scheduling and stderr pipe delivery. Buffer stderr before starting the timed request so the test isolates the transport behavior it intends to verify.
* test(server): synchronize stderr timeout test in parent
Use the injected process adapter to populate stderr through the parent-side stream before starting the timeout. This removes cross-pipe ordering from the test.
* fix(github): support repository lists from older gh versions
GitHub CLI versions before 2.28 reject the visibility JSON field before returning repository data. Retry that specific failure with the older isPrivate field while preserving exact visibility on newer clients.
* fix(github): stop requesting repository visibility
Repository cloning does not use visibility. Leave the released response field optional and keep repository discovery on JSON fields supported by older GitHub CLI versions.
* fix(github): keep the visibility enum on the wire, derive it from isPrivate
Restores the required visibility enum in the search-repositories response
schema. Omitting it broke old clients, which reject the whole response.
The daemon still never asks gh for visibility -- that JSON field does not
exist before gh 2.28 and Debian Bookworm ships 2.23. It asks for isPrivate,
supported on every version, and maps it to public/private.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: preserve context window data when turn_completed lacks usage
Merge incoming usage into agent.lastUsage instead of replacing it
wholesale. Some providers (Claude autonomous turns, OpenCode) emit
turn_completed without a usage payload, which previously wiped the
contextWindowMaxTokens/contextWindowUsedTokens accumulated during
streaming usage_updated events. The result was the context window
meter disappearing from the composer once the agent finished work.
* test(server): preserve usage after autonomous completion
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Add npm release track invariant and beta dist-tag procedure
* Revert "Keep older chat history and image previews stable (#2490)"
The timeline optimistic/pagination rework needs more work than main releases can wait for. Reverted together with #2484; both are reapplied on integration/timeline-optimistic-rework.
* Revert "Stop completed turns from appearing stuck (#2484)"
See the #2490 revert. #2490 built on this submission-authority change, so the two revert and reapply as a pair.
* fix(server): keep idle agents resident
Remove time-based runtime collection so background work and subsequent prompts are not destroyed during idle periods. Runtimes now close only through explicit lifecycle actions.
* fix(server): preserve explicit close coordination
* fix(server): support partial agent manager adapters
* fix(app): keep timeline history and previews stable
Treat persisted timeline replicas as display-only so authoritative pagination always comes from the daemon. Require renewed user intent between older-history pages unless the viewport genuinely remains at the history edge.
Model assistant image acquisition as loading, loaded, or failed so recreating a preview URL cannot be rendered as an error.
* fix(app): keep file image previews current
* fix(app): stabilize history pagination edges
* fix(app): recover transient timeline loads
* fix(app): retain assistant image previews
* Stabilize history pagination lifecycle
* Stabilize history settlement and image previews
* Retry file images after reconnect
* Keep active previews and pagination requests alive
* Replace image hook tests with typed ports
* Integrate timeline hydration with submission authority
* Harden pre-hydration timeline reconciliation
* Make rewind E2E use real scroll intent
* Preserve live timeline state through hydration
* Keep mounted image attachments retained
* Protect preview persistence and lifecycle hydration
* Preserve idless live assistant continuations
* Preserve timeline rows across delayed hydration
* test(app): cover real assistant image files
* Preserve assistant tool ordering during hydration
* fix(server): replace stale plan approvals
Synthetic plan approvals accumulated across planning turns because each proposal used a new permission ID. Dismiss the current proposal only after a follow-up prompt is accepted, and enforce one pending proposal when a newer plan arrives.
* fix(server): close plan approval submission race
Deactivate the existing proposal before starting a revision so it cannot be implemented concurrently. Restore it when submission fails, while preserving any newer approval emitted during the request.
* fix(server): deactivate plans before prompt setup
Start the plan-dismissal transaction before any asynchronous prompt setup so a stale proposal cannot be implemented from another client while the revision is preparing.
* fix(server): make prompt dismissal final
Treat prompt submission as the dismissal event instead of compensating after failures. This avoids resurrecting stale plans across ambiguous provider outcomes and manager failure cleanup.