Commit Graph

216 Commits

Author SHA1 Message Date
dixonl90
d454fa3af4 Fix worktree checkout validation for existing git branch refs (#1358)
* fix(server): allow valid existing worktree branch refs (#1357)

* Fix existing branch checkout validation

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-06-05 23:31:38 +08:00
Mohamed Boudra
0d1eecc388 Keep virtualenvs out of project picker results (#1356)
* Fix project picker virtualenv suggestions

* Ignore more virtualenv names in project picker
2026-06-05 21:35:47 +08:00
Mohamed Boudra
369adced52 Archive merged PR workspaces from settings (#1313)
* Add workspace settings for merged PR cleanup

* Fix worktree branch ahead status

* Handle PR worktree upstream status

* Remove stale checkout tracking field

* Update settings E2E host section slugs

* Treat unknown upstream as unsafe for auto-archive
2026-06-03 23:17:08 +08:00
Mohamed Boudra
4f3116d28a Unify workspace service URLs (#1288)
* Unify workspace service proxying

* Clean up service proxy review issues
2026-06-02 19:49:58 +08:00
Matt Cowger
f1a20a9fe9 Add configurable service proxy (#1280)
* Add configurable service proxy

* Fix service proxy URL used when opening scripts with a public base URL

When a public proxy URL was configured, the View link was falling through
to direct connection logic and opening the wrong URL. Move the public URL
early-return before connection-type checks, and remove the now-dead
duplicate check further down.

* Add service proxy documentation

Covers config options, URL generation format, wildcard DNS setup,
and reverse proxy Host header requirements.

* Cap combined public service label and document hostname format

* Address service proxy review feedback
2026-06-02 14:08:41 +08:00
Matt Cowger
d406c29e16 Add configurable worktree root
* 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>
2026-06-01 16:19:29 +08:00
Mohamed Boudra
8cf80c2486 Mark oversized generated diffs as too large
Gate tracked diff sections by content size before parsing and highlighting while preserving the existing batched git diff path.
2026-05-31 13:57:17 +07:00
Mohamed Boudra
24526a3b23 Move branch-slug to @getpaseo/protocol
The app must not import @getpaseo/server. Moving the slug validation
and slugify helpers into @getpaseo/protocol (already a shared dep of
app, client, server) gives every package a legitimate home for the
import. Drops the corresponding ./utils/branch-slug subpath from
@getpaseo/server's exports map.

Unblocks the Deploy App workflow, which only builds protocol/client/
audio for the app — server's dist never exists in that pipeline.
2026-05-28 18:41:13 +07:00
Mohamed Boudra
53c14d9855 Extract client SDK package (#1052)
* 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.
2026-05-28 01:58:18 +08:00
Mohamed Boudra
fa1b3e88f0 Reuse git facts for workspace observation 2026-05-27 20:33:21 +07:00
Mohamed Boudra
5d8dc800fc Reduce startup git work for workspace snapshots 2026-05-27 20:18:49 +07:00
Mohamed Boudra
79dcbdc1c1 Fix false unpushed commit warnings for worktree archive (#1158)
* fix(server): avoid false unpushed worktree commits

* fix(server): preserve local-only worktree push counts
2026-05-24 12:41:36 +08:00
Mohamed Boudra
4cf985c254 Run Pi agents through the installed Pi CLI (#1097)
* Run Pi through its RPC interface

Replace the embedded Pi runtime packages with a process-backed runtime that starts pi --mode rpc, keeps the runtime port thin, and leaves provider behavior in the Paseo session and mapper modules.

Add focused adapter and mapper tests plus real Pi E2E coverage for the installed binary path.

* Keep Pi provider code in one module

* Add Pi session imports

* Enable Paseo MCP tools in Pi agents

Route injected MCP servers through Pi's MCP adapter and make structured MCP results visible to model-only clients.

Normalize Pi MCP proxy calls so Paseo timelines show the underlying tool names.

* Fix Pi import symlink tests on Windows

Create directory links explicitly in symlink cwd fixtures so Windows server tests exercise the intended path-equivalence behavior.

* Use native realpath for import cwd matching

Resolve import cwd matchers with native realpath so Windows symlink and junction sessions compare against the same path shape used by persisted provider data.

* Fallback realpath for Windows import matching

Use plain realpath when native realpath cannot resolve a cwd so symlink-equivalent persisted sessions still match on Windows runners.

* Add daemon system prompt support for Pi

* Fix Windows realpath import matching

* Remove Pi RPC migration plan doc
2026-05-19 23:56:07 +08:00
Mohamed Boudra
3743df09e6 Add rename for workspaces, terminals, and agent tabs (#531)
* Add rename for workspaces, terminals, and agent tabs

Surfaces rename via the sidebar workspace kebab (git branch rename with
client-side slugify), the terminal tab context menu (stops OSC 2 auto-title
overrides), and the agent tab context menu (locks the title against the
metadata generator). A shared RenameModal wraps AdaptiveModalSheet and
replaces the inline rename on the host page.

Auto-title races are closed from both sides: AgentManager gains
setGeneratedTitleIfUnset with an atomic per-agent write queue, and
TerminalSession replaces lockedTitle with a titleMode discriminated union
so user rename flips to manual and disposes the OSC subscription.

New WebSocket messages are additive: rename_terminal_request/response and
checkout_rename_branch_request/response (branch rename uses the
CheckoutError family). A new @getpaseo/server/utils/branch-slug subpath
export shares slugify + validateBranchSlug between server and app.

* Tighten rename feature and restore lost rebase wiring

Unslop pass on the rename commit plus two rebase artifacts:

- session.ts: collapse handleRenameTerminalRequest to a respond helper
  with early returns; restore workspaceGitWatchTargets.set() in
  syncWorkspaceGitObserver (lost during rebase onto main, which broke
  onBranchChanged firing on branch rename).
- terminal.ts: remove DA1 query handler accidentally re-introduced by the
  rebase (main intentionally removed it); restore conditional
  onTitleChange registration under titleMode === "auto".
- rename-modal.tsx: drop unconfident optional-call on setNativeProps and
  the unknown-cast HTMLInputElement narrowing dance.
- sidebar-workspace-list.tsx: remove unused branch field from rename
  result; flatten validateRenameSlug into early returns.
- host-page.tsx: drop ?? "" fallback on a typed-string field.

* Fix typecheck after rebase: pass parsed config to getScriptConfigs

main refactored getScriptConfigs to take the parsed paseo.json config
instead of a repo path. spawnWorktreeScripts (added in the rename
feature) was still passing repoRoot. Read and parse the config first,
matching how spawnWorkspaceScript already does it.

* Resolve lint regressions and restore lost rebase fixes

Post-rebase cleanup: drop the await on syncWorkspaceGitObservers so
fetch_workspaces emits the response before any cold registration-
triggered git work fires (workspace.id is already on the descriptor —
no registry lookup needed). Restore the DA1 CSI handler that answers
\x1b[?62;4;22c on the daemon-side xterm so foreground apps like nvim
get a reply on stdin. Extract WorkspaceTabRenameModal/useWorkspaceTabRename
to drop WorkspaceScreenContent below the cyclomatic-complexity ceiling.
Switch session test internals to handleMessage so we exercise the public
dispatcher and avoid casting through any. Convert literal 'type' aliases
to interfaces and stop spawning callbacks inline so the codebase passes
the post-rebase oxlint rules.

* Fix typecheck after rebase: wire workspaceGitService and worker setTitle

Bootstrap was missing workspaceGitService when constructing
CreatePaseoWorktreeWorkflowDependencies after main's worktree workflow
refactor. Worker terminal manager needed setTitle on the session and
setTerminalTitle on the manager to satisfy the rename additions to
TerminalSession/TerminalManager interfaces.

* Format session.test.ts after rebase

* Remove stray auto-spawn of workspace scripts after bootstrap

The rebase brought in a spawnWorktreeScripts helper and a call inside
runWorktreeSetupInBackground that auto-started every configured workspace
script after worktree setup completed. main never auto-started scripts —
this regressed the workspace-setup-streaming Playwright test, which
expects the "web" script to be idle so the user can click Run.

Drops the helper, the call, and the workspaceGitService dep that only
existed to feed it.

* Fix checkout branch rename tests

* Fix sidebar checkout action store import

* Skip POSIX terminal tests on Windows

* Unslop the rename-entities feature

Six audit findings closed and ~450 net lines trimmed from the branch:

- Remove the duplicate "rename-branch" union member in
  GitMutationRefreshReason.
- Fold dispatchStashMessage back into handleSessionMessage; the split was a
  feature-first artifact of adding checkout_rename_branch_request, with no
  documented rationale.
- Drop the protected beforeGeneratedTitleIfUnsetWrite test seam from
  AgentStorage; rewrite the race test to exercise real Promise.all
  concurrency against the existing per-agent write queue.
- Reshape useWorkspaceTabRename so the hook returns state and handlers
  only; promote WorkspaceTabRenameModal to an exported component the
  consumer renders directly.
- Rename RenameModal to AdaptiveRenameModal so it reads as a generic
  primitive next to AdaptiveModalSheet, and update host-page,
  sidebar-workspace-list, and the workspace tab rename hook to import it
  by its new name.
- Trim duplicate matrix coverage and Zod self-tests across
  rename-modal.test.tsx, terminal.test.ts, session.test.ts,
  agent-storage.test.ts, messages.rename-entities.test.ts and the three
  rename e2e specs; introduce packages/app/e2e/helpers/rename.ts to share
  setup across the e2e specs without merging coverage.

Behavior of the rename feature is unchanged; targeted vitest, branch-wide
typecheck, and lint all green.

* Restore agentMetadataMocks dropped during rebase

5e6aeb2d removed the agentMetadataMocks hoisted definition and its
vi.mock wiring when it deleted the import describe block. The block
was kept (it belongs to main's import feature) but the mock support
was left behind.

* Fix terminal-manager tests using hardcoded /tmp on Windows

setTerminalTitle tests used cwd: "/tmp" which is not a valid directory
on Windows, causing node-pty error code 267 (ERROR_DIRECTORY). Use
realpathSync(tmpdir()) like the rest of the test file.

* Fix typecheck and lint after rebase onto main

AdaptiveModalSheet moved from a `title` string prop to a structured
`header: SheetHeader`; update AdaptiveRenameModal to memoize and pass
a SheetHeader. invalidateCheckoutGitQueriesForClient moved from
git/actions-store to git/query-keys. useWorkspaceTerminals now owns
the terminal query, so workspace-screen pulls queryKey from the hook
and re-declares queryClient via useQueryClient.

* Update agent metadata test mock to match setGeneratedTitleIfUnset rename

The branch renamed AgentManager.setTitle to setGeneratedTitleIfUnset
for the rename feature; the generateTitlePromptWithConfig helper still
mocked the old method, so eight prompt-byte tests crashed with
"setGeneratedTitleIfUnset is not a function" on both ubuntu and
windows server-tests jobs. Sister mocks in the same file were already
on the new name.

* Fix rename modal showing empty input by using controlled TextInput

AdaptiveTextInput (introduced on main by 29ce6653f) is intentionally
uncontrolled: it drops the `value` prop and seeds the native input
once via `initialValue`. AdaptiveRenameModal still passed `value=
draft` from the pre-rebase shape, so every rename modal opened with
an empty textbox and a disabled Save button. Three playwright e2e
specs (settings-host-page, sidebar-workspace-rename, workspace-agent-
tab-rename) failed for this reason.

Switch the rename modal to a plain controlled TextInput so the input
seeds with the current label and the slug transform (used by sidebar
workspace rename) reflects live in the textbox as the user types.
The unit test's old AdaptiveTextInput mock is replaced with a
react-native mock providing a controlled TextInput shim that captures
the same onChangeText/onSubmitEditing handlers.

* Revert "Fix rename modal showing empty input by using controlled TextInput"

This reverts commit 6283deae842b8a5fed63cb8bf06c78029f98386e.

* Seed rename modal input via AdaptiveTextInput's initialValue + resetKey

AdaptiveTextInput is intentionally uncontrolled — it drops `value` and
seeds the native input once with `initialValue`. The rename modal was
passing `value={draft}` from the pre-rebase shape, so every rename
modal opened with an empty textbox (three playwright specs failing).

Pass `initialValue={draft}` and bump `resetKey` only when the
transform rewrote what the user typed. That seeds the native input
with the current label on open, lets the user keep typing inside
text without cursor jumps (no remount when transform is a no-op),
and remounts the native input with the slug when the transform
diverges so live slugification keeps working in sidebar workspace
rename. Keeping AdaptiveTextInput preserves the BottomSheetTextInput
swap on mobile so the keyboard stays above the sheet — using a plain
TextInput would break that.

The unit test mock is updated to read `initialValue`/`resetKey` so it
mirrors production behavior instead of pretending the input is
controlled.

* Slugify branch rename at submit only, drop live transform

The rename modal's transform prop remounted the native input every
time the slug diverged from what the user typed, which lost focus
mid-edit on every uppercase letter or space in the sidebar workspace
rename. Live-rewriting what the user typed was also surprising — the
expectation is that you type a name, the daemon stores a slug.

Drop the transform prop from AdaptiveRenameModal entirely. Sidebar
workspace rename now slugifies once in handleSubmitRename before
calling renameBranch, and validateRenameSlug runs validateBranchSlug
against the slugified value so the user sees inline errors. The
playwright spec drops the live-slug assertion; it still verifies the
post-submit branch on disk and the rename request payload.

* Rename new rename RPCs to dotted convention

docs/rpc-namespacing.md says new RPCs use dotted names with the
direction as the final segment, and explicitly bans new flat snake_
case names. This PR introduced two flat ones; rename them in place
before the protocol ships:

- checkout_rename_branch_request → checkout.rename_branch.request
- checkout_rename_branch_response → checkout.rename_branch.response
- rename_terminal_request → terminal.rename.request
- rename_terminal_response → terminal.rename.response

Touched: the Zod literals in messages.ts, the discriminated union
entries, the session dispatcher and its tests, the daemon client
wrappers and their tests, the terminal session controller, and the
message-parsing rename-entities test. No callers exist outside the
server package — app and CLI go through the daemon-client wrappers,
which now emit the dotted names.

* Match rename modal Save button and input focus to app conventions

Save uses Button variant=default so it gets the same accent
background + white text as every other primary action in the app
(open-project, settings host save, pair-link confirm, etc).

The input previously fell back to the browser's blue focus outline
on web because the rename modal never set outlineStyle: none — every
other AdaptiveTextInput call site that wants accent feedback already
does this. Kill the browser outline, track focus state, and switch
borderColor to accent while focused.

* Theme the focus-visible outline color via AdaptiveTextInput

public/index.html paints a 2px :focus-visible outline on every web
element with a hard-coded #20744a (Paseo green). On the Claude theme,
the rename modal's input got that green ring instead of the theme's
brown accent — visible mismatch against every other accent-colored
control (primary buttons, etc).

Add an outlineColor entry to AdaptiveTextInput's stylesheet sourced
from theme.colors.accent. Unistyles' Babel plugin tracks the read and
updates the native ShadowTree on theme switch — no React re-render,
no useUnistyles() call (forbidden on this hot path per docs/unistyles.md).
The inline outline-color on the rendered DOM input overrides the
selector-level color from index.html; outline-width/style/offset
still come from the global rule.

Consumer style merges in after, so existing callers that pass
outlineColor (message-input, review/surface, question-form-card)
still win.

Also drop the half-baked isFocused/focusedBorderStyle local state
I added to rename-modal earlier — the AdaptiveTextInput fix removes
the need for it.

* Drop useUnistyles() from rename modal

The rename modal only read theme.colors.foregroundMuted to pass as
the TextInput's placeholderTextColor prop. The rest already went
through StyleSheet.create((theme) => ...). Per docs/unistyles.md the
hook is forbidden when an alternative exists — and this is exactly
the alternative the rest of the codebase uses (project-settings-screen,
add-host-modal, pair-link-modal, command-center): put the muted color
in a tiny StyleSheet entry and read .color off it for the prop.

* Default placeholderTextColor in AdaptiveTextInput

placeholderTextColor was being duplicated at every AdaptiveTextInput
call site (add-host-modal, command-center, pair-link-modal,
project-picker-modal, provider-diagnostic-sheet, combobox, the
sheet's own search inputs, etc.) — all passing the same
theme.colors.foregroundMuted. The shared input should own this.

Move it into AdaptiveTextInput as a default sourced from the same
StyleSheet.create(theme => ...) block as the accent outline. Consumers
still override via the prop if they need a different color. Strip
the redundant placeholderTextColor and local placeholderColor style
entry from rename-modal; other call sites can be cleaned up in a
follow-up.

* Update rename e2e specs to use dotted RPC type strings

Earlier commit (5d5624943) renamed the new rename RPCs to the dotted
convention but missed the two e2e specs that capture the WebSocket
frames by type. captureWsSessionFrames matched the old flat names, so
renameRequests / renameFrames stayed empty even though the rename
went through end to end — the sidebar updated and the branch was
renamed on disk. The toBeGreaterThan(0) assertion fired and the test
failed in playwright CI.
2026-05-19 18:05:45 +08:00
Mohamed Boudra
0f6641c8c0 Show resolved file paths in agent file-link tooltips (#1088)
* fix: show resolved file paths in agent file-link tooltips

Bare filenames in agent messages now resolve to their full workspace
path on hover. Also raises the daemon's directory-suggestion scan
depth so files in deeper package layouts are reachable.

* fix(app): keep file-link wrapper stable to avoid layout shift on resolve

* fix(app): resolve file links on hover instead of at render time

useQuery was firing the daemon RPC for every ambiguous file reference
the moment a message rendered, fanning out a wave of requests on chat
scroll. Switch to enabled: false + prefetchQuery on hover so RPCs are
driven by user intent. Sync-resolvable refs (directFile, external)
still seed via initialData and render with no RPC.

Also memo on source primitives rather than identity so identical-
content sources constructed inline upstream don't bust the memo every
render.

* Redesign assistant file link resolution

* Stabilize assistant file link handlers
2026-05-19 15:32:48 +08:00
Samatar
5a1c7f266c fix(server): render non-ASCII filenames in git output
Fixes #436
2026-05-19 01:16:33 +00:00
Mohamed Boudra
721f1ee8d3 Fix inline code file matching 2026-05-17 23:40:31 +07:00
accx
686a25fb65 Fix Windows import session path matching (#1012) 2026-05-17 20:32:28 +08:00
c4605
b3c272f720 Fix native diff row expansion (#940)
* Fix structured diff parsing for no-prefix paths

Git can emit diff --git path path when diff.noprefix is enabled. The structured diff parser only handled the default a/path b/path form, so checkout diff assembly could lose hunks for affected files and render expanded rows empty.

Normalize optional a/ and b/ prefixes in both app and server parsers, and cover the no-prefix header form with regression tests.

* Fix no-prefix diff path parsing

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-17 18:58:10 +08:00
Mohamed Boudra
9bf36cdb56 Handle unborn repos in changes diff 2026-05-15 15:08:41 +07:00
Mohamed Boudra
2d3c9101f3 Trace git command spawn and close metadata
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.
2026-05-15 09:50:05 +07:00
Mohamed Boudra
7e255a5249 Fix assistant file link resolution (#1025) 2026-05-14 20:55:41 +08:00
Mohamed Boudra
faa0d93cb8 Fix ahead count when upstream branch is gone (#1023) 2026-05-14 19:10:14 +08:00
Mohamed Boudra
eb7fa02ac8 Surface GitHub auto-merge actions (#1001)
* Surface GitHub auto-merge actions

* Namespace GitHub auto-merge RPC

* Update workspace git refresh test expectation

* Avoid redundant GitHub refresh queue
2026-05-14 17:48:05 +08:00
Mohamed Boudra
05c12a9c5c Fix branch-off worktree upstream tracking (#1002) 2026-05-13 17:06:51 +00:00
Mohamed Boudra
512702fe46 Avoid probing extra executable candidates (#996) 2026-05-13 15:14:47 +00:00
Mohamed Boudra
3b7971a463 Fix Windows git command console flashing (#897) 2026-05-10 19:46:25 +08:00
Mohamed Boudra
f17fb77013 refactor: share github remote parsing (#843) 2026-05-09 22:40:05 +08:00
Mohamed Boudra
d88af28e93 fix(server): suffix-fallback auto-named branches and rotate opencode waterfall
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.
2026-05-09 20:44:03 +07:00
Mohamed Boudra
030e163fb8 feat: project-level prompts for metadata generation (#836)
* feat(server): project-level prompts for metadata generation

Add `metadataGeneration` config to `paseo.json` so projects can customize the
four LLM-generated metadata strings — agent title, worktree branch name, commit
message, and pull request title+body. Each entry takes an `instructions` string
that gets injected as a `<user-instructions>` block between the default rules
and the load-bearing JSON format contract, with wrapper text saying user
instructions override defaults. Missing/empty/invalid config keeps every prompt
byte-identical to today.

* refactor(server): centralize project metadata prompt building

Three near-identical readers and four gate-then-wrap blocks collapsed
into one buildMetadataPrompt helper. wrapWithUserInstructions tightened
to require a non-empty string; its misleading empty fallback is gone.

* feat(app): edit metadata generation prompts in project settings

Adds four textareas (agent title, branch name, commit message, pull
request) under project settings, modeled on the existing setup/teardown
pattern. Round-trips through paseo.json preserving unknown sibling
fields at both the metadataGeneration and entry level.
2026-05-09 18:36:29 +08:00
Mohamed Boudra
ed07452c80 feat(checkout): merge PR action with real-GitHub e2e test (#814)
* 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.
2026-05-09 15:39:26 +08:00
Mohamed Boudra
e490bf1dab fix(server-tests): unblock Windows CI for Claude SDK + .exe launch test
- spawn.launch-regression: insert `--` between `node -e <body>` and the
  user JSON args. Without it Node treats `--config` as a CLI option and
  exits with code 9 ("bad option"). The `--` stops Node's flag parsing
  so userArgs land in process.argv intact.
- claude-agent.integration + claude-sdk-behavior: inject
  spawnClaudeCodeProcess that routes the SDK's launch through our
  spawnProcess (shell: false). On Windows the SDK's default spawn fails
  with EINVAL when pathToClaudeCodeExecutable resolves to a `.cmd` shim
  (CVE-2024-27980). This mirrors what claude-agent.ts does in production.
- 3 cleanup sites (event-stream, integration, sdk-behavior): swallow
  EBUSY/ENOTEMPTY/EPERM around rmSync. The maxRetries window doesn't
  reliably win the Windows cwd-lock race after Claude exits; the OS will
  reap the tmpdir.
2026-05-08 17:43:15 +07:00
Mohamed Boudra
b131876e69 test(server): cover .exe/.cmd/.bat end-to-end launch on Windows
Add three end-to-end tests in spawn.launch-regression.test.ts that exercise
findExecutable -> spawnProcess for each Windows-supported extension. Each
test creates a uniquely-named binary in a tempdir, prepends it to PATH,
verifies findExecutable returns the fixture path, then launches via
spawnProcess and asserts an exact JSON-arg roundtrip plus stdout/exit code.

The previous "does not detect a PowerShell shim from PATH" test is removed:
its expectation was satisfied via PATH-prepend (not isolation), so global
claude.CMD on CI made it return non-null. The supported-extension contract
matches which's hardcoded fallback (.EXE;.CMD;.BAT;.COM); .ps1 is not
supported by spawnProcess and not surfaced by which without explicit PATHEXT.

Also add maxRetries: 5 / retryDelay: 100 to rmSync cleanups in three Claude
integration tests to absorb the brief Windows cwd lock after Claude exits.
2026-05-08 17:24:52 +07:00
Mohamed Boudra
c4e4a28bc0 Run server tests on Linux+Windows matrix (#809)
* Run server tests on Linux+Windows matrix

Replace the hand-curated Windows server test allow-list with a full-suite matrix run.\nBoth Ubuntu and Windows now run the same server test command with shared setup and secrets.

* Add isPlatform helper and gate Windows-hostile server tests

- Add a shared server test isPlatform helper.

- Migrate existing Windows-gated spawn, worktree, executable, and worktree-core tests to the helper.

- Gate Windows-hostile symlink, macOS path-normalization, and POSIX shell setup tests with skipIf.

* Replace POSIX shell calls in test fixtures with Node primitives

- Replaced test fixture mkdir/echo shell setup with fs mkdirSync/writeFileSync calls in touched server tests.

- Replaced git shell strings with execFileSync/spawnSync argv calls across checkout, worktree, MCP, script, and workspace git fixtures.

- Gated the directory suggestion symlink escape tests on Windows because those fixtures require POSIX symlink behavior.

* Fix Windows server-test failures and split POSIX-only suites into sibling files

- Fix Windows path/cwd assertions in terminal, session/workspace, git-service, checkout-git, MCP, logger, spawn, and registry bootstrap tests.\n- Keep terminal tests runnable on Windows by canonicalizing temp cwd fixtures and ensuring a Windows shell fallback.\n- Gate POSIX-only shell, signal, Unix socket, and git-worktree reuse fixtures that need dedicated Windows coverage later.

* Move POSIX-only test blocks into sibling .posix.test.ts files

- terminal.test.ts: moved PTY/bash interaction blocks into terminal.posix.test.ts.

- worktree.test.ts: moved git-worktree and teardown shell blocks into worktree.posix.test.ts.

- worktree-bootstrap.test.ts: moved setup shell and terminal-backed service blocks into worktree-bootstrap.posix.test.ts.

- worktree-core.test.ts: moved the POSIX-only worktree-core suite into worktree-core.posix.test.ts and removed the empty original.

- provider-availability.test.ts and file-explorer/service.test.ts: moved POSIX PATH/symlink blocks into sibling suites.

* Fix Windows 8.3 short-name, EBUSY cleanup, and node-pty shell-path failures in server tests

- Normalize temp home directories in directory suggestion assertions to avoid Windows short-name mismatches.

- Use Windows-valid terminal shells/cwds in terminal fixtures and wait for terminal-manager PTYs before cleanup.

- Replace hardcoded POSIX paths in workspace-git/MCP/loop fixtures with platform-resolved paths or command files.

- Gate the two explicitly Linux-only workspace-git watcher tests.

* Replace hardcoded POSIX paths with portable Node path constructions in server tests

- checkout-git.test.ts and worktree-session.test.ts: compare Windows temp paths with realpathSync.native to avoid 8.3 short-name mismatches.

- directory-suggestions.test.ts: canonicalize result and expected paths with realpathSync.native.

- session.workspaces.test.ts: replace literal /tmp and /Users fixtures with path.resolve/path.join constructions.

- workspace-git-service.primitive.test.ts, loop-service.test.ts, and mcp-server.test.ts: use canonical repo/temp paths and shell-safe relative verify commands.

* Fix loop-service verify-check shell and workspace-git-service path-separator on Windows

- Run the loop-service verify script through the current Node executable with a relative script path.

- Normalize the workspace-git-service expected repo cwd to forward slashes for the listWorktrees assertion.

* Fix loop-service worker PTY spawn on Windows

- Canonicalize the loop-service temporary root and workspace with realpathSync.native before worker agents use the path as cwd.

* Gate loop-service real-worker-PTY test on Windows

- Skip the real worker PTY loop test on Windows after ConPTY path resolution still fails with node-pty error 267.\n- Keep the test running on POSIX so the loop behavior remains covered.

* Stub getMetricsSnapshot in test mocks to suppress async-leak uncaught exceptions on Windows

- Add a no-op AgentManager metrics snapshot to the WebSocket notification test server stub.

* Fix terminal-manager Windows-path test to avoid PTY spawn into nonexistent dir

- Use an existing temporary cwd for the createTerminal absolute-path validation assertion so node-pty does not spawn into a missing Windows directory.
2026-05-08 10:57:22 +08:00
Mohamed Boudra
285d4edf23 fix(server): harden findExecutable + remove gratuitous realpath from spawn paths (#787)
* Phase 0: baseline tests for probeExecutable

* Phase 1: invert self node env handling

* Phase 2: refactor probeExecutable to execCommand

* Phase 3: push POSIX executable lookup to system which

* Fix probe test spawn-failure fixture

* Guard system which executable lookup
2026-05-07 14:03:15 +08:00
Mohamed Boudra
80bdb4d45b Use tree-kill for daemon shutdown (#788)
* Use tree-kill for daemon shutdown

* fix: update lockfile signatures and Nix hash

* Use tree-kill for daemon shutdown

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-07 13:18:39 +08:00
Somasundaram Ayyappan
9f01b4b7a2 fix: include untracked files in checkout shortstat (#608) (#762)
Count lines in untracked files when computing branch diffstat.
Adds file size limit, empty file handling, and CRLF normalization.
2026-05-05 21:10:54 +08:00
Mohamed Boudra
685a3d0ee2 refactor(server,app): lift return types and parse at boundaries (T2 typeaware production sweep) (#758)
* 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
2026-05-05 19:58:56 +07:00
Mohamed Boudra
df5c59263e refactor(typeaware): no-unnecessary-type-conversion + unbound-method sweep (T3.b partial) (#754)
* refactor: clear no-unnecessary-type-conversion + unbound-method (T3.b partial)

Fixes 15 files within the hard cap:

unbound-method (interface method shorthand → property function syntax):
- pane-context.tsx: PaneContextValue + PaneFocusContextValue (fixes agent-panel, browser-panel, draft-panel)
- provider-runner.ts: ProviderTurnRunner interface
- process-tree.ts: process.kill.bind(process)

no-unnecessary-type-conversion (remove redundant Boolean()/String() wraps):
- agent-timeline-store.ts, worktree-session.ts, pairing-qr.ts
- sherpa-realtime-session.ts, sherpa-stt.ts (String() → direct call chain)
- workspace-execution.ts, confirm-dialog.ts, use-archive-agent.ts
- use-agent-history.ts, left-sidebar.tsx, autocomplete.tsx, dropdown-menu.tsx

Deferred (9 errors across 7 files, all single-line Boolean(hovered) removals):
project-picker-modal.tsx, agent-list.tsx, branch-switcher.tsx,
file-explorer-pane.tsx, workspace-open-in-editor-button.tsx,
browser-pane.electron.tsx, sortable-inline-list.web.tsx

* fix(app): preserve Boolean coercion fallback in archive state lookups

readPendingState(...)[key] and (pendingQuery.data ?? {})[key] return
boolean | undefined at runtime — noUncheckedIndexedAccess is off so
TypeScript types this as boolean but the actual value can be undefined.
Add ?? false to maintain the explicit boolean return contract.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
cccccca18e refactor(server): remove redundant null from unknown union types (#693) 2026-05-05 19:56:52 +07:00
Mohamed Boudra
4cd9e76bd2 Remove unnecessary type assertions across codebase
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.
2026-05-05 19:56:26 +07:00
Mohamed Boudra
0cb9da17cd Daemon hardening epic 2026-05-05 18:21:10 +07:00
Mohamed Boudra
7c85341a42 Fix worktree creation and archive flow
Server: drop the synchronous branch-name generator dep, use the
worktree slug as the initial branch, and move first-agent branch
auto-naming to an async post-creation step backed by a structured
LLM call. Guards against renaming when the placeholder branch has
already been changed.

App: make workspace and worktree archive optimistic, rolling back
the local hide if the daemon call fails, and suppress incoming
workspace upserts while an archive is pending.
2026-05-03 23:22:54 +07:00
Mohamed Boudra
d89ecae615 fix(server): clean up OpenCode process trees
Spawn the shared OpenCode server in its own POSIX process group and terminate provider child process trees with graceful timeout escalation before forcing SIGKILL. Reuse the same helper for Codex app-server cleanup so process-tree behavior is covered consistently across providers.

Add cross-platform unit coverage for macOS/Linux process groups, Windows taskkill cleanup, and timeout escalation. Add the process-tree test to the Windows-critical CI allowlist; Linux full server tests already include it.

Refs #227
2026-05-02 21:44:59 +07:00
Mohamed Boudra
c2ad4a02ef refactor: unify worktree creation workflow (#636)
Route app, MCP, and agent worktree creation through one workflow boundary. Move branch auto-naming into workspace creation and keep agent metadata title-only.
2026-04-30 21:37:17 +08:00
Mohamed Boudra
0b78379b77 fix: percent escaping for git --format on Windows (#629)
* test(server): add real-spawn round-trip for % in argv

RED test: pass --format=%(refname)... through spawnProcess to a child
node script that echoes argv[2]. Asserts the child receives the original
string verbatim. Wired into the Windows CI matrix to expose the
escapeWindowsCmdValue %-doubling bug under cmd.exe.

* test(server): use bare 'node' command so cmd.exe path is exercised

process.execPath has both an extension and a path separator on Windows,
so shouldUseWindowsShell returns false and the broken %-escape pipeline
is never invoked. Use the bare command name 'node' instead.

* fix(server): stop doubling % in cmd.exe arg escaping on Windows

cmd.exe only collapses `%%` → `%` inside batch files; on the command
line / via `cmd /c "..."` `%%` stays literal. Doubling `%` in
escapeWindowsCmdValue meant args like `--format=%(refname)` reached git
as `--format=%%(refname)`, which git interprets as the escape sequence
for a literal `%`, so format atoms appeared verbatim and the branch
picker showed `%(refname)%09%(committerdate:unix)` instead of branch
names.

Update unit tests that pinned the broken doubling behavior.
2026-04-30 14:22:19 +08:00
Mohamed Boudra
17b1d8f732 fix(server): keep setup/teardown/terminal action paths failing loudly on bad paseo.json
Follow-up to the projection fix. The mechanism/policy split landed projection
on a graceful-degrade path, but I also made the setup, teardown, and terminal
wrappers silently absorb parse failures — which broke the existing contract
that `runWorktreeSetupInBackground` surfaces "Failed to parse paseo.json" to
the user when they invoke setup against a workspace with a malformed config.

Restore the throw, but with the path included and the underlying parse error
attached as `cause` (Pino expands it; UI surfaces the path + detail). Extract
`paseoConfigParseError` so spawnWorkspaceScript and the three wrappers throw
the same shape.

Also update opencode-agent.full-access.test.ts mode-order assertion that the
reorder commit forgot to update.
2026-04-28 20:38:11 +07:00
Mohamed Boudra
00219cfd04 fix(server): isolate paseo.json parse failures so they don't block fetch_workspaces
A malformed paseo.json (e.g. git-conflict markers) in any registered
workspace caused readPaseoConfig to throw, which propagated through
getScriptConfigs -> buildWorkspaceScriptPayloads ->
Session.describeWorkspaceRecord and failed the entire
fetch_workspaces_request with code=fetch_workspaces_failed. The client
showed no workspaces at all.

Reshape: separate loading (mechanism) from policy (per-caller).

- readPaseoConfig now returns a discriminated result
  ({ ok: true; config } | { ok: false; configPath; error }). Pure load,
  no logging, no opinions.
- Workspace projection owns its own policy via readPaseoConfigForProjection
  in script-status-projection.ts: warn via pino with configPath +
  workspaceDirectory + err, treat workspace as having no scripts, keep the
  list alive.
- spawnWorkspaceScript throws with cause so the existing
  handleStartWorkspaceScriptRequest catch logs it once with full context.
- The setup/teardown/terminal wrappers retain their pre-existing silent
  degradation on missing/broken config.
- getScriptConfigs takes a parsed PaseoConfig | null instead of repoRoot
  so the projection can pass the result through without double-reading.

Closes https://github.com/getpaseo/paseo/issues/598
2026-04-28 19:48:24 +07:00
Yurui Zhou
2a4945ad0b fix: keep @ file mention responsive on very large projects (#600)
Typing @ in the chat input to reference a file froze the app on huge
workspaces. Two compounding causes:

1. The autocomplete hook fed fileFilterQuery straight into the React
   Query key, so every keystroke fired a new directory_suggestions
   request and a fresh BFS scan on the daemon. Added a 180 ms debounce
   that mirrors the existing pattern in new-workspace-screen so rapid
   typing coalesces into one request.

2. searchWorkspaceEntries only ignored node_modules, so dist, build,
   target, out, coverage, vendor and __pycache__ each consumed the
   5,000-entry scan budget before any real source file was reached.
   Extended WORKSPACE_IGNORED_DIRECTORY_NAMES with those names and
   added a regression test that proves a deep source file is still
   reachable when those directories are full of bait entries.

Refs #599

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:43:38 +08:00
Jakub Skałecki
ee5a23d962 Support GitHub SSH host aliases for PR actions
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.
2026-04-27 20:28:10 +08:00