Commit Graph

170 Commits

Author SHA1 Message Date
Mohamed Boudra
388f1d426c Keep agent browser tabs connected across workspace switches (#2156)
* fix(desktop): keep browser tabs connected across workspaces

Electron can replace a guest WebContents when a retained browser tab is reparented. Re-register each attachment and keep background actionability checks running so agent browser tools retain the tab through workspace eviction.\n\nCover the full app, daemon, Electron, and MCP path in the existing desktop CI job.

* fix(desktop): launch Electron E2E reliably on Linux

CI Electron must receive --no-sandbox before the app starts because the hosted runner cannot use Electron's bundled SUID sandbox helper. Forward explicit dev-runner arguments and fail readiness waits as soon as a child exits.

* fix(desktop): preserve active browser on repeated registration

* fix(desktop): keep browser keyboard attachment idempotent

* fix(desktop): wait for E2E bridge readiness

* fix(desktop): close E2E logs after output drains
2026-07-17 19:02:09 +08:00
Mohamed Boudra
47532952f3 Catch broken desktop packages before release (#2114)
* test(desktop): exercise packaged app startup

The previous smoke exited before IPC, window creation, and preload execution, so a broken packaged preload could still pass. Launch the normal app and observe its renderer bridge and managed daemon through CDP instead.

* test(desktop): preserve Linux sandbox in smoke

Configure the unpacked artifact's chrome-sandbox helper with the permissions Electron requires, while keeping sandboxing enabled so preload failures remain observable. Bound startup probes and include process logs in daemon timeout failures.

* ci(desktop): keep packaged smoke on releases

Remove the temporary pull-request artifact build after it validated the Linux launch path. Keep the real packaged smoke in the native desktop release matrix.

* ci(desktop): gate packaged smoke by changes

Run the Linux real-artifact smoke in pull-request CI when packages/desktop changes, while leaving the regular desktop unit-test matrix unconditional.
2026-07-16 12:04:45 +02:00
Liam Diprose
3b078240a8 Serve the daemon web UI from Nix packages (#1978)
* fix(nix): include daemon-web-ui in nix package

* fix(nix): verify web UI without packaging website sources

* test(nix): cover bundled web UI inputs

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-14 18:50:09 +08:00
Mohamed Boudra
f2ebac931c Keep CI moving through transient npm failures (#2039)
* fix(ci): retry npm installs

Transient registry and package-download failures should delay a job briefly instead of requiring a manual rerun. Apply one bounded retry policy across CI, deploy, and release workflows.

* fix(ci): preserve retries for historical Android tags

Manual Android rebuilds may check out releases from before the shared retry script existed. Keep that install step self-contained and allow the shared helper's injected runner to be asynchronous.

* fix(ci): preserve retries for desktop release refs

* refactor(ci): use shared npm retry helper everywhere
2026-07-13 15:54:32 +02:00
Mohamed Boudra
d28e174b38 ops(relay): require manual deployment 2026-07-12 00:09:15 +02:00
Mohamed Boudra
61a9117a6b Restore fuzzy project folder search and desktop browsing (#1968)
* fix(projects): restore folder search and browsing

* fix(projects): preserve fuzzy search edge cases

* test(projects): move picker cleanup into fixture

* fix(projects): preserve absolute path descendants

* test(server): compare canonical search paths

* fix(projects): anchor root path suggestions

* fix(projects): anchor Windows root suggestions

* fix(projects): anchor directory search paths

* test(server): canonicalize directory search root

* fix(app): anchor rooted basename suggestions

* refactor(search): unify directory suggestions

Use one parameterized daemon search engine for project paths and workspace entries. Keep local recommendations as a small client overlay and remove unnecessary response metadata.

* fix(search): preserve filesystem root semantics

Accept absolute queries through configured root aliases while keeping traversal and output canonical. Keep blank suffix browsing matcher-independent and avoid metadata-keyed listing caches where Windows cannot invalidate them reliably.
2026-07-10 17:52:55 +08:00
Mohamed Boudra
9968f2ba50 Speed up inbound WebSocket validation (#1895)
* perf(protocol): generate inbound ws validators

* perf(client): use generated ws validation

* docs(protocol): document generated validation

* fix(protocol): make validator generation source-only

* test(protocol): cover explicit provider model normalization

* fix(protocol): preserve inbound compat defaults

* fix(protocol): make validator import rewrite portable

* fix(protocol): harden validation safety checks

* fix(protocol): guard generated validator boundaries

* fix(protocol): normalize legacy inbound defaults

* fix(protocol): simplify generated validation safety net

* fix(protocol): keep cold typecheck source-only

* fix(protocol): encapsulate validator codegen

* fix(protocol): bootstrap source-alias typechecks

* fix(app): keep source aliases out of bundler config

* fix(protocol): harden validator codegen packaging

* fix(protocol): own zod-aot patches in generator

* fix(protocol): trim validator safety net

* fix(client): normalize provider updates before dispatch

* fix(protocol): keep validator generation out of install
2026-07-06 00:07:54 +08:00
Mohamed Boudra
f226222638 Build Docker images from source (#1877)
* fix(docker): build images from source

Collapse the npm-install and source-build image paths into one Dockerfile so every Docker image is built from the checked-out workspace packages. Include procps in the runtime image because provider process cleanup depends on ps through tree-kill on Linux.

* fix(docker): include native speech packages

* fix(docker): cover source build inputs

* fix(docker): ignore local agent artifacts
2026-07-03 15:49:23 +02:00
Mohamed Boudra
cb486c3a5a Speed up app Playwright CI (#1830)
* ci(playwright): shard app e2e in CI

Run the app Playwright suite across isolated CI shards and keep restarted E2E daemons on the same speech-disabled setup path as global setup.

* test(app): share disabled speech e2e env
2026-06-30 12:37:47 +02:00
Mohamed Boudra
276c1f48f1 ci: add Docker-only publish dispatch [skip ci] 2026-06-27 09:49:20 +00:00
Mohamed Boudra
62bdb52eaa ci: build beta Docker images from source 2026-06-27 05:17:51 +00:00
Mohamed Boudra
2c9db5279c Run Paseo from an official Docker image (#1740)
* Add Docker images and agent Docker Mods

Ship official container images that run the Paseo daemon headless. One
Dockerfile parametrized by BASE_IMAGE covers Debian 12/13, Ubuntu 22.04/24.04
and Alpine; it bundles Node 22, the npm-published server + CLI, a vendored
s6-overlay as PID 1, and a small Docker Mods loader.

Agents are chosen at runtime via DOCKER_MODS (pipe-separated mod images). Each
mod is a FROM scratch image carrying only an install hook that runs
`npm install -g <agent-cli>`; the loader pulls the layers from the registry,
extracts them, and runs the hook before the daemon starts, so any requested
agent is on PATH when Paseo probes provider availability.

- docker/base: Dockerfile, install scripts, s6 services, mods loader
- docker/mods/*: claude-code, codex, copilot, opencode, pi
- docker/docker-compose.example.yml + docker/README.md
- .github/workflows/docker.yml: multi-arch (amd64/arm64) buildx matrix,
  publishes to ghcr.io/getpaseo on version tags
- docs/docker.md + CLAUDE.md docs index row

* feat(docker): print pairing QR and link on daemon startup

Add an s6 oneshot service that waits for the daemon to listen, then runs
`paseo daemon pair` so the pairing QR code and link surface in the container
logs. Best-effort: never blocks boot, skips gracefully when relay is disabled.
Opt out with PASEO_PAIRING_QR=0.

* build(docker): add Arch image support

* ci(docker): build Arch without Buildx

* docs(docker): document paseo env contract

* feat(docker): add opt-in sudo mode

* docs(docker): link env references

* fix(docker): create agent config dirs

* fix(docker): default home to /home/paseo

* docs(docker): document agent auth setup

* docs(docker): document relay port setup

* fix(docker): install Node from tarball

* docs: add Docker quick start

* docs(docker): remove legacy home example

* docs(docker): set container hostname

* fix(docker): prepare opencode storage

* fix(docker): allow paseo login shell

* fix docker opencode permissions

* ci(docker): use Node 24 actions

* fix(docker): install bzip2 runtime tools

* fix(docker): update Pi mod package

* fix(docker): quiet default daemon logs

* fix(docker): split home from state

Docker images now keep HOME at /home/paseo and store Paseo daemon state under /home/paseo/.paseo by default.

Existing volumes can keep the old layout by setting PASEO_HOME=/home/paseo.

* fix(docker): keep mods out of paseo home

* ci(docker): skip alpine arm64 builds

* fix(docker): address review findings

* fix(docker): verify s6 overlay downloads

* fix(docker): honor custom healthcheck port

* fix(docker): fail on mod extraction errors

* feat(docker): add official container image

Ship a focused daemon image with the bundled web UI enabled and document extending it with agent CLIs.

* ci(docker): publish images only on stable releases

* fix(docker): check daemon health over HTTP

---------

Co-authored-by: Herbrant <cdavide98carnemolla@gmail.com>
2026-06-26 14:48:13 +08:00
Mohamed Boudra
de5307dfa0 Retry browser test dependency installs 2026-06-19 05:11:52 +07:00
Mohamed Boudra
1fc565118b Skip Playwright system deps in CI 2026-06-19 04:31:32 +07:00
Mohamed Boudra
ee3a7a2d28 Bound Playwright browser install in CI 2026-06-19 03:26:33 +07:00
Éverton Toffanetto
b83ee957d1 ci: stabilize Electron dependency installs (#1409)
* ci: stabilize Electron dependency installs

Skip unused Electron binary downloads in non-desktop CI jobs and retry Electron installs in desktop tests to avoid external CDN 504 failures during npm ci.

* ci: preserve npm retry exit code

Preserve the final npm ci exit code in the non-Windows Electron retry path so CI diagnostics retain the original failure code.

* ci: preserve retry exit code under errexit

Keep npm ci inside the retry conditional so bash errexit does not bypass retries, while preserving the final npm exit code for diagnostics.
2026-06-09 17:05:11 +08:00
Mohamed Boudra
9f41904c6f Clarify urgent support path 2026-06-09 16:00:03 +07:00
Mohamed Boudra
8234ecb7ba Upload only mac DMGs as workflow artifacts 2026-05-28 16:20:58 +07:00
Mohamed Boudra
a00152290f Upload desktop workflow artifacts 2026-05-28 15:40:37 +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
9c2d47ab34 Deploy website for docs changes 2026-05-27 16:17:10 +07:00
Mohamed Boudra
68bce623c8 Route OpenCode live tests through OpenRouter
Big Pickle's models.dev metadata changed upstream to route via the
Anthropic SDK against the Zen OpenAI-compatible endpoint, which the
endpoint rejects. Swap the live-turn smoke tests to
openrouter/~openai/gpt-mini-latest and gate them on OPENROUTER_API_KEY
so fork PRs skip cleanly when the secret is absent. Also unpin
opencode-ai so CI tracks the latest CLI.
2026-05-18 17:16:57 +07:00
Mohamed Boudra
0419346d6a Pin OpenCode CLI in CI 2026-05-18 16:01:30 +07:00
Mohamed Boudra
00e7ac9ec8 Fix OpenCode custom command hangs (#1063)
* Fix OpenCode slash command turns

* ci: install opencode in server-tests so real OpenCode e2e runs

Server tests previously only installed the Claude Code CLI, so every
opencode-*.real.e2e.test.ts self-skipped on CI. The custom-command and
turn-lifecycle real tests cover the OpenCode session SSE rewrite, so
add opencode-ai globally and let them run. Big Pickle is free so no
provider secrets needed.

* Fix OpenCode unit tests exposed by enabling opencode in CI

server-tests previously skipped every OpenCodeAgentClient test because
opencode-ai was not installed. Now that CI runs them, four real issues
surface:

- Drop unused mkdirSync import (was used on a code path 1812b1489
  refactored away on main; the rebase merged imports without rechecking
  usage). Fixes the lint job.
- Fix slash-command-timeout test passing four ctor args instead of three,
  which silently dropped the TestOpenCodeRuntime and ran against the real
  opencode runtime.
- Relax the listModels contextWindowMaxTokens assertion. Some providers
  routed through OpenCode (OpenAI in particular) don't expose a numeric
  context window; assert the type only when the field is present.
- Swallow late notifySubscribers calls once close() flips the closed
  flag and clear subscribers up front. The session-lifetime SSE stream
  introduced by the prior commit means a session.error arriving after
  the owning test has moved on can rejectCompletion on a deferred no
  one is awaiting, surfacing as an unhandled rejection in a downstream
  test file.
2026-05-17 19:51:20 +08:00
ezra
952b58c0eb Fix Windows Android app scripts (#1058)
* Fix Windows Android app scripts

* ci: skip onnxruntime CUDA download
2026-05-17 14:06:42 +08:00
Mohamed Boudra
1f79c4d039 Bump default desktop rollout to 36h 2026-05-15 14:47:42 +07:00
Mohamed Boudra
defb4f82f7 ci(nix): smoke test daemon boot (#939) 2026-05-12 16:05:15 +08:00
Mohamed Boudra
77c82dfdbd ci: split nix into two workflows
nix.yml: PR-only build check.
nix-update-hash.yml: push-to-main hash update via paseo-ai App token.
2026-05-12 12:57:38 +07:00
Mohamed Boudra
29277c900d ci: split nix workflow into build (anywhere) + update-hash (main only)
build runs on push to main and every PR including forks. Same model as
the test/lint/typecheck workflows: read-only token, no secrets injected,
runs scripts/update-nix.sh + nix build to validate. No commit, no push.

update-hash runs only on push to main. Mints the paseo-ai App token,
re-runs the update + build, and commits the refreshed hash via the
ruleset bypass. The default GITHUB_TOKEN no longer pushes anywhere.

Rename file to nix.yml since it's no longer just a build job.
2026-05-12 12:54:22 +07:00
Mohamed Boudra
ed1943058a ci: gate nix-build on same-repo PRs only
scripts/update-nix.sh and scripts/fix-lockfile.mjs are executed from
the PR's checked-out tree. A fork PR can modify those scripts to run
arbitrary code on the runner. Same-repo PRs and push-to-main still
run; fork PRs skip the job entirely.

Closes #365.
2026-05-12 12:37:01 +07:00
Mohamed Boudra
e0361ddd22 ci: drop unused contents: write override in nix-build
GITHUB_TOKEN no longer pushes anything in this workflow. PR runs are
read-only, and main pushes go through the paseo-ai App token. The
workflow-level 'contents: read' is sufficient.

Addresses #365.
2026-05-12 12:35:05 +07:00
Mohamed Boudra
ce9474055e ci: skip CI on nix-build auto hash commits
The hash-update commit only changes package-lock.json and
nix/npm-deps.hash. The parent commit already passed all 13 required
checks, and the file diff is content-only, so re-running the full
suite on every hash bump is wasted CI minutes.
2026-05-12 12:28:34 +07:00
Mohamed Boudra
8f9b4c8828 ci: skip App token on PRs in nix-build
Fork PRs cannot read repo secrets, so the App-token step failed with
'Input required and not supplied: app-id'. Gate the App-token step on
push-to-main and fall back to github.token for the checkout on PRs,
since PRs only need to validate the build.
2026-05-12 12:26:22 +07:00
Matan Bendix Shenhav
0759932dad nix: declarative config, typed relay options, desktop packaging (#923)
* nix: expose npmDepsHash as a callPackage arg

Downstream flakes that follow a different nixpkgs revision can hit a
hash mismatch on the npm-deps FOD even though package-lock.json is
unchanged, because fetchNpmDeps output is sensitive to nixpkgs version.
The standard fix — `.overrideAttrs { npmDepsHash = ...; }` — does not
work for buildNpmPackage: npmDepsHash is destructured from args, so the
default `npmDeps = fetchNpmDeps { hash = npmDepsHash; }` is already
bound by the time overrideAttrs runs.

Promote npmDepsHash to a callPackage arg with the current value as the
default. Consumers can now `.override { npmDepsHash = "sha256-..."; }`
and have it propagate to the npmDeps fetcher. Upstream CI behavior is
unchanged — update-nix.sh is adjusted to match the new
`npmDepsHash ? "..."` pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: move npmDepsHash default to a sidecar file

Read the default `npmDepsHash` from `nix/npm-deps.hash` via
`lib.fileContents` instead of inlining it as a string literal in
`nix/package.nix`. The CI auto-updater becomes a one-line file write
instead of a regex against a .nix source — decoupling lockfile bumps
from the formatting of the package definition.

No behavior change: same hash, same default, same `.override` surface.
Lockfile diffs become smaller and the update path stops being load-
bearing on a sed pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: declarative config via services.paseo.settings

Today only a handful of `config.json` fields are surfaced as module
options (listen, port, hostnames, relay.enable). Anything richer —
custom agent providers, MCP injection, log config, voice features —
requires hand-editing `$PASEO_HOME/config.json`.

Add `services.paseo.settings` as a freeform attrset rendered to JSON
via `pkgs.formats.json` and installed at `$PASEO_HOME/config.json`
on each service start. Standard NixOS idiom.

`install` on `preStart` rather than a `tmpfiles` symlink because the
daemon writes to `config.json` at runtime via `DaemonConfigStore.patch`
(MCP / provider toggles). A read-only symlink would break those writes;
a copy-on-start lets the daemon mutate freely within a session while
the Nix-managed file remains the source of truth at boot.

The full schema is `PersistedConfigSchema` in
`packages/server/src/server/persisted-config.ts`. Documented in the
option description that runtime mutations don't survive restarts when
`settings` is non-empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: typed services.paseo.relay options with auto-wired endpoint

Addresses #224 (option surface only).

Today `services.paseo.relay.enable` is a bool that just toggles
`--no-relay`. Pointing the daemon at a self-hosted relay requires
hand-setting `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS` via
the freeform `environment` option.

Add a typed relay subtree:

- `relay.mode = "hosted" | "remote"` selects how the daemon reaches
  the relay when enabled. Default is `"hosted"` (current behavior).
- `relay.{host,port,useTls}` configure the `"remote"` case.
- The module auto-wires `PASEO_RELAY_ENDPOINT` and `PASEO_RELAY_USE_TLS`
  when `mode = "remote"`.
- Assertion fires at eval time when `mode = "remote"` but `host` is empty.
- `relay.enable` keeps its current semantics — bool answers "is it on?",
  the new options answer "how is it configured?".

The `"local"` mode from #224 (running a relay on the same host as a
systemd unit) is deliberately not added here: `packages/relay` ships
only a Cloudflare Workers adapter, so there's no Node.js runtime to
package as a binary. Adding a Node adapter is a TS-side feature change
worth its own design discussion; tracked as a follow-up.

No breaking changes — existing `relay.enable = true|false` configs
evaluate unchanged with the new `mode = "hosted"` default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: package paseo desktop app for Linux

NixOS users have no easy way to run the desktop app today —
electron-builder's outputs (.deb, .rpm, .AppImage) don't fit Nix's
model, so `nix run github:getpaseo/paseo#desktop` doesn't exist.

Add `packages.<linux>.desktop` following the standard nixpkgs Electron
pattern (see e.g. signal-desktop, vscode): skip electron-builder
entirely, build the desktop main process with `tsc`, bundle the Expo
web export and built daemon workspaces, and wrap `pkgs.electron` with
`makeWrapper`. Output is a runnable derivation usable via `nix run` or
`environment.systemPackages`.

The install layout preserves the monorepo source tree
(`packages/desktop/dist/main.js`, `packages/app/dist`, `node_modules`
at the workspace root) so `main.ts`'s dev-mode path resolution
(`__dirname/../../app/dist`, `__dirname/../assets/icon.png`) works
without any source patches. When Electron is invoked unpackaged via
`electron path/to/main.js`, `app.isPackaged` is false and these
relative paths are used.

`--no-sandbox` is set on the launcher: Chromium's setuid sandbox can't
live in `/nix/store` (immutable, no setuid). A follow-up can wire
`security.wrappers` from a NixOS module for users who want the
renderer sandbox.

No CI changes — `desktop-release.yml` continues to produce
.deb/.AppImage/.rpm/macOS/Windows installers as today. This is purely
additive for NixOS users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: copy full packages/ tree in desktop derivation

The previous installPhase selectively copied built artifacts (dist/
under server, cli, relay, highlight, expo-two-way-audio), which left
two workspace symlinks dangling and failed noBrokenSymlinks:

- node_modules/@getpaseo/expo-two-way-audio → packages/expo-two-way-audio
  (the Expo native module ships source + native projects, no built dist/)
- node_modules/.bin/paseo → @getpaseo/cli/bin/paseo
  (the CLI launcher script lives under bin/, not dist/)

npm workspace symlinks expect every workspace package to exist at its
source path. Copy the whole packages/ tree instead. The cleanSourceWith
filter already excludes the heavy platform-specific paths (android/ios
under packages/app, website, tests), and the remaining ~16MB of src is
acceptable for an Electron app derivation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* nix: route desktop renderer through paseo:// protocol handler

When `paseo-desktop` is launched via `electron path/to/main.js` (our
unpackaged Nix layout), `app.isPackaged` is false and main.ts loads
`DEV_SERVER_URL` — which defaults to http://localhost:8081 (the Expo
dev server). That URL has nothing listening in a Nix-installed run,
so the renderer fails with ERR_CONNECTION_REFUSED.

main.ts already supports overriding this via the `EXPO_DEV_URL` env
var. Set it to `paseo://app/` so the request goes through the
`paseo://` protocol handler that main.ts registers unconditionally.
The handler resolves files via `getAppDistDir()`, which in the
unpackaged branch returns `__dirname/../../app/dist` — exactly where
our install layout places the Expo web export.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: track nix/npm-deps.hash in nix-build commit step

The commit step still referenced nix/package.nix in its diff check
and git add. After moving the hash to nix/npm-deps.hash, the
auto-updated hash would never be staged and the new value would
sit unstaged in the working tree forever.

* ci: push nix-build hash commits via paseo-ai[bot] App token

The default GITHUB_TOKEN cannot bypass main's required status checks,
so the auto-commit of stale Nix hash updates has been silently failing.
Mint an installation token for the paseo-ai App (which is in the
ruleset bypass list) and use it for checkout and push.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-12 13:11:42 +08:00
Mohamed Boudra
478aa4b70e ci: cancel superseded PR runs to free runner capacity
cancel-in-progress is gated to pull_request events so merge_group and
push runs always complete — only redundant CI from rapid PR pushes
gets cancelled.
2026-05-10 14:12:56 +07:00
Mohamed Boudra
fd74abcdca ci: trigger required checks on merge_group events (#879)
Without merge_group on the workflow, PRs entering the merge queue would
fail because no required check ever reports a status against the
merge_group ref.
2026-05-10 15:07:39 +08:00
Mohamed Boudra
90cb20ce79 ci: remove duplicate server-ci workflow (#870)
The matrix in CI / server-tests (ci.yml) covers what Server CI / test was
doing, on Linux and Windows instead of Linux only. Two workflows running
the same test suite on every server-touching PR is wasted runner time.
2026-05-10 06:05:27 +00:00
Mohamed Boudra
371c29fecd chore: issue forms, PR template, contributor guidance (#864)
* chore: add issue forms, PR template, contributor guidance

- bug report and feature request issue forms with structured fields for surface, version, provider, logs, screenshots
- issue config disables blank issues and routes casual questions to Discord
- single PR template with explicit verification section and AI-assisted contribution stance
- CONTRIBUTING.md: soften drive-by stance for objective bug fixes, codify expectations on AI-assisted reports and PRs (full logs over summaries, proof of testing over prose)

* chore: drop BDFL framing for one-person team

* chore: tighten CONTRIBUTING, drop duplication
2026-05-10 12:32:22 +08:00
João Sousa Andrade
9dd5c6e5f3 Patch production dependency advisories (#856)
* Patch production dependency advisories

* ci: harden dependency installs with npm ci --ignore-scripts

- Replace npm install with npm ci --ignore-scripts across all workflows
  to enforce lockfile parity and block dependency postinstall scripts.
- Run npm run postinstall explicitly after install so our root patch
  script still applies (the only legitimate postinstall use in this
  repo); dep lifecycle scripts stay blocked.
- Add lockfile-lint and npm audit signatures to the lint job to catch
  registry-host tampering and verify cryptographic signatures.
- Regenerate package-lock.json to match bumped package.json versions.

* ci: drop --ignore-scripts; rely on npm ci + lockfile-lint + audit signatures

Lavamoat allow-scripts only traverses root deps; it can't see workspace
deps like electron, esbuild, sharp. Without proper monorepo support, an
allowlist would be incomplete and electron's binary download breaks.

Keep the rest of the security stack:
- npm ci (strict lockfile parity)
- lockfile-lint (resolved-host check)
- npm audit signatures (cryptographic verification)

Real script-blocking would need pnpm 10+ migration. Tracking separately.

* ci(nix-build): use PR head SHA so fork checkouts work

The previous `ref: ${{ github.head_ref || github.ref }}` made
actions/checkout fetch a branch name that only exists on the fork,
not origin. Use the PR head SHA instead — origin mirrors PR commits
via refs/pull/N/head, so this works for fork PRs without changing
the push-to-main auto-commit behavior.

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-10 11:45:24 +08:00
Mohamed Boudra
1042270298 ci(nix): merge Fix Nix hash into Nix Build, commit only after green build
Single workflow now: update lockfile + hash, build, then commit the fix on
main only if the build passed. Eliminates the structural first-failure where
Nix Build always raced the autofixer and lost.
2026-05-09 10:26:36 +07:00
Mohamed Boudra
b7d2e6d3ec Revert "ci: gate Claude Code install to Linux server-tests"
This reverts commit 09ed46b997.
2026-05-08 16:28:49 +07:00
Mohamed Boudra
09ed46b997 ci: gate Claude Code install to Linux server-tests
Installing claude-code globally on the Windows server-tests runner
exposed 12 pre-existing Windows-specific test failures that were
silently skipped before (rmdir EBUSY during cleanup, .CMD spawn
EINVAL via the SDK, plus the launch-regression test that asserts
claude is *not* on PATH).

The Linux e2e tests (model-catalog, live-preferences, models) are
the only ones that needed the binary to flip isAvailable() true;
none of those run on Windows. Skip the install on Windows so those
Windows compatibility gaps stay surfaced separately and don't block
the SDK upgrade.
2026-05-08 16:26:43 +07:00
Mohamed Boudra
a4d365c8f2 fix server-ci: install only claude-code, set SDK path in contract test
Two regressions from the previous CI install:

- Installing opencode-ai globally in server-ci/server-tests caused
  opencode-agent.test.ts (gated on \`hasOpenCode\`) to start running
  for the first time and surface real but unrelated test breakage.
  Trim the install to claude-code, which is all that's needed for the
  Claude e2e tests that motivated the change.

- claude-agent.integration.test.ts's supportedModels test calls the
  SDK's \`query()\` directly (bypassing ClaudeAgentClient) and didn't
  pass \`pathToClaudeCodeExecutable\`. The 0.2.71 SDK had a bundled
  cli.js fallback; 0.2.133 doesn't, so on Ubuntu CI it tries the
  per-platform binary path (\`...claude-agent-sdk-linux-x64-musl/claude\`)
  which isn't installed via the \`claude-code\` global. Resolve the
  binary explicitly in the test and pass it as the SDK option.
2026-05-08 16:14:28 +07:00
Mohamed Boudra
13538dd710 ci: install claude-code globally in server-tests
server-tests in ci.yml and Server CI both need claude on PATH for the
e2e tests that probe model catalogs and live preferences. cli-tests
already installs the trio (claude-code, codex, opencode-ai); server
tests now match.
2026-05-08 16:05:30 +07:00
Mohamed Boudra
cb2fe91998 fix CI fallout from claude-agent-sdk 0.2.133 upgrade
- mcp-server tests: MCP SDK 1.29 renamed RegisteredTool.callback → handler
- ci: install @anthropic-ai/claude-code globally for cli-tests, matching
  codex/opencode now that bundled fallback is gone
2026-05-08 15:18:24 +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
Mohamed Boudra
8d3946c36c perf(cli): run CLI E2E tests in parallel (#708)
* perf(cli): run E2E tests in parallel via worker pool

The custom CLI test runner ran 35 tsx test files sequentially, making the
cli-tests CI job the longest in main CI (~17 minutes). Each test file
already isolates its own daemon (ephemeral port + tmp PASEO_HOME), so
parallelism was just gated by the runner.

Replace the sequential recursion with a fixed-size worker pool (default
concurrency=4 to match GitHub Actions standard runners; override via
PASEO_CLI_TEST_CONCURRENCY). Buffer per-test stdout/stderr and flush as
a contiguous block on completion so concurrent output stays readable,
and report per-test wall clock plus the five slowest tests.

Local wall clock drops from ~12-15 minutes serial to ~2:49 with
concurrency=4. The slowest single test (05-agent-run, 49s) is now the
floor; CI should land near 4-5 minutes.

* fix(cli-tests): deflake 30-chat under load and shard CI across 3 runners

`chat wait` reads the latest message id and then subscribes for newer
messages. Under CI load the subprocess takes >1s to bootstrap, so the
old test's 250ms head start before posting "second message" raced
against that read. When the post landed first, the subprocess saw the
second message as latest and timed out waiting for a newer one. Replace
the brittle delay with a post-and-race loop: every iteration posts a
fresh "second message" and races a 250ms tick against the wait promise,
so whichever message lands after the snapshot wakes wait deterministically.

CI was also slower than local (10.3min vs 2.8min). On 4-vCPU GHA
runners, 35 sequential-CPU-time of ~1850s caps wall clock around 8 min
even at concurrency=4. Shard the suite across 3 GHA runners via matrix
strategy. The runner now reads PASEO_CLI_TEST_SHARD/SHARD_TOTAL and
distributes files into buckets — known long-pole tests
(05/06/11/13/14-...) round-robin first so they spread across shards
instead of clustering by their numeric prefixes, then the remainder
fills in the reverse direction to balance light load.

Local run is unchanged (single shard by default). Expected per-shard
wall on CI: ~2:30 + setup ≈ ~4:30 total.
2026-05-05 19:58:55 +07:00
Mohamed Boudra
e8a64fb569 ci: build server before desktop tests
f0d96f8e added packages/desktop/src/daemon/daemon-manager.test.ts,
which calls vi.mock("@getpaseo/server", ...). Vite's module
resolution still needs to find the package's main entry, so
desktop CI must build the server (and its dependency chain) before
running the tests. Mirrors the prebuild steps used by typecheck,
server-tests, and playwright.
2026-05-04 00:40:38 +07:00