* 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(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
* feat(app): dismiss the chat keyboard on a fast upward flick
Scrolling the history to read earlier messages left the keyboard up, so the
visible transcript stayed cramped and the keyboard had to be closed by hand
first — two steps for what should be one.
React Native's keyboardDismissMode cannot express the wanted behaviour:
"on-drag" fires on the first pixel and kills the keyboard on any peek-scroll,
and "interactive" is broken on inverted lists. So the gesture is measured
here: samples are taken from the scroll events and, at release, the speed over
the drag's final stretch decides. Measuring at release rather than averaging
the whole drag is what keeps a fast but controlled read-scroll from counting
as a flick, since such a gesture decelerates before the finger lifts.
Timestamps and offsets come from the events, never from a clock: with a busy
JS thread the callbacks arrive in a burst long after the gesture, and
wall-clock spacing then reads a calm scroll as a flick. The release offset
comes from the end-drag event for the same reason — the last onScroll can be
stale by the time a short flick lands.
Android needs both the blur and the dismiss. Dismissing alone leaves the
input focused and the keyboard inset applied, so the layout stays shifted
with an empty gap where the keyboard was; blurring alone releases focus but
leaves the IME on screen.
Verified on an Android device with a Release build: a slow drag and a
0.6 dp/ms scroll keep the keyboard, a 2.9 dp/ms flick dismisses it and the
composer settles back with no leftover gap. iOS was exercised by hand on
device only, without automated coverage of the gesture itself.
* refactor(app): isolate keyboard flick dismissal
* fix(app): isolate keyboard shift context
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* 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.
* fix(chat): load older timeline history consistently
Pagination depended on scroll events, so short or compacted initial pages could never request older history. Reevaluate edge visibility as layout and history change, and standardize loading indicators on the canonical app spinner.
* fix(chat): keep older history loading reliably
Use authoritative timeline cursor progress so merged rows cannot stall pagination. Wait for bottom anchoring before evaluating the history edge, and re-arm retries on a fresh upward gesture without automatic failure loops.
Render optimistic turn feedback before host acknowledgement and roll it back on rejection. Reconcile iOS keyboard offsets from the native transition end event so JS contention cannot leave the composer displaced.
* fix(app): refine compact composer controls and native scrolling
Consolidate responsive agent controls and model browsing inside the compact sheet. Preserve manual native scrolling by suspending sticky bottom maintenance while the user owns the viewport.
* fix(app): suppress recoverable resume refresh errors
Resume revalidation can race host reconnection and reject while the host is offline. Treat the refresh as deferred so development LogBox does not surface a recoverable disconnect.
* fix(app): stabilize compact model sheet loading
Let the model list consume the sheet space above persistent controls as the sheet expands. Keep unresolved defaults in a loading state so Select model only represents a genuinely empty selection.
* fix(app): preserve route anchors during native scroll
* fix(app): preserve native scroll intent and sheet dismissal
* fix(app): keep compact sheet styles composable
Open the complete working comparison in a desktop workspace tab while preserving inline sidebar diffs when the tab is closed.
Working and commit comparisons share one diff panel, and workspace layout is the sole owner of live tab persistence and identity.
Refs #1520
The inverted list's native position maintenance fought the sticky-bottom controller as the live message grew. Let the controller exclusively own the Android sticky-bottom anchor while preserving position maintenance when reading history.
Module-level style composites could materialize the temporary adaptive theme before persisted settings loaded. Keep Unistyles reads in render and guard against eager module-scope access.
* fix(chat): allow failed turns to be forked
Failed turns can end in Paseo-generated timeline items without provider
message IDs. Anchor forks to canonical timeline positions while retaining
legacy boundaries for compatibility.
Place the fork summary before the new prompt and delimit it as structured
chat history.
* fix(chat): tighten fork boundary handling
* feat(app): refine tool call summaries and scrolling
Keep summary groups stable from the first tool call, preserve live activity feedback, and scroll expanded groups to the latest entry. Consolidate web scrollbar styling around the native browser mechanism.
* refactor(app): reuse tool call height constant
* fix(app): preserve tool call summary state
* fix(app): refresh web tool groups on expand
* fix(app): preserve grouped tool errors
* fix(app): focus explicitly opened workspace tabs
Explicit tab navigation could be overwritten by attention-aware workspace
navigation when the visible agent had just finished. Make workspace navigation
own both normal attention selection and explicit targets so callers cannot
sequence them inconsistently.
* test(app): strengthen forked tab focus assertion
* fix(app): keep stream controls clickable under scroll button
The full-width animated overlay won browser hit testing even outside the circular control. Keep positioning on a pass-through container and animate only the button-sized child.
* test(app): preserve scroll hit-test diagnostics
* test(app): align scroll hit-test fixture deterministically
* test(server): include OpenCode message IDs in subagent assertions
* fix(app): preserve loader behavior in retained panels
Keep the running footer mounted across panel switches. Inactive loaders stop their animation work, then realign to the original wall-clock phase before they become visible.
* perf(app): remove inactive loader wrapper worklet
* perf(app): pause hidden turn elapsed timers
* perf(app): reduce loader animation work
Keep one wall-clock-aligned clock for visible loaders and detach retained
hidden loaders without unmounting them. Preserve the 950 ms sequence and
reduced-motion behavior.
Plain chat views started the history virtualizer even without virtualized rows. Its delayed scroll callback could outlive the mounted view and make the app test job fail during environment teardown.
* fix(app): stabilize streamed chat rendering
Use deterministic Markdown AST keys and preserve unique row identities when an assistant message resumes after a tool. Keep native gesture and plan-card children correctly keyed to avoid unsupported Fabric reconciliation paths.
* fix(app): stabilize retained native panels
Keep retained workspace roots in a stable native order and make active
panels present in the selection commit. Hidden panels now stop expensive
subscriptions and animations without losing mounted state.
* fix(app): harden retained panel activity
* fix(app): place turn footers after trailing tools
Completed turn footers now render on the last visible item before the next user message while still using the latest assistant message for footer content and timing.
* test(app): cover split turn footer placement
* fix(client): wait longer for session responses
* fix(client): default RPC waits to sixty seconds
* fix(app): preserve detached stream scroll on delayed history
Code drift: longer client RPC waits let delayed timeline responses arrive after a user scroll-away, so web stream anchoring must not reattach on transient scroll-top resets.
Restore the 15s connect deadline and leave app initialization slack above the default 60s session RPC wait.
* fix(client): keep helper waits within caller deadlines
Review fix: the 60s default session RPC wait leaked into wait previews and waitForAgentUpsert helper fetches. Bound those helper RPCs to the caller deadline or a short best-effort preview timeout, and allow small scroll ranges to reattach at bottom.
* fix(client): respect caller timeout budgets
* fix(cli): keep diagnostic probes responsive
* Refactor daemon client request options
* Preserve daemon client legacy overloads
Multiple workspaces can share one checkout directory. Agent and terminal
status, sidebar tabs, and ownership now attribute strictly by workspace ID,
so a running or blocked agent in one workspace no longer shows up on its
same-directory siblings.
A one-time startup migration backfills workspace IDs onto legacy agents, and
every agent and terminal is created with one thereafter. Directory lookups
remain only for genuinely folder-derived git facts (branch, diff) and the
archive-by-path adapter.
* Make workspace IDs opaque, independent of the filesystem path
Workspace IDs were the resolved checkout/worktree path, so code could
treat an ID as a path: prefix matching, deriving directories from it,
falling back to a path when a lookup missed. IDs are now opaque -
compared only by exact equality and never used as a path. Anything
path-shaped resolves the workspace record first and reads its cwd.
New workspaces get a generated `wks_` ID; existing path-shaped IDs are
read from disk and never regenerated, so there is no migration and no
change to the wire or persisted schemas. Groundwork for running
multiple workspaces in a single directory.
* Keep attachment scope key stable and align refetch test
The attachment scope key keeps `workspace=` instead of `wsid=` so existing
persisted drafts are not orphaned; the rename was cosmetic. The SDK refetch
test no longer asserts a filter, matching refetch fetching one page and
selecting by id client-side.
* Fix live agent updates in directories without a registered workspace
The opaque-ID work removed buildProjectPlacementForCwd's directory
fallback, so an agent running in a folder with no registered workspace
(e.g. a fresh non-git dir) produced a null placement. forwardAgentUpdate
then threw "Workspace not found", the error was swallowed by its catch,
and no agent_update was emitted — live model/thinking switches and status
updates silently stopped. Caught by the live-preferences e2e suite.
The fallback builds a directory-scoped project placement keyed by the
path. That key is a project grouping key (non-git projects group by
path), not a workspace id, so it stays within the opaque-id rule.
* Always run server worktree archive, even without a resolved workspace
Archiving bailed out entirely when the workspace was not found in the
client store, so a race or stale state could make "archive" do nothing
server-side with only a console.warn. The server archive is keyed by
worktreePath, which is always available, so it now runs regardless; only
the optimistic client-side updates (keyed by workspace id) are gated on
the workspace being resolved.
* Expect a directory-scoped placement for unregistered agent dirs
This unit test asserted the no-placement behavior reverted in the live
agent-update fix, which had broken live model/thinking switching. Update
it to expect the directory-scoped placement now emitted for an agent in a
directory with no registered workspace.
* Fix opaque workspace routing in app E2E
* Preserve workspace IDs during partial bootstrap
* Fix archive flows for opaque workspace IDs
* Stop treating opaque workspace IDs as filesystem paths
Workspace IDs are opaque (wks_<hex>), but several call sites still passed
the id where a directory was expected, which broke those flows for opaque
IDs. The branch switcher sent the id as a cwd to branch/stash/checkout git
operations, and server archive/reconcile cleanup keyed git-watch and
subscription teardown by id, leaking that state.
Git and filesystem operations now take the workspace directory; the opaque
id is used only for identity and cache keys. Archive/reconcile cleanup
routes through a single teardownArchivedWorkspace helper that keeps the key
split explicit: runtime store by id, git watch and subscription by cwd.
Path-derived grouping keys are renamed to directory keys, and the helpers
are split into workspace-identity and workspace-directory so the id-vs-path
boundary is obvious.
* Extract client SDK package
* Polish SDK client identity defaults
* Build client before dependent CI jobs
* Restore daemon client server export
* Extract protocol and client SDK packages
* Fix provider override schema validation
* Fix app test daemon client imports
* Simplify workspace build targets
* Fix CLI test server build bootstrap
* Run SDK package tests in CI
* Fix rebase package split drift
* Restore lockfile registry metadata
* Update SDK config test for prompt default
* Move terminal stream router test to client package
* Fix rebase drift for protocol imports
* Fix SDK agent capability fixture
* Restore legacy server client exports
* Fix server export compatibility test
* Advertise custom mode icon client capability
* Remove server daemon-client exports
* Format rebased mode control import
* Fix rebase drift for protocol imports
Files added by upstream PRs (#893, #1147, #1154) referenced the pre-split
shared/ paths that this branch moves into @getpaseo/protocol. Redirect
those imports to the protocol package so typecheck stays green after the
rebase.
* Add Pi extension-launch plumbing for in-process tree primitives
* Add Rewind controls for agent sessions
* Preserve SDK checkpoint env var so Claude file rewind works
The SDK injects CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true when file checkpointing is enabled. The earlier parent-env stripping from c4782ec71c was too broad and removed that child flag along with real parent-session markers.
Keep stripping parent markers, but preserve the checkpoint flag so the SDK rewind path can run directly. With that boundary fixed, the JSONL file-rewind fallback is no longer needed.
* Update OpenCode history replay tests for rewind state
* Show Rewind actions in an adaptive sheet
* Fix rewind semantics across providers
* Refactor Pi IDs through extension capture
* Restore user message to composer after rewind
* Fix Claude rewind tool result mapping
* Fix Claude rewind session switch roundtrip
* Fix rewind and optimistic message regressions
* Tighten rewind test discipline
* Fix OpenCode optimistic message reconciliation
* Reconcile optimistic user messages by marker
* Reorder user message actions
* Unify provider user message contract
* Add Claude rewind flow Playwright contract
* Add Codex rewind flow Playwright contract
* Add OpenCode rewind flow Playwright contract
* Add Pi rewind flow Playwright contract
* Remove real-provider rewind Playwright contracts
* Gate real-provider rewind specs from CI Playwright