076d9751b7215878a5ffd8124eeb57e90027f31e
372
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
076d9751b7 |
docs: define Service against Workspace script in the glossary
The sidebar row narrowed to services only and its display-preferences item is now labelled Services, while the panel and menu that list both types are still Scripts. That is two words in the UI for things a reader will assume are one, which is exactly what this file exists to settle. |
||
|
|
211489c658 |
refactor(app): unify status colours on one token family
Three systems were reporting the same states: a full and a muted status tier in the theme, plus raw palette steps in StatusBadge and getStatusDotColor. A PR icon was muted in the sidebar and full-strength in the PR panel, and the Resolved badge used a theme-independent green so it painted a dark block in light mode. The muted tier is deleted and every status surface now reads the one token per signal. Dots keep a band of their own. A dot is a few points of solid colour with no shape to read and no label attached, and the running one pulses, so at the status band's lightness it sat below the check icons and host badge on its own row — backwards, since the dot is the row's state. Chroma caps at 90% of gamut rather than 100% because at the edge green reaches C=0.215 while blue manages 0.116 and the set stops reading as one family; dark lightness caps at 0.72 because red desaturates to pink above it. Host badges needed a foreground variant of the identity table. That table is tuned for a white letter on the colour as a fill; used as text it cannot clear 4.5:1 on both themes at once, since light needs luminance <= 0.183 and dark needs >= 0.212. The new set is generated on the status family's exact lightness and chroma fraction, so a host name and a CI check in the same meta row carry equal weight instead of the brighter one winning the row. Also: PR avatars had their own eight-hex palette and hash duplicating identity-colors; workspace rows under a project group now indent to the same rail as status-grouped rows; group headers are muted; and the pulsing dot is faster and fades further. |
||
|
|
1c43e4eea1 |
feat(website): offer the beta channel on the download page
Beta testers had to find prerelease assets on GitHub by hand. The download page now carries a Stable/Beta switch, with stable staying the default and the canonical URL. The channel lives in the URL (?channel=beta) rather than client state so maintainers can link someone straight to it. That link outlives the beta it was shared for, so the switch only renders while the newest prerelease leads stable on its core version, and a stale ?channel=beta falls back to stable instead of serving a promoted version twice. "What's new" deep-links the minor group anchor (/changelog#release-0.3), not the exact entry: promotion rewrites the -beta.N entry in place, so that anchor dies while the group survives — and the group is the stable-to-beta diff a beta downloader came to read. A version missing from the bundled changelog links plain /changelog rather than a dead anchor, which covers the window between a tag going public and the site redeploying. Homebrew, the app stores, and the web app have no beta, so the Beta view drops those rows instead of showing inert placeholders. |
||
|
|
d191fa6685 |
feat(app): automate iOS beta distribution
Beta tags now reach external testers without per-release dashboard work. Reserve distinct native build slots so successive betas and the promoted stable build can coexist under one app version. |
||
|
|
7dbeff8290 |
perf(app): reduce startup websocket work
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. |
||
|
|
b020a1d1ed | Merge remote-tracking branch 'origin/main' | ||
|
|
bf6cd09d6c |
fix(app): run menu selections that close the menu on iOS
Selecting an item parked its action in a ref and waited for the Modal's onDismiss to flush it. MenuOverlay returns null as soon as the menu closes, so the Modal unmounted and onDismiss never fired: every close-on-select action was dropped. Creating a terminal from the compact workspace menu did nothing at all. Wait on a timer instead. The grace period was always about giving UIKit time to tear the surface down before a native presenter launches, and a callback fired from inside a subtree being removed is racing its own removal. The sheet had no flush at all, so it was broken the same way for a different reason. |
||
|
|
56cb70fa59 |
feat(app): size menu rows for the input driving them
Rows are 40pt below md and 28pt above it. The split is on breakpoint rather than presentation because the compact popover, which compactMode defaults to, is worked with a thumb just as a sheet is. Move vertical spacing off the row onto a new MenuPage, which also provides a page's depth so the popover and the sheet cannot disagree about what a page is. A row carrying vertical margin would serve both the edge inset and the inter-row gap at once, and margins do not collapse. |
||
|
|
df32e6ddcd |
Fix the CI failures on main (#2850)
* fix(app): keep an open workspace menu from unmounting its own trigger The sidebar kebab is revealed by hover, and on a compact layout the menu it opens is a sheet that slides up over the row it came from. The row un-hovers, the trailing overlay unmounts the trigger, and the menu's open state goes with it — nothing is left that can close the surface already presented in the portal, so a full-screen backdrop swallows every click until reload. Lift the menu's open state to the component that decides whether to render the kebab, and keep the kebab rendered while the menu is up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(app): follow the sidebar and host badge redesigns in the e2e suite Three specs were still driving surfaces that moved out from under them, so CI has been red on main since the sidebar polish landed. Display preferences became one row per decision with the options a page down, so grouping and the host filter need that page walked first. Five specs had their own copy of the two-line dance; it lives in the sidebar helper now, and the host filter has its own opener next to the menu it belongs to. The host badge is plain secondary text on the meta line and its identity colour lives on the server icon alone, so the helpers stop asserting a tinted label and a pill fill that the redesign removed. Alignment rails are compared across canvas glyph metrics and SVG bounding boxes, so the residual depends on the host's font; a 1px and a 0.5px tolerance both landed on the wrong side of it in CI. Two pixels still catches the whole- pixel offsets these rails encode. The compact sheet spec also read its rails while the panel was still sliding in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(app): let a browser e2e run take a private Metro cache Metro's on-disk cache is shared by every checkout on the machine. Editing one app source file rebuilds that module against everyone else's cached output and the Unistyles styles can come back desynced, which surfaces as alignment specs failing with real-looking pixel numbers that go away when the edit is reverted. E2E_METRO_CACHE_VERSION namespaces a run's cache without deleting anyone else's entries, so the cache can be ruled out before a layout failure is believed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(app): stop the chat scroll helper from hanging on a stalled sampler `scrollChatAwayFromBottom` waited on a promise that could only ever resolve from a `requestAnimationFrame` sampler started by a `wheel` listener. rAF does not fire while the page is occluded, which is reachable on a CI runner, so the sampler could stall on its first frame and the wait never returned — the test died on its own 240s timeout with nothing to read. A missed wheel event did the same. Settling the scroll before measuring is an optimization; the assertion after it is what decides the test. Sample on a timer with a deadline so a stall falls through to that assertion instead of consuming the test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(app): remove layout geometry e2e coverage Rail and pixel-alignment assertions depend on host rendering details and should not define the browser E2E contract. Preserve the surrounding behavior coverage while dropping the Metro cache escape hatch that only supported diagnosing those assertions. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
fb657ebc20 |
Default new worktrees to the upstream branch (#2848)
* 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. |
||
|
|
3a9d39b63a | Merge branch 'main' of github.com:getpaseo/paseo | ||
|
|
b4a4507fb2 |
feat(app): keep recently viewed chats live (#2842)
Replace time-based subscription expiry with a bounded hot set so switching among a small working set and backgrounding the app do not force stale timeline catch-up. Visible chats always retain priority, while disconnect and legacy delivery preserve their existing authoritative boundaries. |
||
|
|
a361d1cb2f |
fix(app): polish workspace navigation surfaces
Use mobile action sheets and long-press file menus while preserving provider context across sheet portals. Keep sidebar and file-pane glyphs on shared visual rails, avoid scrollbar layout shifts, and hide keyboard affordances where shortcuts are unavailable. |
||
|
|
ab8151d33f |
feat(app): sidebar display preferences, a shared menu engine, and a colour pass
Sidebar polish round. Menus. `dropdown-menu` and `context-menu` were two copies of one engine that differed only in how the trigger fires — identical from `computePosition` down — and neither could nest. Both are now thin wrappers over `components/ui/menu/`, which owns anchoring, a pure page-stack navigation model, and one presentation fork: flyout on pointer, pushed pages in a sheet on compact. All 31 call sites are unchanged. Items got a rounded inset fill, a visible separator, and `selected` (a check) split from `active` (a fill). Sidebar display preferences. The flat wall of four sections becomes one row per decision with its value, and per-feature toggles over what a workspace row shows: host, pull request, checks, scripts. Each is independent — a ticked toggle that draws nothing is a lie. `display-preferences/` hides that grouping lives in zustand while title source and row items are app settings. Timestamps. "Last activity" now ticks itself. The label rounds to the minute and never shows seconds; past a week it becomes a date. The timer lives in the leaf `<Text>`, at most one per resolution tier shared by every row, and a tick only re-renders when the string actually changed — so a memoized row that legitimately never re-renders still stays current. Colour. Full status tier regenerated at 90% of gamut max (was 85), same rule as the muted tier, lightness and hue untouched. File icons are pulled toward neutral in OKLab behind `getFileIconSvg` — material-icon-theme picks its palette for a tree where the icon is the only colour on the row, and ours is not. New/deleted files use the forge square-plus/square-minus glyphs at muted weight instead of tinted pills. Project icons round a little more; uploaded icons never do. The project status badge is a knockout again: it fills with the colour of the row behind it, so the ring reads as a gap in the icon rather than a white halo. File rows. The always-visible kebab is gone from the file explorer and diff tree in favour of right-click, which also reclaims the reserved action slot. |
||
|
|
5c7623997c |
feat(hub): pass execution MCP servers to agents (#2813)
* 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 |
||
|
|
04fe30a3e2 |
Improve mobile terminal input, selection, and resizing (#1607)
* 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. |
||
|
|
2e188787ff |
Limit daemon-wide Git process pressure (#2797)
* 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 |
||
|
|
ccb668b3b4 |
Improve sidebar workspace readability and host identity (#2790)
* fix(app): align turn footer actions Keep running-turn actions in the same position as completed-turn actions and use the shared small icon size for both copy and fork controls. * feat(app): improve sidebar workspace identity Make dense sidebar workspace rows easier to scan and let each device control how connected hosts are identified. * fix(app): keep host badges readable Use a compact display menu and keep identity color on the host icon so translated controls fit and badge text retains standard contrast. * fix(app): announce selected sidebar badge mode * fix(app): retain icon-only host names for screen readers * fix(app): name hoisted workspace rows * fix(app): retain host identity in workspace labels * fix(app): retain workspace status in row labels * fix(app): defer host appearance until identity resolves * fix(app): serialize host appearance writes * fix(app): serialize host registry writes * fix(app): bound sidebar host badge labels * test(app): match workspace status labels * fix(app): consolidate sidebar identity behavior * test(app): assert status grouping behavior * fix(app): keep sidebar row actions accessible * fix(app): stabilize optimistic message layout |
||
|
|
f888bae04d |
Keep workspaces active while native subagents run (#2777)
* 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> |
||
|
|
048b82f2a7 |
Preserve interactive ACP permission choices (#2762)
* 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 |
||
|
|
5a30affd7f |
Skip unrelated required checks without losing their names (#2542)
* 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 |
||
|
|
76abc50f82 |
Fix/local branch worktree base (#2328)
* 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> |
||
|
|
b0a5c09ace |
Add shared auto-accept controls to ACP sessions (#2752)
* 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 |
||
|
|
47c18a9213 |
Fork an agent session mid-run from the in-flight turn footer (#2638)
* feat(app): fork an agent session mid-run from the tab menu
Forking already existed, but only on the footer of a *completed*
assistant turn. While an agent is running, the in-flight turn renders
RunningTurnFooter with no fork menu, so exploring another path meant
scrolling back to hunt for an older turn's footer — or waiting for the
run to finish.
Add two entries to the agent tab context menu ("Fork chat in a new tab",
"Fork chat in a new workspace"), reachable regardless of run state. They
omit the fork boundary, and selectForkContextRows projects the whole
timeline when no boundary is given, so a mid-run fork carries everything
up to now — including the partially streamed in-flight turn.
Fork logic moves out of agent-stream/view.tsx into a shared useForkAgent
hook so the turn footer and the tab menu share one code path; the
per-turn behavior is unchanged (it still pins a boundary to its turn).
No server, protocol, or capability changes — this reuses the existing
agent.fork_context RPC and agentForkContext host feature. The daemon
handler is already a pure in-memory timeline read, so it was safe
mid-turn before this change.
* fix(app): resolve missing agent records before failing a fork
handleForkAgent read the agent record from the session store and gave up
if it was absent from both agents and agentDetails. A persisted agent tab
can outlive its record — restored on startup before hydration — so both
fork entries failed with a generic toast even though the agent exists on
the daemon and is perfectly forkable.
Fall back to client.fetchAgent and store the result before failing,
mirroring the resolution path agent-panel.tsx already uses. Only report
forkFailed when the agent genuinely cannot be resolved; a disconnected
host now says so instead.
The resolution logic lands in resolveForkAgentSource with injected
dependencies, since workspace-screen.tsx has no handler-level test
harness and a sibling testable module is the pattern in this directory.
storeFetchedAgentDetail moves out of panels/agent-panel.tsx into
utils/agent-detail-store.ts so the workspace screen doesn't import a
panel module — and into its own file rather than agent-directory-sync.ts,
which would close an import cycle through legacy-daemon-workspaces.
* feat(app): fork from the in-flight turn instead of the tab menu
Move the mid-run fork affordance to where the fork button already lives:
the assistant turn footer, beside the progress loader.
RunningTurnFooter previously showed only a spinner and elapsed timer, so
the fork button vanished for the whole duration of a run. It now renders
AssistantForkMenu alongside them, reachable the moment a run starts.
The in-flight fork passes no boundary, so selectForkContextRows projects
the whole timeline and the fork captures the response being streamed. A
fork button sitting next to a live response that silently dropped that
response would be worse than no button. Completed turns still pin their
own boundary, enforced by a separate handler type that keeps boundary
required.
layout.ts is untouched — the affordance is added to the running footer
component rather than by lifting the footer suppression, so no copy or
duration affordance leaks onto an incomplete turn.
Reverts the workspace tab context menu approach, along with the agent
resolution helper and store-writer extraction that existed only to serve
it. The footer path already holds the agent record.
* test(app): cover the in-flight turn fork with a Playwright spec
Extends the existing assistant fork menu spec rather than adding a
sibling file, so the completed-turn and in-flight cases sit side by side.
The new test pins the behavior that defines the feature: the in-flight
fork omits the boundary fields entirely, while the completed-turn fork
sends one. Both assertions read the actual agent.fork_context frames off
the WebSocket, using the read-only page.on("websocket") idiom from
helpers/timeline-delivery.ts so the observer cannot perturb the flow it
measures. Note the request omits the fields rather than nulling them —
daemon-client spreads them conditionally — so absence is what's asserted
there, and null only on the response.
Also asserts the fork trigger is scoped inside turn-working-indicator,
that no copy or duration affordance leaks onto the incomplete turn, that
the attachment carries the streamed text, and that the source agent keeps
streaming afterwards.
* test(app): cover the in-flight fork call site, gate, and teardown
Three behaviors this PR introduced had no coverage below the Playwright
spec. A copy-paste of resolveAssistantTurnForkBoundary into
handleForkInFlightTurn would have left every unit test green.
view-fork-in-flight.test.tsx mounts the real AgentStreamView with the
render strategy stubbed to the live-auxiliary slot, so handleForkInFlightTurn,
useStableEvent and the readOnly expression all stay real. It asserts the
fork request has no boundary KEY — matching the wire contract, where the
field is absent rather than null — for both targets, and that the read-only
gate withholds the trigger, with positive controls either side so an empty
tree cannot pass for a working gate.
turn-footer.test.tsx covers the race this PR created: the fork menu now
mounts only while a run is in flight, so a fork can be pending when the
subtree is torn down. The behavior is benign — React 18 makes the orphaned
setState a no-op — so the test asserts the observable outcome and leaves a
tripwire for unhandled rejections, since AssistantForkMenu's finally has no
catch.
Each test was verified by mutation: adding boundary: undefined, dropping the
readOnly gate, and keeping the running subtree alive each fail the matching
test.
Scoped to what this PR changed. The relocated hook's error paths, draft
setup construction, and attachment metadata predate it and stay untested
here.
* test(app): replace mocked fork coverage with Playwright
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
|
||
|
|
8021d2b6c9 |
Keep agent timelines stable from submission through resume (#2718)
* 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 |
||
|
|
ef63dd212b |
Keep restored merged workspaces active (#2714)
* 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 |
||
|
|
b151dfb88d | Add Darwin desktop package to flake | ||
|
|
5f575fcdd6 |
fix(dev): use portable Bash shebangs (#2536)
Resolve Bash through PATH so repository development entrypoints run on NixOS and other systems without /bin/bash. |
||
|
|
d2de309fc3 |
Make relay opt-in when pairing devices (#2706)
* 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. |
||
|
|
79fccb368d |
chore(app): ship mobile updates through stores only (#2708)
Paseo does not publish EAS updates, so remove the update runtime and channel metadata from native builds. |
||
|
|
800bd2bef1 |
feat: add host-local custom project icons (#2335)
* 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> |
||
|
|
706ae3d9bb |
Read Claude subagents from the SDK task protocol (#2498)
* 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> |
||
|
|
91da0bac4d |
Choose which orchestration skills get installed (#2680)
* feat(settings): choose which orchestration skills to install The skills row installed the whole bundle or nothing. A settings control next to it now opens a sheet where you keep every bundled skill or pick a subset, and saving converges ~/.agents, ~/.claude, and ~/.codex to it. "All skills" reads the bundle directory instead of a hardcoded catalog, so a skill added in a later release installs itself without another code change. Retired bundle names stay in a separate cleanup list so old copies on disk are still removed but never become selectable. The selection persists under Electron user data, separate from install state, so uninstalling does not erase the choice — a later install brings back exactly the chosen set. Installs with no saved selection read as "all", which is what they already had. * Restore skill selection after failed save * fix(settings): don't save a skill selection that never reached disk A failed save wrote the preference, converged, then wrote the old preference back. That leaves a window where a selection the user was told did not save is the one on disk, and a crash inside it makes the window permanent. Converge first and persist only on success — nothing to roll back, and a failed save now leaves disk and preference exactly as Cancel would. Resolving the bundle path when the command handlers are wired also broke daemon-manager's tests: the packaged branch reads process.resourcesPath, which is undefined outside Electron. Resolve targets per command instead — registering handlers should not touch the environment. * fix(settings): make skills convergence single-writer and confirm removals Startup auto-update and the settings commands were separate writers over the same skill directories. Either could apply a plan computed from a selection the other had already replaced — narrow the selection while startup is repairing drift and the repair puts the removed skill back. There is now one controller that owns scan, apply, and persist, and every entry point queues through it, so concurrent callers see a consistent result instead of an interleaved one. Save also had no recovery once it had converged: if the selection could not be committed afterwards, disk was changed while the preference still said the old thing, and the next startup silently undid the change. Save now puts the managed directories back to what the committed selection describes before it reports the failure, so a failed Save lands where Cancel would. Deselecting a skill deletes its directory in all three agent homes, including files the user put inside it. That is now confirmed like every other destructive action, naming the skills and the directories. Adding skills or saving an unchanged selection still saves without a prompt, and a skill the host has not installed yet deletes nothing so it does not prompt either. * fix(settings): confirm removal for skills installed in only some agent homes The removal confirmation read an `add` op as "not on disk yet". An `add` op means "missing from at least one target", so a skill sitting in ~/.claude but not the other two looked absent, and deselecting it deleted that directory — with anything the user had put inside it — without asking. The status snapshot now carries the installed skill names directly, taken from union presence across the three agent homes, and the confirmation reads that. `ops` keeps its one meaning. A host that predates the field cannot say what is on disk, so the app bridge assumes the saved selection is installed. Asking about a directory that turns out to be absent is harmless; the reverse is not. * fix(settings): warn about every skill directory a save deletes The confirmation asked "was this selected before and is it selected now", which is a different question from the one the host answers. The host converges to the desired set and deletes every managed directory outside it, so a skill on disk that the saved preference never mentioned — reinstalled by hand, restored by a sync, left behind by drift — was deleted with no warning, user files included. The predicate now mirrors the host rule: everything installed that the draft will not keep, regardless of what was saved before. It moved out of the sheet into its own module so the rule has one owner and can be tested directly. Saving with nothing to delete still does not prompt. * fix(settings): a failed save keeps files the user added inside a skill Save converges before it commits, and convergence deletes whole skill directories. When the commit then failed, the recovery re-installed the committed selection — which rebuilds the bundled files and nothing else, so notes, hooks, and anything else the user had put inside a deselected skill were gone for good. Cancel does not do that, and a failed save is supposed to land where Cancel would. Save now runs as a transaction. Before converging it stages a copy of every managed skill directory across the three agent homes, next to the tree so the copy stays on one filesystem. Committing drops the staging; failing anywhere puts each directory back exactly as it was, deletes the ones the candidate had created, and then drops the staging. Staging left by a process that died mid-save is cleaned up by the next one. The controller only sees begin/commit/rollback; the filesystem work lives in skills-transaction.ts. * fix(settings): make skill removal confirmation authoritative * fix(settings): verify skill convergence before commit * fix(settings): preserve concurrent skill edits on rollback * fix(settings): preserve staged skill metadata * fix(settings): stage skill deletions atomically * fix(settings): stage removals per filesystem * fix(settings): restore staged skill symlinks * fix(settings): canonicalize skill transaction roots * fix(settings): keep automatic skill updates non-destructive * fix(settings): report committed skill saves accurately * fix(settings): keep skill updates non-destructive * fix(settings): route skill removals through selection * fix(settings): close skill selection edge cases * fix(settings): preserve skill data during maintenance * fix(settings): hide unavailable skill install * fix(desktop): keep install policy at command boundary * fix(desktop): restore staged skill directories atomically * test(app): retry transient skills sandbox cleanup * fix(desktop): recover externally deleted skill stages * fix(desktop): preserve conflicting skill rollback data * fix(desktop): generalize skill rollback conflicts * fix(desktop): preserve skill rollback collisions * fix(desktop): recover quarantined skill backups * fix(app): inset adaptive sheet content on compact The compact Choose skills sheet rendered edge to edge: the All skills card touched both screen edges, the section label was glued to it, and the skill list ran to the edges too. Desktop was correct. AdaptiveModalSheet expressed its content inset through BottomSheetScrollView.contentContainerStyle. That is a library prop, not the style prop Unistyles registers, and gorhom's scroller was never rewritten by the Babel plugin, so on web the emitted class was never applied and the padding and gap resolved to nothing. The desktop card uses a real ScrollView, which Unistyles does track, which is why only compact was affected. The inset now lives on a View the sheet owns, rendered by every presentation, so one definition covers compact and desktop and a scroller only ever gets theme-free layout. The caller-facing prop is contentStyle, since it no longer lands on a scroll content container. Choose skills drops its ad-hoc label for SettingsSection, so the label carries the app's section rhythm instead of sitting equidistant between two cards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(desktop): prevent skill rollback data loss Preserve externally replaced directory trees during recovery and reject managed child symlinks before synchronization can write outside a skill directory. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f3fee73976 |
Stop OpenCode cancels from killing the next turn (#2662)
* 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. |
||
|
|
588fdc794b |
docs: rewrite contributing guide, add QA and compatibility docs
Issues are bug-only now. The feature-request template is gone, requests opened as issues get closed automatically, and feature talk moves to product discussions on GitHub Discussions and Discord. "Product discussion" is the term everywhere, recorded in the glossary. Contributing states the QA evidence bar and links out instead of teaching; docs/qa.md carries the depth. The protocol back-compat rules moved out of CLAUDE.md into docs/protocol-compatibility.md, since that constraint is invisible in development where both sides always run the same version. CLAUDE.md gains doc-writing and voice rules to keep docs from accumulating appended paragraphs. design.md gains an alignment section (glyph rails) and a layout-shift rule. |
||
|
|
c6df5ffd4d |
Revert "Carry local files and shared state into new worktrees (#2419)"
This reverts commit
|
||
|
|
20e46e3e3f |
Restore project grouping across hosts (#2565)
* 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. |
||
|
|
acb8506d1f |
Revert the timeline optimistic and pagination rework (#2596)
* 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. |
||
|
|
fab975a059 |
Keep idle agents and their background work alive (#2590)
* 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 |
||
|
|
504b687f89 |
Keep older chat history and image previews stable (#2490)
* 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 |
||
|
|
cbbf6c1684 |
Carry local files and shared state into new worktrees (#2419)
* feat(server): support symlink worktree includes * refactor(server): simplify worktree include handling * fix(server): constrain worktree include traversal * fix(server): clean up failed worktree branches * fix(server): skip missing worktree include entries * fix(server): make worktree includes best effort * fix(server): skip failed worktree includes * fix(server): harden worktree include planning * fix(server): preserve reused worktree result shape Materialization reports describe one creation attempt, not durable worktree identity. Carry them beside the worktree so newly created and reused results keep the same stable shape. * fix(server): harden worktree include boundaries Replace directory snapshots exactly, keep canonical Git metadata protected, report recovery skips, and only roll back branches owned before worktree creation. * fix(server): follow safe include aliases Traverse canonical in-checkout directory links during glob planning and treat coded revalidation failures as per-entry skips. * fix(server): make include preflight race safe Create fetched checkout refs atomically, retain overlapping copy entries for independent fallback, and protect the full managed-worktree base. * fix(server): preserve staged recovery state Validate completed directory snapshots, retain backups after failed restoration, and derive atomic ref guards from the repository object ID width. * fix(server): preserve partial include progress Keep safe glob matches, enforce recursive-directory types, validate staged roots, and retain OID guards through rollback. --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
5bd317205c | fix(omp): expose injected Paseo tools directly (#2418) | ||
|
|
c596e058cd |
fix(dev): make worktree setup run on Windows (#2431)
`worktree.setup` ran two POSIX-only command strings, but lifecycle commands
go through PowerShell on Windows, so worktree creation failed at:
PASEO_DEV_MANAGED_HOME=1 PASEO_DEV_SEED_HOME=... ./scripts/dev-home.sh
-> PASEO_DEV_MANAGED_HOME=1 : The term ... is not recognized
PowerShell has no `VAR=value cmd` prefix syntax. The `cp` entry was broken the
same way: `$PASEO_SOURCE_CHECKOUT_PATH` is an undefined *PowerShell* variable,
not an env var, so it expanded to empty and the copy resolved to
`/packages/server/.env`.
Neither entry can be expressed portably in a single shell string, and `bash` is
not guaranteed on Windows, so move both steps into a Node script that reads its
inputs from `process.env` — matching the existing
`node ./scripts/seed-ios-native-cache.mjs` entry. One code path, no platform
branching.
`scripts/dev-home.sh` is unchanged and still sourced by the bash service
scripts; only the setup-time seeding is ported.
One behavior change: a missing `packages/server/.env` in the source checkout is
now skipped with a log instead of aborting setup. It is untracked local config,
and the old `cp` hard-failed worktree creation for anyone without one.
Co-authored-by: ABorakati <ABorakati@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
fa1198c2be |
Stop completed turns from appearing stuck (#2484)
* fix(app): stop completed turns from appearing stuck Turn activity was inferred from a user-message row flag, so a stale duplicate row could keep the working footer active after the turn ended. Track submission lifecycle separately and route every send path through the shared submission flow. * fix(app): guard pending message identity * test(app): measure submission layout independent of scroll * fix(app): keep submitted messages consistent through reconnects Track each in-flight send until its own RPC establishes acceptance, and keep canonical timeline placement authoritative. Restore legacy cached rewind IDs during cache deserialization. * fix(app): close canonical submission races * test(app): cover canonical submission races in browser * fix(app): keep replacement submissions authoritative Keep current pending rows out of legacy cache migration and leave ambiguous terminal lifecycle events to the daemon snapshot. This prevents fabricated rewind identities and stale completion events from marking replacement turns idle. * fix(app): keep submitted messages stable across sync Give submission transport and canonical timeline ingestion separate authority. Preserve every unresolved local send across replacement, reconcile provider identity once, and bridge RPC acceptance to authoritative running state without deriving lifecycle from timeline rows. * fix(app): settle submissions in either acknowledgement order Complete submission transactions when RPC and provider acknowledgement arrive in either order. Cache only transaction-owned local rows as transient data, preserve canonical ID-less prompts, and invalidate ambiguous legacy display caches instead of inventing provider identity. * fix(app): keep agent visible during history handoff Running and terminal updates could clear create continuity before the initial authoritative timeline arrived, leaving a streaming agent behind the loading screen. End the handoff only when authoritative history is applied. * fix(app): settle attachment-only submissions Canonical providers can acknowledge image-only prompts with empty text. Reconcile those events by client identity without rendering a blank canonical row. * fix(agent): settle out-of-band message submissions Accepted commands that do not allocate a foreground turn previously had no canonical user acknowledgement. Record the command before its handler runs so submission state and reconnect history converge through the normal timeline producer. * fix(app): settle out-of-band submissions compatibly * fix(app): preserve canonical prompt order * test(app): keep workspace status check timing-independent The workspace-status scenario asserted footer settlement before initial agent creation had necessarily completed. The dedicated draft-handoff coverage owns that lifecycle contract. |
||
|
|
2acb10fce9 |
Keep provider settings above the model selector (#2476)
* fix(app): keep provider settings above model selector Desktop web comboboxes used the browser top layer, so ordinary modal portals could not cover them. Keep web overlays in one relative layer stack while preserving native bottom-sheet stacking. * fix(app): route overlay keyboard ownership * fix(app): preserve nested overlay ownership * fix(app): route command center overlay input * fix(app): preserve global overlay ancestry * fix(app): register global dialog hosts |
||
|
|
e1b1ca569d |
Keep large file views from disconnecting (#2482)
* fix(files): keep large downloads connected Large file reads were emitted as one frame, crossing the physical socket high-water mark and terminating otherwise healthy connections. Stream bounded chunks from one file handle and pace each transfer by its own send completion without globally queueing unrelated traffic. * fix(files): keep paced relay transfers bounded Carry send completion through encryption to the physical WebSocket callback, cap growing files at their advertised size, and preserve text classification when a UTF-8 code point crosses the sample boundary. * fix(files): reject changing transfer snapshots Abort when a file shrinks below its advertised size, finalize UTF-8 validation for complete samples, and let the physical relay socket remain the sole authority for queued-byte accounting. * fix(files): validate metadata before transfer Keep the daemon handshake pending until the ready frame is physically sent, and classify file content with a bounded full-handle scan so streaming preserves the previous binary/text behavior. * fix(files): detect changing transfer sources * fix(files): enforce transfer snapshot integrity |
||
|
|
80c8a08393 |
Keep parent agents alive while child work runs (#2458)
* fix(server): keep active agent trees resident Idle collection treated an inactive parent turn as process quiescence even when descendant work was still running. Protect managed and provider-owned subagent trees, carry descendant activity into the idle window, and use a conservative 30-minute fallback. * fix(server): close idle collection races Retain managed ancestry across closed intermediates, refresh descendant state after each awaited close, and invalidate running provider subagents when their runtime is terminated. * fix(server): serialize agent tree collection Keep ancestors resident until managed descendants are collected, serialize descendant registration with idle collection, and preserve provider subagent state across hot reloads. * fix(server): retain closed descendant activity Carry persisted descendant activity through runtime closure, avoid error children pinning parents, and cancel stale provider children when reload replacement aborts. * test(server): make descendant expiry deterministic * fix(server): scope idle child protection * fix(server): tighten idle tree coordination * fix(server): reduce aggressive idle cleanup Keep the mitigation conservative: extend the idle timeout without inferring tree liveness from incomplete lifecycle signals. * fix(server): keep parents alive during child work Idle cleanup now respects running managed and provider-native children while retaining a conservative 30-minute fallback. * fix(server): clear running child state on close * fix(server): drain child events before close |
||
|
|
1d1132de9c |
perf(relay): reduce encrypted binary traffic overhead (#2480)
Preserve application frame identity through encryption so negotiated binary traffic avoids base64 expansion while mixed-version peers remain compatible. |
||
|
|
392095c1b2 |
feat(desktop): stop the daemon when you quit the app (#2454)
Quitting the desktop app now shuts down the daemon it started, so "restart the app" is a complete reset users can act on without first learning what a daemon is. The old default kept it alive; the toggle under Settings > Host still opts back in. Existing installs already persisted `keepRunningAfterQuit: true` from the old default, so a new default alone would only reach fresh installs. A one-time migration resets it, and an explicit toggle afterwards persists the migration flag and is never overridden again. Only a desktop-managed daemon is stopped — one started with `paseo daemon start` is left alone. |
||
|
|
fe28850fad |
docs(relay): point readers to the official service
Remove the maintainer callout and star history chart from the README while keeping translated copies aligned. |