* fix(projects): detect when projects become Git repositories
Project identity was coupled to Git placement, while non-Git roots were dropped by session-scoped observation. Keep identity tied to the selected root and observe Git transitions daemon-wide so empty projects update without rehoming workspaces.
* fix(projects): preserve metadata across Git read failures
Refresh archived workspace facts before persistence and treat only confirmed non-repositories as non-Git so transient failures cannot rewrite stored project metadata.
* fix(projects): close project update races
* fix(projects): refresh checkout metadata when reopening folders
Persist worktree ownership separately from workspace kind and gate exact-root project creation on stable host identity. Refresh active and archived records so missed Git transitions cannot return stale checkout descriptors.
* test(projects): accept Windows aliases for repaired roots
* test(server): retry transient Windows cleanup locks
* fix(projects): propagate project metadata refreshes
Project-only Git transitions now fan out workspace updates for legacy clients. Explicit project creation refreshes stored kind, and equivalent cwd spellings reuse existing workspace records.
* fix(projects): refresh worktree source project kind
* fix(projects): preserve exact-folder runtime isolation
* fix(projects): preserve exact cwd across worktree lifecycle
* fix(projects): harden exact-folder worktree flows
* fix(projects): derive exact cwd from matched path identity
* fix(projects): preserve nested worktree lifecycle
* refactor(projects): centralize workspace placement
* test(e2e): verify isolated server ports
* fix(projects): preserve placement reshape compatibility
* fix(projects): validate created worktree placement
* fix(projects): preserve placement through workspace lifecycle
Archive and recovery were rediscovering or guessing checkout roots instead of consuming the persisted workspace placement. Make the record authoritative for exact cwd, backing worktree, and source repository, and classify stale paths before Git reconciliation.
* fix(worktrees): preserve source checkout root
Convert Git's common administrative directory back to the source checkout root when legacy archive placement is reconstructed.
* fix(worktrees): compare filesystem identities
Resolve discovered worktrees and teardown locations through the shared realpath-aware matcher so Windows short and long path spellings cannot split placement identity.
* fix(worktrees): centralize path containment
Route worktree ownership, listing, resolution, and deletion through the realpath-aware containment primitive so Windows path aliases cannot be filtered by an earlier raw prefix check.
* fix(worktrees): remove duplicate ownership discovery
* test(e2e): isolate daemon restart ownership
* fix(worktrees): make lifecycle operations transactional
* fix(workspaces): share git watches by cwd
* test(worktrees): make teardown proof portable
* fix(sync): integrate project updates with directory owner
* fix(workspaces): close reconciliation edge cases
* refactor(sync): remove duplicate project reconciliation
Keep workspace and project deltas behind the host directory transaction, with owner-boundary coverage for snapshot replay and queued full reconciliation.
* fix(sync): buffer project updates before hydration
Start the workspace transaction with the online connection epoch so project broadcasts cannot publish a partial directory before the authoritative snapshot commits.
* refactor(forge): forge-neutral foundation (GitHub-only)
Decouple git-hosting from GitHub behind a neutral abstraction (issue #1616), GitHub-only for now; existing GitHub behaviour is unchanged.
- Forge manifest, neutral ForgeService contract, forge registry + resolver, and a client forge-module registry.
- GitHub code renamed to the neutral shape; PR/Issue attachment wording preserved.
- forge.search.response enums parse tolerantly (unknown kind/auth state degrade instead of breaking the client).
- createPullRequest reports typed CLI/auth errors instead of a generic message.
- forge-resolver host/remote caches are LRU-bounded.
- Forge host trust is explicit: only a known cloud host or a CLI-authenticated host is ever talked to; an unauthenticated GitHub Enterprise host fails resolution instead of routing to github.com.
- Docs: forge-providers guide, glossary and i18n forge-copy conventions, architecture and rpc-namespacing terminology.
- Vitest React Native mocks (unistyles, svg, linking, lucide) consolidated into shared aliased test-stubs.
* feat(forge): GitLab adapter, forge-aware UI, pipelines and approvals
GitLab adapter over the glab CLI on the neutral contracts: MR status, forge-aware UI, pipeline tree, and N-of-M approvals.
- threadIsResolved is part of the neutral timeline item.
- Pipeline load failures show an error instead of an empty section.
- Manual pipeline jobs render as pending.
- Fork/detached MR head pipelines are fetched by MR iid (glab ci get --merge-request).
* feat(forge): Gitea family adapter (Gitea, Forgejo, Codeberg)
One adapter over the tea CLI serving Gitea, Forgejo, and Codeberg on the neutral contracts.
- CI status aggregates commit statuses and Actions runs together.
- Gitea's terminal "warning" state maps to failure on server and client.
- Gitea Actions check details are reachable from the PR pane by workflowRunId.
* refactor(forge): localize compatibility handling
* test(forge): expect normalized GitLab facts
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* feat(app): add fileView preference for changes panel
* feat(app): add buildDiffTree util for changed-files tree
* feat(app): add changed-files tree directory row
* feat(app): changed-files tree view mode in changes panel (#117)
* feat(protocol): add checkout.commits.list RPC + commitsList feature flag
* feat(server): list branch commits ahead of base with on-remote flags
* feat(server): handle checkout.commits.list RPC and advertise capability
* feat(client): checkout.commits.list method + useCommitsQuery hook
* feat(app): per-commit inline view with local-vs-remote markers (#117)
* feat(protocol,server): per-commit file diff RPC (checkout.commits.file_diff)
* feat(app): open per-commit file diff on click (#117)
* refactor: surface baseRef + commits loading/error, drop dead depth field
* fix(app): flip commit local/remote dot — local hollow, remote filled
* feat(app): list/tree view for expanded commit file list (#117)
* fix(app): syntax-highlight per-commit file diff to match Changes view
* feat(app): draggable resize between commits and diff sections
* refactor(app): dedupe wrap-text helpers into diff-highlighted-text
* fix(app): clean up commits resize drag on unmount + a11y label
* fix(app): collapse commits section by default
* fix(app): render per-commit file diff with the shared Changes line renderer
* perf(app): memoize shared diff line row; drop redundant DiffLineView wrapper
* refactor(app): extract shared DiffFileBody; render commit file diff through it
* fix(app): hide inline-comment affordance in per-commit diffs (no reviewActions)
* feat(app): move commits to a resizable bottom drawer in the Changes panel
* feat(review): commitSha scoping for per-commit review drafts + attachment
* feat(app): per-commit inline comments wired through to the composer (#117)
* refactor(app): own commit file-diff open state in CommitFileList (drop reset effect)
* refactor(app): colocate diff-render cluster under git/diff-file-body/
* feat(app): add diff tab target kind (working/commit diff tabs)
* feat(app): useDiffFiles hook unifying working + commit diff targets
* feat(app): diff tab panel rendering working/commit diffs
* feat(app): open a commit diff tab on commit click; drop per-commit drawer/file list
* feat(app): open/scroll working diff tab on changed-file click
* fix(app): keep commit diff tabs ephemeral; align working diff whitespace
* feat(app): collapsible file sections in the diff tab, collapsed by default
* feat(app): make the on-remote commit dot more subtle
Dims the filled green remote dot (row + legend) to ~0.55 opacity so the
local-only ring stays the state that draws the eye.
* Reshape commit diffs around the existing Changes view
* Fix diff tab migration complexity after rebase
* Address diff tab review findings
* Clean up diff tab review interfaces
* Collapse commit diffs into the existing view
* Load commits when expanded
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(worktree): track checkout PR branches correctly
Checkout-PR worktrees could point branch tracking at a PR push remote without fetching a matching remote-tracking ref. Configure same-repo PRs to track origin, make fork PR remotes fetchable before assigning upstream, and push fork PR branches to their configured remote.
* fix(worktree): scope PR push routing to Paseo targets
Review caught that using ordinary upstream config for Push could send normal branches to non-origin remotes and mishandle deduped PR branch names. Store a Paseo-specific push target during PR worktree creation, then have Push honor only that marker before falling back to origin.
* fix(worktree): use git upstream for PR pushes
* fix(worktree): allow PR checkout without head upstream
* fix(worktree): preserve fork PR git push target
* fix(worktree): preserve PR push targets
* fix(worktree): preserve PR push status
* fix(worktree): set upstream after push-only PR push
* fix(server): link PRs for differently named tracked branches
PR lookup now uses the configured tracked head ref when it differs from the local branch, while keeping fork owner scoping only for cross-repo remotes.
* fix(server): avoid extra PR lookup git reads
Use the shared lookup-target builder for an early local-branch result before reading origin and base metadata on on-demand PR status refreshes.
* refactor(server): move checkout write handlers into CheckoutSession
The checkout read side moved into session/checkout/checkout-session.ts in
#1644; the 17 inline write/PR/stash handlers stayed on the Session god-object.
This carves them into the same subsystem behind an expanded CheckoutSessionHost,
so dispatchCheckoutMessage becomes pure delegation and session.ts drops ~800
lines.
Moved into CheckoutSession (verbatim, only this.X -> this.host.X rewiring):
switch/rename branch, commit, merge, merge-from-base, pull, push, PR
create/merge, github auto-merge/check-details, PR status/timeline, github
search, stash save/pop/list, plus resolveCurrentPullRequest and the
PR-timeline helpers.
CheckoutSessionHost gains the Session-owned, non-checkout collaborators these
handlers orchestrate (notifyGitMutation, emitWorkspaceUpdateForCwd,
handleWorkspaceGitBranchSnapshot, renameCurrentBranch, checkoutExistingBranch,
and the LLM commit/PR-text generators); paseoHome/worktreesRoot join the
options bag. GitMutationRefreshReason moves to utils/checkout-git.ts so both
the shell and the subsystem share one canonical type.
session.test.ts checkout tests now drive the handlers through the public
handleMessage boundary instead of reaching into private methods; new mock-free
fake-host tests cover the moved handlers directly in checkout-session.test.ts.
Behavior-preserving: full session.test.ts + checkout-session.test.ts green,
typecheck/lint/format clean.
* refactor(server): extract chat/schedule/loop handlers into ChatScheduleLoopSession
Move the 7 chat/*, 9 schedule/*, and 5 loop/* request handlers, the three
rpc-error emitters (kept separate), and toScheduleSummary out of the Session
god-object into session/chat/chat-schedule-loop-session.ts, mirroring the shipped
CheckoutSession/VoiceSession deep-module-with-narrow-Host pattern. The least-coupled
remaining domain: stateless request/response over chatService/scheduleService/
loopService, reaching the shell only through a narrow ChatScheduleLoopSessionHost
(emit + agent-roster reads + the mention-fanout send). session.ts drops ~590 lines.
The two dispatch switches collapse into one: schedule/* was previously reached only
via the chat dispatcher's default fall-through arm — now all 21 types are explicit
delegation cases, removing that fragility.
Behavior-preserving: a new routing test drives the real Session.handleMessage for all
21 types (guards against a silently-dropped case), green before and after the move; a
collocated subsystem test covers the mention-fanout send seam, the fanout-limit error
code, the self->agent target remap, and the toScheduleSummary runs-stripping.
* refactor(server): extract provider catalog handlers into ProviderCatalogSession
Move the 8 provider-catalog handlers (model/mode/feature listing, providers
snapshot pull/refresh, diagnostic, usage) plus emitProviderDisabledResponse,
getProviderSnapshotEntryForRead, buildDraftAgentSessionConfig, the two mode-icon
downgrade helpers, and the providers_snapshot_update PUSH wiring out of session.ts
into session/provider/provider-catalog-session.ts behind a narrow
ProviderCatalogSessionHost seam — the same deep-module pattern as ChatScheduleLoopSession,
CheckoutSession, and VoiceSession.
The PUSH (start) and every PULL gate provider visibility and downgrade mode icons
through the SAME injected predicates (isProviderVisibleToClient + supportsCustomModeIcons),
both reading appVersion/clientCapabilities live — the COMPAT invariant the shell could
only enforce by code proximity before. dispatchProviderMessage collapses to delegation;
start()/dispose() wire into subscribeToOptionalManagers/cleanup at the same ordinal.
session.ts drops ~372 lines. Behavior-preservation gate (provider routing through the
real Session.handleMessage) green before and after; full session.test.ts 115/115; new
collocated subsystem test covers the PUSH/PULL parity seam, the disabled-provider path,
and the usage/feature error envelopes.
* refactor(server): extract workspace file-access handlers into WorkspaceFilesSession
Move the file-explorer, file-upload, file-transfer-frame, project-icon and
file-download-token handlers out of session.ts into
session/files/workspace-files-session.ts behind a narrow WorkspaceFilesSessionHost
seam (emit/emitBinary/hasBinaryChannel), matching the deep-module-with-Host shape
used by CheckoutSession/ChatScheduleLoopSession/ProviderCatalogSession.
The new module owns the FileUploadStore and is injected the daemon's
DownloadTokenStore; the shell's dispatch cases and the file_transfer binary route
collapse to delegation. The five handlers touch no workspace-git observer,
registry or subscription state, so this is a clean self-contained slice of the
workspace domain. session.ts drops 240 lines (7316 -> 7076).
Behavior-preservation tests (file explorer list/read/binary, download token
success + empty-cwd, project icon, upload round-trip) added to session.test.ts and
run green against the pre- and post-refactor Session; a collocated
workspace-files-session.test.ts exercises the module via a fake host with real
upload/download stores (no module mocks).
* refactor(server): extract agent-config setters into AgentConfigSession
Move the four agent-config setters (set_agent_mode/model/feature/thinking)
out of the session god-object into session/agent-config/, collapsing their
four near-identical try/log/emit envelopes into one applyConfigChange helper
behind a narrow { emit } host plus an AgentConfigOperations port.
Behavior-preserving: the dispatch cases delegate unchanged, setAgentModeCommand
still routes the mode path, and model/feature still emit no notice. Adds the
previously-absent failure-envelope coverage: 8 gate tests in session.test.ts
(success + forced failure per setter, asserting the *_response envelope and the
activity_log-before-response ordering) green before and after the carve, plus a
collocated agent-config-session.test.ts (typed fake operations + fake host, no
module mocks). session.ts drops ~173 lines.
* refactor(server): extract project-config handlers into ProjectConfigSession
Move read/write_project_config handlers, their failure emitters, and the
root-resolution helpers (resolveKnownProjectRootForConfig, canonicalizeConfigRoot,
stripTrailingPathSeparators) out of session.ts into session/project-config/, behind
a narrow { emit } host plus a projectRegistry port — matching the established
session/<domain>/ pattern. Also corrects a misfiling: project-config was dispatched
inside dispatchAgentConfigMessage despite being its own domain.
session.ts drops 155 lines (6903 -> 6748). The handleMessage-driven gate in
session.test.ts (project config RPC authorization) is green before and after; new
collocated project-config-session.test.ts covers the subsystem with a fake host and
fake registry over a real filesystem (no module mocks).
* refactor(server): extract daemon status/pairing handlers into DaemonSession
Move the two daemon.* RPC handlers (daemon.get_status, daemon.get_pairing_offer)
out of session.ts into session/daemon/daemon-session.ts behind a narrow
{ emit } host + injected reads (paseoHome, serverId, daemonVersion,
daemonRuntimeConfig, listProviderAvailability). These were misfiled inside
dispatchAgentConfigMessage despite being daemon- not agent-scoped; the slice
also removes the now-dead serverId/daemonVersion/daemonRuntimeConfig fields and
the getPidLockInfo/generateLocalPairingOffer imports from the god-file, and
collapses the inline daemonRuntimeConfig type into a named DaemonRuntimeConfig.
The daemon-config get/set handlers stay inline: daemonConfigStore is a
listener-bearing, cross-domain hub (read by structured-generation,
websocket-server, bootstrap, archive-if-safe), so moving it would double-own it.
session.ts: 6748 -> 6662 lines. Gate: new session.test.ts daemon round-trips
(status success + listing-rejects fallback, pairing relay-disabled) pass before
and after; new collocated daemon-session.test.ts (fake host + injected reads
over a real temp dir, no module mocks) 4/4. typecheck/lint/format clean.
* Add configurable worktree root
Allow new Paseo worktrees to be created under a custom worktrees.root from config.json while preserving the existing PASEO_HOME/worktrees default. Thread the resolved root through create/list/archive/ownership flows, MCP/session paths, checkout metadata, schema, docs, and targeted tests.
* Address worktree root review feedback
Use the resolved Paseo worktree base when a custom PASEO_HOME is provided, and remove an unused worktreesRoot dependency from worktree list handling.
* Fix checkout worktree path test on Windows
Use platform-native paths in the custom PASEO_HOME regression test so Windows CI compares against the same resolved root shape as production code.
* Clarify optional worktrees config docs
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Thread an optional logger through runGitCommand and the CheckoutContext
helpers so workspace-git-service can emit trace-level traces around each
git invocation. Behavior is unchanged when no logger is supplied.
Auto-namer now probes refs/heads/<name> and appends -2, -3, … (up to 50)
when the desired slug is taken, instead of failing with "branch already
exists" and leaving the worktree on its placeholder name.
Structured-generation waterfall replaces opencode/gpt-5-nano with
opencode/minimax-m2.5-free and opencode/nemotron-3-super-free so users
without nano configured fall through to free models they actually have.
* feat(checkout): merge PR action with real-GitHub e2e test
Adds a `checkoutPrMerge` server RPC backed by `gh pr merge` (squash, merge,
or rebase) and surfaces three new actions in the git-actions menu —
"Squash and merge", "Create a merge commit", "Rebase and merge" — with
squash taking the primary slot when a PR is open and mergeable. Also
renames the existing local merge action to "Merge locally" to disambiguate
it from the new GitHub-side merge.
Adds an end-to-end test that creates a real temporary GitHub repo, opens a
PR via the daemon, polls until mergeable, calls `checkoutPrMerge`, and
verifies the merge landed on `main` — no mocks. The test pre-flights the
`delete_repo` scope so it skips cleanly on tokens that can't tear down,
and fails loud if cleanup ever errors so we don't leak repos.
PR status gains an optional `mergeable` field (`MERGEABLE | CONFLICTING |
UNKNOWN`) populated from `gh pr view --json mergeable`. The schema is
backward compatible — old payloads without the field still parse and
default to `UNKNOWN` via `z.catch`.
* Update PR status snapshot expectations
* refactor: collect git/checkout/PR feature into packages/app/src/git/
Move 21 git/checkout/PR files out of components/, hooks/, stores/, screens/,
utils/ flat-peer slop into a single packages/app/src/git/ module with
shrunk filenames (git-diff-pane.tsx -> diff-pane.tsx, use-git-actions.ts ->
use-actions.ts, etc).
Dedupe: drop the duplicate useGitActionHandlers from diff-pane.tsx — the
shared useGitActions hook is the single source. Drop the
buildGitActionsForPane wrapper. Collapse the merge-PR triple fan-out
(mergePrSquash/Merge/RebaseStatus) into one record keyed by
CheckoutPrMergeMethod. Streamline server-side single-use helpers
(dispatchStash/PullRequestMessage adapters, GitHubPullRequestMergeError
wrapper, legacy worktree test helpers).
Schema-additive only: messages.ts exports CheckoutPrMergeMethod and
PullRequestMergeable types; daemon-client.ts uses the typed alias.
Backward-compatible.
Net delta: 43 files, +219/-1212.
* fix(app): use GitHub icon for all merge-PR menu actions
* test: namespace temp GitHub repos under `paseotmp-` and centralize naming
Old prefixes (`paseo-checkout-ship-`, `paseo-e2e-`, `paseo-checkout-pr-merge-`)
collided with the `paseo-*` namespace of real repos, making bulk cleanup
unsafe. Switch every test that creates real GitHub repos to a single
unmistakable `paseotmp-` prefix, owned by one shared helper per test
boundary so no caller can name a repo outside the namespace.
- New `packages/server/src/server/test-utils/temp-github-repo.ts` exports
`TEMP_GITHUB_REPO_PREFIX` + `createTempGithubRepoName(category)`; both
server e2e tests (checkout-ship, checkout-pr-merge) consume it.
- `packages/app/e2e/helpers/github-fixtures.ts` locks the prefix internally
and now requires callers to pass a `category` instead of an arbitrary
`prefix`. Updated pr-pane and composer-attachments specs accordingly.
* refactor(server,app): lift return types and parse at boundaries (T2 typeaware production sweep)
* fix(server): align acp-agent test assertions with pino call shape
Add oxlint-tsgolint and configure typescript/no-unnecessary-type-assertion
to flag redundant `!` and `as Foo` casts. Type-aware mode is left off by
default to keep `npm run lint` fast; the rule sits configured for when we
turn type-aware on intentionally. Auto-fix removed ~283 redundant casts;
two manual touch-ups: a real tsgolint false positive in split-container.tsx
and a stale ChildProcess import after a double-cast collapsed.
Resolves the case where a remote like `git@github-work:owner/repo.git`
points at github.com via `~/.ssh/config` but Paseo treats it as
non-GitHub.
Adds a centralized GitHub remote resolver that parses standard remotes
directly and resolves SSH aliases via `ssh -G` (with hostname
validation to keep the alias out of ssh's option parser). The resolver
is shared by workspace GitHub feature detection, PR status, PR
creation, and workspace git metadata.
* chore(lint): use Set for LOG_FORMATS membership check
* chore(lint): convert type aliases to interfaces (autofix)
- Remove no-use-before-define rule (conflicts with unistyles ordering)
- Add typescript/consistent-type-definitions: interface
- Run oxlint --fix: 606 type->interface conversions across 268 files
Typecheck green. Warnings: 5432 -> 3787 (-1645).
* chore(lint): clean up relay package warnings
* chore(lint): clean up cli package warnings
* chore(lint): flatten nested ternaries in server (no-nested-ternary)
* chore(lint): escape entities and hoist inline objects in website
* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)
* fix(types): restore Record assignability after type->interface autofix
* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)
* chore(lint): clean up desktop and highlight packages
* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)
* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)
* chore(lint): format keyboard-shortcuts-section
* chore(lint): rename shadowed bindings in server (no-shadow)
Rename inner bindings that shadow outer imports or function params:
- Promise executor `resolve` -> `resolvePromise` (shadowed path `resolve`)
- Method params `options` -> `input`/`target`/`update`/`runOptions`/`opts`/`killOptions`
- Misc loop/destructure renames for `workspaceId`, `scriptNames`, `path`, `query`, `taskNotificationItem`
Mechanical change only; no behavior change.
* fix(types): add index signatures for Record assignability
* chore(lint): extract nested callbacks in server (max-nested-callbacks)
* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)
Work in progress: 30 of 369 warnings fixed across 23 files.
* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)
* chore(lint): reduce cyclomatic complexity in server
* chore(lint): hoist inline callbacks in app components (jsx-no-new-function-as-prop)
* chore(lint): parallelize safe await-in-loop in server
* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)
Finish eliminating jsx-no-new-function-as-prop warnings in
agent-status-bar.tsx and complete refactor of git-diff-pane.tsx
by extracting per-item components and using stable useCallback
handlers.
* chore(lint): parallelize more safe await-in-loop in server
* fix(server): restore microtask ordering in message dispatch and stream event handler
The complexity refactor added async dispatcher chains that inserted extra
microtasks before message handlers ran, and wrapped the stream event switch
in an await that fired between emitState and dispatchStream. Two tests
regressed on both counts. Route to the matching dispatcher synchronously
and skip the await when the stream handler has no async work.
* chore(lint): hoist/memoize inline arrays and objects in app JSX
* chore(lint): parallelize safe awaits in server (no-await-in-loop)
Converts three sequential `for ... await` loops to `Promise.all`:
- OpenCode: configure MCP servers in parallel
- Sherpa: download model files and ensure multiple models concurrently
- Speech runtime: check required model files across models in parallel
* chore(lint): clear website warnings to zero
Resolves all 61 oxlint warnings in packages/website/ by hoisting inline
callbacks/JSX, extracting sub-components to satisfy jsx-max-depth, using
stable data-derived keys, adding explicit button types, flattening nested
ternaries, and removing unused code.
* chore(lint): parallelize more safe awaits in server (no-await-in-loop)
- Workspace reconciliation: archive missing workspaces and orphaned
projects concurrently instead of sequentially.
- Directory suggestions: resolve child directory candidates in parallel
before filtering.
* chore(lint): allow css imports in import/no-unassigned-import, hoist host page styles
* chore(lint): hoist dictation-controls inline arrays
* fix(server): restore microtask ordering in session message dispatch
Converts per-group dispatchers to Promise<void> | undefined. The complexity
refactor broke two tests by inserting extra microtasks between each
dispatcher's await. The new pattern routes synchronously via a nullish-
coalescing chain and awaits only the matching dispatcher's promise.
* chore(lint): flatten nested ternaries in button/volume-meter/autocomplete
* chore(lint): narrow explicit any in misc server files
* chore(lint): flatten nested ternaries in host-runtime/host-page/providers
* chore(lint): clear desktop warnings to zero
Convert polling loops to recursive helpers to avoid no-await-in-loop,
and switch the ws import to the named export so the WebSocket type is
referenced directly.
* chore(lint): remove useless constructors in server tests
* chore(lint): hoist pair-scan inline arrays and objects
Extracts corner style arrays and barcode scanner settings to module
scope, memoizes insets-dependent body/helper text styles.
* chore(lint): hoist inline style arrays in question-form-card
* chore(lint): hoist/memoize inline styles in plan-card
Extracts markdown rule style arrays into dedicated MarkdownInlineText,
MarkdownListItemContent, and MarkdownParagraph subcomponents that
memoize their own style arrays. Memoizes PlanCard container/title/
description styles.
* chore(lint): narrow explicit any in server relay/loader/logger-likes
* chore(lint): hoist SheetBackground combined style
* chore(lint): hoist inline styles in screen-header
* chore(lint): hoist inline styles/objects in menu-header
* chore(lint): hoist top-level inline arrays in diff-viewer
* chore(lint): narrow explicit any in codex-app-server-agent thread items
* chore(lint): reduce max-depth in claude-sdk-behavior test
* chore(lint): memoize inline styles in command-center
* chore(lint): extract helper to reduce max-depth in agent-response-loop
* chore(lint): flatten nested conditions in pi-direct-agent history
* chore(lint): reduce max-depth in claude-agent query pump
* chore(lint): clear cli warnings to zero
Refactor polling loops into recursive helpers to satisfy
no-await-in-loop, fix WebSocket named import, and correct
an absolute-path import in tests/tmp.
* chore(lint): memoize inline styles in context-menu
* chore(lint): narrow explicit any in claude-agent/openai stt
* chore(lint): reduce max-depth in opencode-agent foreground loop
* chore(lint): memoize inline styles in explorer-sidebar
* chore(lint): memoize mobile sidebar styles in explorer-sidebar
* chore(lint): memoize inline styles in agent-list
* chore(lint): reduce relay test no-await-in-loop warnings
Refactor while-loops and for-retry loops in e2e.test.ts and
live-relay.e2e.test.ts into recursive poll/attempt helpers.
Remaining 8 warnings in encrypted-channel.ts concern the custom
Transport interface (on-handler slots, not DOM EventTarget) and a
sequential send loop; both reflect deliberate runtime contracts.
* chore(lint): fix typecheck errors from hoisting refactors
- pair-scan: type BARCODE_SCANNER_SETTINGS as BarcodeSettings
- explorer-sidebar: add missing desktopSidebarStyle useMemo
- sidebar-workspace-list: coerce null dotColor to transparent
- menu-header, e2e.test: formatting
* chore(lint): memoize inline styles in workspace-screen
* chore(lint): memoize inline styles in workspace-desktop-tabs-row
* chore(lint): hoist constant style arrays in desktop-updates-section
* chore(lint): memoize inline styles in combobox
* chore(lint): extract SplitGroupChild to memoize inline styles
* chore(lint): memoize inline styles in dropdown-menu
* chore(lint): memoize inline styles in workspace-hover-card
* chore(lint): memoize inline styles in autocomplete, message-input, composer
* chore(lint): remove explicit any in server (129 warnings)
- session.ts: catch(error: any) -> catch(error) with Error coercion at use
- daemon-client transport: any -> unknown in listener types
- sherpa/onnx loaders: introduce structural native types
- pocket-tts-onnx: typed ONNX session inputs/outputs/tensors
- tests: any -> unknown + named stub types
* chore(lint): hoist inline arrays and objects in app (145 warnings)
- Hoist static style arrays/objects to module-level consts
- Memoize dynamic ones with useMemo and correct deps
- jsx-no-new-array-as-prop: 145 -> 43
- jsx-no-new-object-as-prop: 64 -> 21
* chore(lint): memoize inline styles in _layout
Fixes 3 react-perf/jsx-no-new-object-as-prop warnings in root layout by
memoizing the stack screen options, the agent screen override, and the
GestureHandlerRootView root style.
* chore(lint): memoize inline styles and objects in stream/status panes
Clears 10 react-perf/jsx-no-new warnings across welcome-screen,
file-explorer-pane, terminal-pane, agent-stream-view, and
agent-status-bar by extracting per-item subcomponents, hoisting constant
style tuples, and memoizing derived arrays/objects.
* chore(lint): memoize inline style objects in list/pane components
Clears 6 react-perf/jsx-no-new-object-as-prop warnings across
synced-loader, stream-strategy-web, sortable-inline-list, file-pane, and
both draggable-list platform variants by memoizing or hoisting their
inline style objects.
* chore(lint): hoist test fixture arrays/objects
Clears 14 react-perf/jsx-no-new-* warnings across app test files by
hoisting constant fixtures to module scope or wrapping them in helper
factories so they no longer appear as inline JSX prop values.
* chore(lint): memoize inline styles in message components
Clears 5 react-perf/jsx-no-new-* warnings in message.tsx by memoizing
image source objects, extracting an AssistantMessageBlockContainer for
per-block spacing, and moving todo list item rendering into a
TodoListItemRow subcomponent so each row memoizes its own style arrays.
The remaining 8 warnings in react-native-markdown-display render rules
are inherent to that library's rule API.
* chore(lint): remove unused vitest imports in server tests
* chore(lint): remove unused helpers and vars in server tests
* chore(lint): add explicit returns in then() callbacks in server
* chore(lint): fix no-shadow and no-nested-ternary in server
* chore(lint): flatten nested blocks to satisfy max-depth in server
* chore(lint): extract helpers to satisfy max-depth in server
* chore(lint): parallelize Claude persisted agents lookup
Replace sequential parseClaudeSessionDescriptor loop with Promise.all
to fix no-await-in-loop warning.
* chore(lint): parallelize Linux watch directory traversal
Switch BFS to level-by-level Promise.all over readdir calls, replacing
the sequential pop/await loop that triggered no-await-in-loop.
* chore(lint): parallelize workspace registry bootstrap upserts
Collect per-workspace upsert inputs in a sync pass, then run registry
writes via Promise.all to eliminate no-await-in-loop warnings.
* chore(lint): parallelize test daemon cleanup rm calls
Run the paseoHomeRoot/staticDir removals concurrently in close() and
the startup catch block to fix no-await-in-loop.
* chore(lint): mechanical cleanup in app (unused, shadow, nested-ternary)
Recovers in-flight edits from the app mechanical agent that couldn't be
pushed due to concurrent tree contention. Removes unused helpers and
locals, flattens nested ternaries, narrows a few props where unused.
* chore(lint): parallelize pending permission approvals
approvePendingPermissions now filters and records handled IDs in a
sync pass, then awaits all allowPermission calls concurrently instead
of looping awaits.
* chore(lint): parallelize rebase head-name lookup
Try both rebase backends (rebase-merge, rebase-apply) concurrently
via Promise.all and return the first non-null result.
* chore(lint): no-unused-vars in app
* chore(lint): parallelize worktree terminal bootstrap
Start each bootstrap terminal concurrently via Promise.all. Since every
spec creates an independent terminal and returns a standalone result,
parallelization preserves order while removing two no-await-in-loop
warnings.
* chore(lint): parallelize worktree bootstrap test cleanup and script spawn
Run terminal cleanup across managers and spawnWorkspaceScript for api
and web concurrently to eliminate no-await-in-loop warnings while
preserving semantics.
* chore(lint): prefer-add-event-listener in app
* chore(lint): parallelize script-health-monitor afterEach and spawns
Close all stub TCP servers concurrently and spawn typecheck/api
scripts via Promise.all to drop two no-await-in-loop warnings.
* chore(lint): jsx-no-useless-fragment in app
* chore(lint): parallelize executable PATH probing
* chore(lint): parallelize client and relay transport test cleanup
* chore(lint): parallelize agent storage record file probing
* chore(lint): no-array-index-key in app
* chore(lint): parallelize bootstrap provider availability cleanup
* chore(lint): parallelize Codex rollout file search
* chore(lint): parallelize dictation wav fixture search
* chore(lint): parallelize Claude session id fixture test
* chore(lint): parallelize script health monitor server cleanup
* chore(lint): parallelize Codex skills directory scan
* chore(lint): promise/always-return in app
* chore(lint): no-shadow in use-dictation, hoist icons in splash screen
* chore(lint): no-shadow in composer callbacks
* chore(format): apply oxfmt to 3 server files
* chore(lint): no-explicit-any in stt-manager/chat-mentions tests
* chore(lint): no-shadow in small app files
Rename inner shadowing identifiers in:
- add-host-modal, agent-status-bar, sidebar-workspace-list, terminal-pane
- workspace-setup-dialog, session-context, use-is-local-daemon
- setup-panel, new-workspace-screen, workspace-desktop-tabs-row
* chore(lint): no-explicit-any in claude-agent.test
* chore(lint): no-shadow in small app test/e2e files
* chore(lint): no-explicit-any in workspace-git-service.test
* chore(lint): no-shadow in app test hoisted blocks
* chore(lint): no-explicit-any in websocket-server.runtime-metrics.test
* chore(lint): no-explicit-any in acp-agent.test
* chore(lint): no-shadow in app composer/message-input/e2e helpers
* chore(lint): no-explicit-any in session.workspace-git-watch.test
* chore(lint): no-explicit-any in websocket-server.notifications.test
* chore(lint): no-shadow in sidebar-workspace-list.test, use-pr-pane-data.test
* chore(lint): no-explicit-any in websocket-server.relay-reconnect.test
* chore(lint): no-explicit-any in codex-app-server-agent.test
* chore(lint): no-shadow in composer.test, host-runtime.test, new-workspace-screen.test
* chore(lint): no-explicit-any in codex-app-server-agent.features.test
* chore(lint): hoist jsx-as-prop in app components batch 1
Memoize inline JSX icons passed as props across add-host-modal,
branch-switcher, composer, pair-link-modal, pr-pane, and
sidebar-workspace-list to satisfy react-perf/jsx-no-jsx-as-prop.
* chore(lint): no-explicit-any in session.test
* chore(lint): hoist jsx-as-prop in app components batch 2
Memoize inline JSX passed as props across agent-list, agent-status-bar,
file-explorer-pane, git-actions-split-button, and message to satisfy
react-perf/jsx-no-jsx-as-prop.
* chore(lint): hoist jsx-as-prop in app components batch 3
Memoize inline JSX passed as props across combined-model-selector,
draggable-list.native, provider-diagnostic-sheet, and
workspace-setup-dialog to satisfy react-perf/jsx-no-jsx-as-prop.
* chore(lint): hoist jsx-as-prop in desktop components
Memoize inline JSX passed as props across desktop-permissions-section,
desktop-updates-section, integrations-section, and pair-device-section
to satisfy react-perf/jsx-no-jsx-as-prop.
* chore(lint): no-explicit-any in mcp-server.test
* chore(lint): hoist jsx-as-prop in workspace/new-workspace screens
* chore(lint): hoist jsx-as-prop in remaining screens
* chore(lint): no-explicit-any in session.workspaces.test
* chore(lint): no-explicit-any in snapshot-mutation-ownership.test
* chore(lint): no-explicit-any in dictation-stream-manager.test
* chore(lint): no-explicit-any in relay-transport.e2e.test
* chore(lint): no-explicit-any in worktree-session.test
* chore(lint): no-explicit-any in model-resolver.test
* chore(lint): no-explicit-any in sherpa-parakeet-stt.test
* chore(lint): no-explicit-any in speech-download.e2e.test
* chore(lint): no-explicit-any in script-health-monitor.test
* chore(lint): no-explicit-any in schedule/service.test
* chore(lint): no-explicit-any in persistence-hooks.test
* chore(lint): no-explicit-any in editor-targets.test
* chore(lint): no-explicit-any in send-while-running-stuck-test-utils.test
* chore(lint): no-explicit-any in agent-storage.test
* chore(lint): no-nested-ternary in app batch 1 (18 files)
* chore(lint): no-explicit-any in generate-sherpa-tts-matrix
* chore(lint): no-explicit-any in claude-agent
* chore(lint): no-explicit-any in websocket-server
* chore(lint): no-explicit-any in process-conversation
* chore(lint): no-explicit-any in daemon-client
* chore(lint): no-explicit-any in codex-app-server-agent
* chore(lint): no-nested-ternary in app batch 2 (26 files)
* chore(format): apply oxfmt to server test files
* chore(lint): parallelize agent archiving in test-mcp-inject
* chore(lint): no-explicit-any in small app files (batch 1)
* chore(lint): no-explicit-any in small app files (batch 2)
* chore(lint): no-explicit-any in app (batch 3)
Covers use-web-scrollbar, stream-strategy, dictation-stream-sender test,
terminal-perf helper, checkout-git-actions-store test, and
web-desktop-scrollbar.
* chore(lint): no-explicit-any in app (batch 4)
Covers composer, message-input, tooltip, workspace-desktop-tabs-row,
and the e2e helpers (app, node-ws-factory, terminal-probes).
* chore(lint): no-explicit-any in polyfills/crypto.ts
* chore(lint): no-explicit-any in components/sidebar-workspace-list.tsx
* chore(lint): prefer-array-find over filter().at/pop
Replace filter(pred).at(-1)/pop() patterns with findLast(pred) and
filter(pred)[0] with find(pred) across server and app.
* chore(lint): no-explicit-any in components/message.tsx
* chore(lint): no-explicit-any in components/plan-card.tsx
* chore(lint): no-map-spread replace with Object.assign
Replace { ...obj, override } inside map callbacks with Object.assign({}, obj, { override })
to satisfy oxc/no-map-spread. Preserves copy-on-write semantics.
* chore(lint): no-extraneous-class in test mocks
Add dispose() stubs to xterm addon mocks to satisfy typescript-eslint/no-extraneous-class, and convert static-only Notification mocks to plain objects.
* chore(lint): no-named-as-default use named imports for ws and openai
* chore(lint): jsx-no-new-array-as-prop hoist composed style in volume-display
Memoize the style array so it is not created inline on every render.
* chore(lint): prefer-add-event-listener use Object.assign for non-DOM handlers
These transports (Transport interface, StreamableHTTPServerTransport) use
plain on<event> properties rather than EventTarget, so addEventListener is
not an option. Using Object.assign to set the handlers avoids the lint
false-positive without changing behavior.
* chore(lint): no-multiple-resolved null pendingResolve after settling
Replace boolean-settled guards with a nullable captured resolve/reject.
Each promise callback captures the resolver, nulls it on first use, and
calls it. This preserves the existing single-resolution semantics while
making the no-multiple-resolved rule happy.
* chore(lint): reduce structural complexity in server
Extract nested-callback cleanup helpers in worktree-bootstrap.test. Extract Codex model definition builder to drop list-models complexity below 20.
* chore(lint): extract helpers in agent-activity and session-store-hooks.test
Split tool-call update handling in groupActivities into helpers to clear max-depth. Hoist useWorkspaceFields selector in the hooks test to drop callback nesting.
* chore(lint): rules-of-hooks and exhaustive-deps in agent-stream-view
Move useMemo calls for permission card styles above the early return
so hooks run unconditionally. Add missing dependencies to the inline
path press handler and the stream render callback.
* chore(lint): rules-of-hooks in diff-scroll via optional context hook
Replace try/catch around useExplorerSidebarAnimation with a new
useExplorerSidebarAnimationOptional hook that returns null when the
provider is absent.
* chore(lint): rules-of-hooks and exhaustive-deps in sidebar-workspace-list
Replace conditional useMemo calls with plain inline arrays, capture
creatingWorkspaceTimeoutsRef.current in the effect body before cleanup,
and depend on the full input object in armTimers.
* chore(lint): rules-of-hooks in split-container
Hoist useWorkspaceLayoutStore and useMemo calls above the pane-kind
early return so hooks always run in the same order.
* chore(lint): rules-of-hooks in context-menu and dropdown-menu
Inline the resolvedWidthStyle object inside the content useMemo and move
the early return below the hook so it is always invoked in the same order.
* chore(lint): rules-of-hooks in web-desktop-scrollbar
Move thumbRegionStyle and handleStyle useMemo calls above the visibility
early return.
* chore(lint): reduce complexity in types/stream
Split reduceStreamUpdate timeline branch into per-case helpers (tool call and compaction) to clear complexity and max-depth warnings.
* chore(lint): reduce complexity in diff-highlighter
Extract metadata detection, path extraction, hunk parsing, and content-line push into helpers to drop parseDiff complexity below 20.
* chore(lint): exhaustive-deps in composer and use-attachment-preview-url
Memoize githubSearchItems so the picker callbacks don't see a new array
every render, hoist realtimeVoiceButtonStyle above rightContent useMemo
that depends on it, and add missing style dependencies. Tighten
use-attachment-preview-url to read the attachment via a ref while keying
off stable field primitives.
* chore(lint): reduce complexity in tool-call-detail-state
Extract per-detail helpers for hasMeaningfulToolCallDetail to clear complexity warning.
* chore(lint): reduce complexity in agent-grouping
Split groupAgents into partition, project-activity map, active-project, and inactive-date helpers.
* chore(lint): reduce max-depth in voice-runtime
Extract retireFinishedGroup helper to flatten processPlaybackQueue nesting.
* chore(lint): reduce max-depth in use-branch-switcher
Extract maybeRestoreStashForBranch callback to flatten handleBranchSelect nesting.
* chore(lint): exhaustive-deps in components batch
Address exhaustive-deps warnings across file-explorer-pane, file-pane,
git-diff-pane, message, provider-diagnostic-sheet, stream-strategy-web,
terminal-emulator, terminal-pane, and volume-meter. Memoize values that
otherwise changed every render, read non-reactive values through refs
when the effect intentionally tracks a different key, and add missing
dependencies where they were genuinely absent.
* chore(lint): reduce complexity/max-depth in draft-store
Extract collectQueuedMessageAttachmentIds and collectStreamUserImageIds helpers from runAttachmentGc. Extract buildMigratedDraftRecord helper for migratePersistedState.
* chore(lint): reduce complexity in tooltip and workspace-scripts-button
Extract resolveActualSide and resolveAlignedCoordinate helpers in tooltip. Extract resolveScriptIconColor in workspace-scripts-button.
* chore(lint): exhaustive-deps in contexts and hooks batch
Fixes exhaustive-deps warnings across app contexts and hooks by adding
missing deps, stabilizing derived arrays with useMemo, capturing ref values
at effect entry, and using ref pattern where deps were intentionally omitted.
* chore(lint): final hook-rules in workspace-screen, editor-button, stores, e2e
Memoizes derived arrays (availableEditors, terminals) to stabilize
useMemo deps, adds missing deps (normalizedServerId, normalizedWorkspaceId,
explorerToggleStyle, workspaceIdsKey, eventName), and renames the Playwright
fixture callback parameter from `use` to `provide` so eslint-plugin-react-hooks
doesn't conflate it with React's use() hook.
* chore(lint): reduce complexity/max-nested-callbacks in workspace-tabs-store
Extract migrate function body into migrateWorkspaceTabsState top-level
helper with per-loop sub-helpers (migrateUiTabsForKey, mergeExplicitTabOrder,
mergeLegacyTabOrder, migrateFocusedTabIds) and replace the inline IIFE in
ensureTab with buildNextTabsForEnsure helper.
* chore(lint): reduce complexity in session-store and panel-store
Extract isSessionServerInfoUnchanged helper from updateSessionServerInfo
in session-store. In panel-store split migrate body into per-version
migration helpers (migratePanelV2Explorer, migratePanelV3Explorer,
migratePanelExplorerTabByCheckout, migratePanelDesktopFocusMode) and
a top-level migratePanelState function.
* chore(lint): reduce complexity in keyboard-shortcuts and use-keyboard-shortcuts
Split resolveKeyboardShortcut into resolveInitialChordStep and
resolveAdvancingChordStep helpers with a shared buildMatchFromBinding
factory. Split handleAction's large switch into handleDispatchOnlyAction,
handlePayloadAction, handleSettingsToggle, and handleCommandCenterToggle
helpers, with hasPayloadKey type guard replacing inline payload checks.
* chore(lint): reduce complexity in audio recorder web hooks
Extract assertMicrophoneEnvironment helper from useAudioRecorder start
callback. Split useDictationAudioSource stop callback with
disconnectDictationAudioGraph, stopMediaRecorderIfActive,
finalizeRecorderStoppedPromise, and safeDisconnectNode helpers.
* chore(lint): reduce complexity in use-git-actions
* chore(lint): reduce complexity in use-pr-pane-data
* chore(lint): reduce complexity in use-agent-autocomplete
* chore(lint): reduce complexity in use-agent-screen-state-machine
* chore(lint): reduce complexity in session-stream-reducers
* chore(lint): reduce complexity in desktop-updates-section
* chore(lint): reduce complexity in pair-device-section
* chore(lint): reduce complexity in update-callout-source
* chore(lint): reduce complexity in provider-diagnostic-sheet
* chore(lint): disable no-await-in-loop (sequential-by-necessity cases)
Most remaining no-await-in-loop warnings were in legitimate sequential
patterns: polling loops with sleep, streaming/pagination cursors, shared
mutable state, ordered side effects (audio playback, port allocation).
Parallelizing these would change observable behavior. Rather than force
restructures that add complexity without benefit, disable the rule.
* chore(lint): reduce complexity in setup-panel
Extract helpers (resolveAutoExpandIndex, resolveSetupStatusLabel,
resolveCommandLog, buildCommandRowState) and lift the standalone log
view and top-level error banner into dedicated sub-components to drop
SetupPanel below the complexity limit.
* chore(lint): reduce complexity in AssistantMarkdownImage
Extract error-text resolution into a helper so the image component
drops below the cyclomatic complexity threshold.
* chore(lint): reduce complexity in ProjectLeadingVisual
Extract the status-variant dispatch into a ProjectLeadingVisualStatus
sub-component so the outer function stays below the cyclomatic
complexity limit.
* chore(lint): extract ChatAgentContent agent-building helper
* chore(lint): reduce complexity in ChatAgentContent
Extract the chat-agent selector (selectChatAgentState +
resolveChatAgentFromSession), the agent-shape constructor
(buildChatAgentFromState), and the not-found/error/boot view
dispatch (renderChatAgentNonReadyView) into helpers so both the
selector callback and the component function drop below the
cyclomatic complexity limit.
* chore(lint): extract subcomponents to reduce JSX nesting depth
* chore(lint): reduce complexity in app components (status-bar, explorer, setup-dialog, tool-call-details)
* chore(lint): reduce complexity in app hooks/runtime/stores/misc components
* chore(lint): long-tail cleanup (no-async-endpoint-handlers, unicorn rules, unescaped-entities, unassigned-import allowlist)
* chore(lint): simplify agent useMemo deps to match helper signature
* chore(lint): rename ChatService.postMessage to dispatchMessage
* chore(lint): server cleanup (no-shadow, no-useless-*, no-unmodified-loop, control-regex, require-yield)
* chore(lint): app cleanup (jsx-max-depth, jsx-no-new-*-as-prop, complexity, misleading-regex, no-useless-*)
* chore(lint): e2e cleanup (no-unmodified-loop-condition, max-nested-callbacks)
* chore(lint): workspace-screen complexity + no-empty-pattern cleanup
* chore(lint): use Array.from for defensive snapshot iteration
* chore(lint): final mechanical tail (control-regex, max-depth, useless-expressions, examples cleanup)
* chore(lint): reduce agent-stream-view complexity
* chore(lint): reduce resolveAgentModelSelection complexity
* chore(lint): reduce globalSetup complexity
* chore(format): oxfmt draft-store single-line signature
* chore(lint): reduce GitDiffPane complexity
* chore(lint): reduce MessageInput complexity
* chore(lint): reduce Combobox complexity
* fix(server): bound listLinuxWatchDirectories readdir concurrency
Wraps the per-level readdir traversal in listLinuxWatchDirectories with a
module-level p-limit (default 16, tunable via PASEO_LINUX_WATCH_READDIR_CONCURRENCY).
On broad repos, the previous Promise.all over an entire BFS level could issue
an unbounded number of concurrent readdir calls. Behavior is otherwise
identical: still traverses all non-.git directories and swallows per-directory
readdir failures.
* chore(lint): promote oxlint warnings to errors
- package.json: lint/lint:fix now pass --deny-warnings so any warning fails
- .github/workflows/ci.yml: add lint job running npm run lint
- .oxlintrc.json: disable no-empty-pattern for e2e/fixtures.ts
(Playwright requires the empty object destructure as the first arg)
- packages/app/e2e/fixtures.ts: restore async ({}, provide) signature
- packages/app/e2e/global-setup.ts: oxfmt single-line signature
* fix(test): relay-transport ws mock exports WebSocket named binding
Commit 38efad7d switched relay-transport.ts from default to named import
of ws.WebSocket, but the test mock still only exported default. Updated
vi.mock("ws") to return both default and named WebSocket bindings so the
mocked module matches the new import shape. Classification: test drifted
with production code; test now exports what the production import needs.
* chore(lint): make oxlint error at config level; add Lefthook pre-commit
Replaces --deny-warnings workaround with semantic severity at the config
level, and adds whole-repo pre-commit gates via Lefthook.
- .oxlintrc.json: all enabled categories (correctness, suspicious, perf)
and intentional rule overrides now use "error" instead of "warn".
Removed duplicate "require-await" key (last-wins "off" preserved).
- package.json: lint scripts are plain oxlint / oxlint --fix. Adds
lefthook devDep and "prepare": "lefthook install --force" so hooks
install on npm install even where core.hooksPath is set globally.
- lefthook.yml: pre-commit runs format:check, lint, typecheck in
parallel. Whole-repo gates (no glob filters). Block-only, no
auto-formatting.
* fix: update lockfile signatures and Nix hash
* chore(lint): pre-commit runs formatter in write mode, then lint+typecheck
Switches the pre-commit hook from format:check to format (write) with
stage_fixed: true, so the formatter rewrites files and re-stages them
into the commit. Lint and typecheck then run in parallel against the
formatted tree.
Sequencing uses Lefthook 2.x jobs with a nested group: top-level jobs
run sequentially, so the format job completes before the checks group
starts; within checks, lint and typecheck run in parallel.
CI continues to use format:check, so unformatted code pushed with
--no-verify still fails on the server.
* chore(lint): revert pre-commit to format:check (no stage_fixed)
stage_fixed: true with a whole-repo formatter (oxfmt .) can silently
re-stage the full reformatted file when a developer used git add -p
to stage only a hunk, losing their hunk-level intent. Safer to just
block the commit on unformatted code and let the dev run
`npm run format` themselves.
Reverts to the simple parallel check form: all three commands run
concurrently and are read-only.
* chore(ts7): tsgo compatibility across the monorepo
Preserves tsc 5.9.3 behavior while making TypeScript 7 / tsgo
(@typescript/native-preview 7.0.0-dev.20260423.1) green
package-by-package.
tsconfig:
- server/tsconfig.server.json: drop removed `baseUrl`; rewrite
`@server/*` paths entry to be relative to the tsconfig.
- website/tsconfig.json: drop removed `baseUrl`; rewrite
`~/*` paths entry to be relative.
- expo-two-way-audio/tsconfig.json: replace inherited
moduleResolution=node10 with `bundler`, set `module: esnext`
(required by `bundler`). Matches Metro/Expo runtime behavior
and avoids forcing `.js` extensions in source.
source:
- app/e2e/helpers/terminal-probes.ts: WebSocket subclass `send`
now types its argument as `Parameters<WebSocket["send"]>[0]`
so the subclass signature tracks the platform type across
tsc 5.9 and TS 7 libdom.
- relay/src/crypto.ts: tweetnacl setPRNG callback allocates a
fresh Uint8Array for getRandomValues (owning ArrayBuffer) and
copies into the caller-provided view. TS 7 libdom requires
ArrayBufferView<ArrayBuffer> (not ArrayBufferLike).
All 8 packages green under both tsc --noEmit and tsgo --noEmit.
* fix(app): update os-notifications test mocks for addEventListener
Commit 46731a51 switched attachWebClickHandler from notification.onclick
to notification.addEventListener("click", ...) to satisfy the
prefer-add-event-listener lint rule. The test mocks still asserted
the old onclick property, so two specs failed on CI with
"notification.addEventListener is not a function". Update the three
MockNotification classes to record click listeners through
addEventListener and drive them via clickListeners[0] in the
assertions.
* chore: switch typecheck from tsc to tsgo (@typescript/native-preview)
Installs @typescript/native-preview 7.0.0-dev.20260423.1 as a root
devDependency and swaps every per-workspace `typecheck` script from
`tsc --noEmit` to `tsgo --noEmit`. Build scripts still use `tsc`, so
emit behavior is unchanged. Monorepo typecheck drops from ~28s to
~4.7s wall clock (~6x faster).
expo-two-way-audio previously ran `tsc` without --noEmit for its
typecheck step. tsgo is stricter about rootDir on emit, so the
typecheck script explicitly passes --noEmit here too.
* fix: update lockfile signatures and Nix hash
* fix(app): commit *.css module declaration for CI typecheck
expo generates expo-env.d.ts (which references expo/types where
*.css is declared) locally, but the file is gitignored. CI does a
fresh checkout + install and doesn't run expo, so the declaration
is missing and tsgo rejects the side-effect import
'@xterm/xterm/css/xterm.css' with TS2882. Add a committed
packages/app/global.d.ts with 'declare module "*.css"' so typecheck
is self-contained.
tsc tolerated the missing declaration more loosely; tsgo is stricter.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Scoped to only `no-unused-vars` and `react/jsx-no-useless-fragment` —
the safe subset of --fix-suggestions. Primary changes:
- Remove unused named imports from statements
- Remove unused default/type imports entirely
- Collapse `<><Single/></>` fragments to `<Single/>`
Verified clean: typecheck + format + lint all green.
Auto-fixed 102 warnings (6040 → 5938) across 57 files. Main categories:
- preserve-caught-error: add { cause: error } to re-thrown errors
- unicorn/no-array-reverse: .reverse() → .toReversed()
- unicorn/no-useless-fallback-in-spread: drop unnecessary ?? {} in spreads
- oxc/no-map-spread: rewrite object spread in .map() to Object.assign
- react-hooks/exhaustive-deps: trim module-level constants from dep arrays
Ran npm run format after to clean up whitespace from lint mutations.
Centralizes refresh orchestration in WorkspaceGitService and GitHubService:
server coalesces, deduplicates, and throttles git/GitHub reads; clients peek
snapshots (cold reads fall through to a one-shot fetch) and never drive
refreshes. Introduces a two-TTL model (consumer vs internal), force-emit
bypass so fingerprint-matched refreshes still propagate, notifyGitMutation
wiring at ~13 action handlers, adaptive GitHub polling based on check state,
and checkout_status_update / prStatus push-to-cache wire messages. Fixes the
stale diff-stat bug and the inverted base-branch diff direction, and flips
next-action affordances promptly after commit / PR merge.
Branch switcher listed remote-only branches as if they were local, then
threw "Branch not found" when clicked because the checkout path only
verified `refs/heads/<name>`. Explicit `origin/<name>` input went the
other way — verify passed, but checkout produced a detached HEAD.
Split the concerns into three layers:
- Enumerate: `listBranchSuggestions` returns provenance (`hasLocal`,
`hasRemote`) so the wire payload no longer loses origin-only info.
Fields are optional on `branchDetails` for backward compat.
- Resolve: new `resolveBranchCheckout` normalizes `origin/<name>` →
`<name>`, verifies explicit `refs/heads/` and `refs/remotes/origin/`
paths (no tag-DWIM), returns a tagged
`{ local | remote-only | not-found }` result. Policy (local wins,
origin fallback) lives here.
- Act: `checkoutExistingBranch` no longer does implicit DWIM. Local →
`git checkout <name>`. Remote-only → `git checkout -b <name> --track
origin/<name>`. Not-found → throws the existing
`Branch not found: <name>` string. Response carries optional
`source: "local" | "remote"`.
`validateBranch` now routes through the same resolver so both paths
agree on what a branch is.
Also fixes a related stale-base diff bug: worktrees created off
`origin/main` could show phantom "added" commits when the local `main`
lagged. `resolveBestComparisonBaseRef` is now origin-first for
diff/status. Merge still uses the "most-ahead" helper so unpublished
local commits aren't lost. `doesGitRefExist` tightened to only swallow
exit 1 (missing ref), propagating other git failures.
Introduce a PR tab in the explorer sidebar that shows status checks,
reviews, and the merged timeline for the current branch's pull
request. Extend checkout PR status with number, draft flag, repo
identity, and per-check workflow/duration; add a new
pull_request_timeline request/response pair backed by a GraphQL
query; enrich the workspace git snapshot to carry the fields through
to the client; and extend the checkout-git-actions store so timeline
queries are invalidated alongside status when mutations land.
Pass the GitHub service into pullCurrentBranch, pushCurrentBranch,
createPullRequest, branch setup, and worktree archival, and invalidate
the cached PR status / repo view whenever a mutation succeeds. Have
mergeToBase return the worktree cwd it actually touched so the session
can invalidate the base checkout when the merge lands there.
Plumb updatedAt through GitHub issue/PR search and committerDate through
branch suggestions so the new-workspace picker interleaves branches and
PRs sorted by last activity. Debounce the search query so remote GitHub
searches are driven by what the user typed rather than a static list.
* fix(server): harden Codex/Windows startup and provider resolution
Addresses #452, #443, #353, #418, #403, #221, #307, and locks in #284.
- Replace custom where.exe/which output parsing with npm `which@^5` plus a
spawn probe. findExecutable now enumerates all PATH+PATHEXT candidates
and returns the first invokable one. A WindowsApps-ACL'd codex.exe no
longer wins over a working codex.cmd (#452).
- Make default provider isAvailable() check the binary instead of always
returning true. Codex/Claude/OpenCode default isAvailable() now defer
to isCommandAvailable() so missing CLIs surface as unavailable instead
of throwing later from spawn (#221, #443).
- Gate AgentManager.resumeAgentFromPersistence on isAvailable() so a
persisted agent record with a missing binary cannot reach provider
spawn during rehydration. The daemon stays up and the agent reports
unavailable (#443, #353, #418, #403).
- Drop --path-format=absolute from rev-parse callers and validate stdout
through a shared parser that rejects multi-line output and unknown-flag
echoes. --show-toplevel is absolute by default in modern Git;
--git-common-dir is resolved against the command cwd. Fixes workspace
registration on pre-2.31 Git that echoed the unknown flag and produced
a two-line "path" (#307).
Tests:
- Real-FS executable.test.ts using temp PATH fixtures, covering the .cmd
fallback after .exe pre-spawn failure (Windows-only) plus the
null-on-no-invokable-candidate case.
- provider-availability.test.ts builds real provider clients against a
temp-dir-only PATH for Codex/Claude/OpenCode.
- bootstrap-provider-availability.test.ts builds the daemon and triggers
ensureAgentLoaded so it actually exercises resumeAgentFromPersistence.
- claude-agent.spawn.test.ts asserts shell: false reaches spawnProcess
from the Claude SDK spawn override, locking in 39b56af4 for #284.
- checkout-git-rev-parse.test.ts covers nested-checkout resolution and
the old-Git multi-line stdout case via a tightly-scoped runGitCommand
fake.
CI:
- server-tests-windows now also runs the new and modified test files so
the Windows behaviors are exercised on windows-latest.
* fix: update lockfile signatures and Nix hash
* fix(server): handle synchronous spawn UNKNOWN on Windows
On Windows, child_process.spawn() throws synchronously when invoked on
a corrupt or invalid .exe (e.g., a WindowsApps stub the current user
cannot execute, or a zero-byte file). The executable probe did not
guard the spawn call, so the synchronous throw rejected the probe
promise instead of resolving false, preventing findExecutable() from
trying the next candidate. This is the root cause of the daemon-crash
pattern in #452: codex.exe from WindowsApps would hard-fail before the
.cmd shim ever got a chance.
Wrap spawn() in try/catch and settle false on sync throw. The existing
error-event and exit-event handlers already cover async failure modes;
sync throw just needed one more guard.
Also adjust three Windows-only test comparisons that were asserting
platform-dependent string equality:
- executable.test: compare .cmd paths case-insensitively (which@5
preserves PATHEXT casing, which is uppercase in production).
- workspace-registry-model.test: expect normalizeWorkspaceId(path),
not the hardcoded POSIX form.
- checkout-git-rev-parse.test: normalize separators/case when
comparing git's Windows forward-slash output against realpathSync.
* test(server): canonicalize repo root via git on Windows to fix short-name mismatch
realpathSync on Windows preserves 8.3 short names (e.g. RUNNER~1) while git's
rev-parse --show-toplevel always returns the long-name form (runneradmin).
Use git as the canonicalizer on both sides of the assertion so the comparison
holds regardless of how Windows exposes the temp directory path.
* fix(server): Claude is always available in default mode; SDK bundles cli.js
The Phase 2 change wrongly tied Claude's default-mode isAvailable() to
isCommandAvailable("claude"). Claude's default runtime does not use an
external `claude` binary — @anthropic-ai/claude-agent-sdk ships its own
cli.js and spawnClaudeCodeProcess runs it via process.execPath. The
previous `return true` was correct; restore it.
Update provider-availability.test.ts to assert the truthful behavior:
Claude reports available even when no `claude` binary is on PATH.
Codex and OpenCode genuinely require their binaries on PATH, so their
availability checks remain unchanged.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
git rev-parse --abbrev-ref HEAD fails with exit code 128 on freshly
initialized repos with no commits. This error bubbled up through
workspace listing and showed a toast error on every app launch for
users with empty git repos.
Route all git checkout queries through WorkspaceGitService instead of
shelling out to git on every call. This makes fetch_agents_request
instant when warm (cached in-memory snapshots) and deduplicates
concurrent cold-start refreshes via ensureWorkspaceTarget.
- buildProjectPlacementForCwd now uses workspaceGitService.getSnapshot()
- worktree-session call sites use WorkspaceGitService
- Deleted getCheckoutStatusLite and its 4 return types from checkout-git
- Fixed cold-start thundering herd: getSnapshot deduplicates via
ensureWorkspaceTarget instead of spawning parallel git processes
- Extracted checkout-diff logic into WorkspaceGitService
* Add Windows exec foundation helpers
* server: centralize windows exec handling phase 2
* migrate cli daemon status to execCommand
* refactor: complete phase 4 async exec migrations
* test: cover windows exec helpers
* fix: use beforeEach for context.skip in test setup
* Fix uncaught timeout rejection when archiving agents
* fix(server): centralize git subprocesses under runGitCommand with p-limit throttling
Supersedes #299. Same problem (unbounded git subprocess concurrency causing
zombie accumulation), different approach: a single `runGitCommand(args, opts)`
function backed by `p-limit` instead of a custom semaphore pool.
- Always uses `spawn` (one code path, no exec/execFile split)
- Concurrency configurable via PASEO_GIT_CONCURRENCY env var (default 8)
- 30s default timeout for read-only ops, 120s for mutations
- Centralized error formatting, stdout truncation, process reaping
- Runtime tests verifying throttling, timeout kills, deadlock prevention
* fix: update lockfile signatures and Nix hash
* fix(server): add Windows compatibility to runGitCommand
- shell: true on Windows (resolves .cmd/.bat git shims)
- windowsHide: true always (prevents console window flashes)
- quoteForCmd for args with spaces when routing through cmd.exe
- Tests verify shell/windowsHide behavior per platform
* refactor: use spawnProcess instead of duplicating Windows handling
spawnProcess from spawn.ts already handles shell: true on Windows,
arg quoting for cmd.exe, and windowsHide: true. No need to reimplement.
* fix: non-null assertions for stdio streams in strict build
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>