mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
325 lines
15 KiB
Markdown
325 lines
15 KiB
Markdown
# Development
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js (see `.tool-versions` for exact version)
|
|
- npm workspaces (comes with Node)
|
|
|
|
## Running the dev server
|
|
|
|
```bash
|
|
npm run dev:server
|
|
npm run dev:app
|
|
npm run dev:desktop
|
|
```
|
|
|
|
Root checkout dev is intentionally split across terminals:
|
|
|
|
- `npm run dev:server` runs the daemon on `127.0.0.1:6768`.
|
|
- `npm run dev:app` runs Expo on `http://localhost:8081` and connects to the dev daemon.
|
|
- `npm run dev:desktop` runs its own Electron-flavored Expo server on the first free port from `8082` through `8089`. It never claims port `8081`.
|
|
|
|
`npm run dev` is only a shorthand for `npm run dev:server`. Keep `127.0.0.1:6767` for the packaged app and production-style `~/.paseo` state.
|
|
|
|
### PASEO_HOME
|
|
|
|
`PASEO_HOME` is the directory that holds runtime state (agents, worktrees, workspace config, sockets, daemon log). Resolution rules:
|
|
|
|
- The **server itself** (e.g. when launched by the desktop app or `npm run start`) defaults to `~/.paseo` (see `packages/server/src/server/paseo-home.ts`).
|
|
- **Repo dev scripts** default to `$ROOT/.dev/paseo-home`, where `$ROOT` is the current checkout or worktree root. This keeps all dev state scoped to the checkout instead of the packaged desktop app.
|
|
- **`npm run cli -- ...`** runs through the same dev-home wrapper as the dev scripts, so the in-repo CLI automatically targets the current checkout's `.dev/paseo-home` and configured dev daemon endpoint.
|
|
- **Paseo-created worktrees** seed `$PASEO_WORKTREE_PATH/.dev/paseo-home` from `$PASEO_SOURCE_CHECKOUT_PATH/.dev/paseo-home` by copying durable JSON metadata. Runtime files like pid files, sockets, and logs are not copied.
|
|
|
|
Override knobs:
|
|
|
|
```bash
|
|
PASEO_HOME=~/.paseo-blue npm run dev # explicit home
|
|
PASEO_DEV_SEED_HOME=/path/to/home npm run dev # seed from a different source home
|
|
PASEO_DEV_RESET_HOME=1 npm run dev # clear and reseed the derived worktree home
|
|
```
|
|
|
|
### Daemon endpoints
|
|
|
|
- Stable daemon launched by the desktop app: `localhost:6767`.
|
|
- Root checkout dev daemon: `localhost:6768`.
|
|
- Root checkout Expo: `http://localhost:8081`.
|
|
- Root checkout desktop dev Expo: first free port from `8082` through `8089`.
|
|
- `npm run dev` (Windows): `localhost:6767` for the daemon.
|
|
|
|
In Paseo-managed worktree services, use the injected service environment rather than hardcoded root checkout ports.
|
|
|
|
### Desktop renderer profiling
|
|
|
|
`npm run dev:desktop` starts Electron with Chromium remote debugging enabled on
|
|
`http://127.0.0.1:9223` so renderer CPU profiles can be captured through CDP.
|
|
It launches its own Electron-flavored Expo server and passes that URL to Electron.
|
|
Override the CDP port with `PASEO_ELECTRON_REMOTE_DEBUGGING_PORT` when `9223` is busy.
|
|
|
|
### React render profiling
|
|
|
|
The app has a gated React render profiler in
|
|
`packages/app/src/utils/render-profiler.tsx`. Wrap the component boundary you want
|
|
to measure with `RenderProfile`, then open the app with `?renderProfile=1`. When
|
|
the query param is absent, `RenderProfile` returns children directly and records
|
|
nothing.
|
|
|
|
Captured samples are exposed on `globalThis.__PASEO_RENDER_PROFILE__`. Call
|
|
`globalThis.__PASEO_RESET_RENDER_PROFILE__?.()` after warm-up and before the
|
|
interaction you want to measure. If a memo comparator or subscription boundary
|
|
needs explanation, call `recordRenderProfileReasons(id, reasons)` while profiling;
|
|
reason counts are exposed on `globalThis.__PASEO_RENDER_PROFILE_REASONS__`.
|
|
|
|
Use this workflow for any render investigation:
|
|
|
|
1. Add stable `RenderProfile` boundaries around the suspected root and expensive
|
|
children. Keep IDs specific enough to compare before and after.
|
|
2. Reproduce against real app state, not toy fixtures, whenever practical.
|
|
3. Record an idle baseline first. If idle is noisy, fix or account for that
|
|
before optimizing the interaction.
|
|
4. Warm up the route, reset profiler samples, run the exact interaction, then
|
|
compare `actualDuration`, render counts, and per-commit samples.
|
|
5. When a memo boundary still renders, record reasons before changing code. Do
|
|
not guess from object identity alone.
|
|
6. Keep changes that move the measured profile. Remove probes or memo wrappers
|
|
that do not move the number.
|
|
|
|
What this caught during the workspace tab investigation:
|
|
|
|
- A large apparent workspace cost was real interaction work, not daemon noise;
|
|
the idle baseline stayed near zero.
|
|
- The expensive stream rerender was mostly prop identity churn from pane context
|
|
callbacks and capability objects, not new stream data.
|
|
- Stabilizing provider actions at the pane boundary helped because every mounted
|
|
panel consumes that context.
|
|
- Comparing value-shaped capability flags beat preserving object identity through
|
|
unrelated stores.
|
|
- Some plausible fixes did not pay off: memoizing the tab row and composer draft
|
|
object barely moved the profile, so they were removed.
|
|
|
|
Existing scenario script: workspace agent/terminal tab switching. Start Expo on
|
|
web, keep a daemon available, then run:
|
|
|
|
```bash
|
|
PASEO_PROFILE_SERVER_ID=<server-id> \
|
|
PASEO_PROFILE_WORKSPACE_ID=<workspace-path> \
|
|
PASEO_PROFILE_AGENT_ID=<agent-id> \
|
|
npm run profile:workspace-tabs --workspace=@getpaseo/app
|
|
```
|
|
|
|
This script opens the app with `?renderProfile=1`, creates a temporary terminal
|
|
tab, switches between a real agent and that terminal, prints aggregated React
|
|
Profiler timings, then removes the temporary terminal. It is an example of the
|
|
workflow above, not the only way to use the profiler. Useful knobs:
|
|
|
|
```bash
|
|
PASEO_PROFILE_APP_URL=http://localhost:19010 # Expo web URL
|
|
PASEO_PROFILE_SWITCH_COUNT=1 # number of agent/terminal switch pairs
|
|
PASEO_PROFILE_SWITCH_WAIT_MS=250 # delay after each click
|
|
PASEO_PROFILE_IDLE_WAIT_MS=3000 # idle baseline before switching
|
|
PASEO_PROFILE_DUMP_COMMITS=1 # include per-commit profiler samples
|
|
```
|
|
|
|
### Desktop macOS compositor watchdog
|
|
|
|
macOS display sleep can leave Chromium's GPU-process display link — the vsync
|
|
source that drives frame production — stuck on a stale display. The compositor
|
|
then stops producing frames and the window looks frozen: unresponsive to clicks
|
|
and keys even though the renderer and every process stay alive. It self-recovers
|
|
after a few minutes, which is too long for a foreground app.
|
|
|
|
`setupDarwinCompositorWatchdog`
|
|
(`packages/desktop/src/window/compositor-watchdog/index.ts`) guards against
|
|
this. It polls the renderer for frame production every couple of seconds and,
|
|
after a sustained stall while the window is visible and unlocked, restarts the
|
|
GPU process so Chromium rebuilds the display link. The probe is skipped while
|
|
the screen is locked or the window is hidden or minimized, since a window
|
|
legitimately stops producing frames then.
|
|
|
|
The watchdog deliberately leaves background throttling **enabled**. Calling
|
|
`webContents.setBackgroundThrottling(false)` would keep the compositor producing
|
|
frames non-stop, pinning ProMotion displays at 120Hz forever and draining the
|
|
battery while the app is idle — so do not re-add it. The probe's visibility
|
|
guards already prevent throttling from causing a false stall.
|
|
|
|
### Daemon logs
|
|
|
|
Check `$PASEO_HOME/daemon.log` for daemon logs. The default level is `info`; set
|
|
`PASEO_LOG_LEVEL=trace` before launching the daemon when you need full provider,
|
|
session, and agent-manager traces for stuck-state debugging.
|
|
|
|
The supervisor rotates `daemon.log`. Persisted `log.file.rotate` settings in
|
|
`$PASEO_HOME/config.json` win first. Without persisted config, the optional
|
|
`PASEO_LOG_ROTATE_SIZE` and `PASEO_LOG_ROTATE_COUNT` env vars override the
|
|
defaults. The default rotation is `10m` x `3` files everywhere.
|
|
|
|
## paseo.json service scripts
|
|
|
|
`worktree.setup` and `worktree.teardown` accept either a multiline shell script or an array
|
|
of commands. Both run sequentially.
|
|
|
|
```json
|
|
{
|
|
"worktree": {
|
|
"setup": "npm ci\ncp \"$PASEO_SOURCE_CHECKOUT_PATH/.env\" .env\nnpm run db:migrate",
|
|
"teardown": "npm run db:drop || true"
|
|
}
|
|
}
|
|
```
|
|
|
|
Every `scripts` entry with `"type": "service"` receives these environment variables:
|
|
|
|
| Variable | Value |
|
|
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
| `PASEO_SERVICE_<NAME>_URL` | Proxied URL for a declared peer service. Prefer this for peer discovery; it survives peer restarts. |
|
|
| `PASEO_SERVICE_<NAME>_PORT` | Raw ephemeral port for a declared peer service. Use only as a bypass escape hatch; it can go stale if that peer restarts. |
|
|
| `PASEO_URL` | Self alias for `PASEO_SERVICE_<SELF>_URL`. |
|
|
| `PASEO_PORT` | Self alias for `PASEO_SERVICE_<SELF>_PORT`. |
|
|
| `HOST` | Bind host for the service process. |
|
|
|
|
Service proxy hostnames use the double-dash shape: `web--feature-auth--project.localhost` or, on the default branch, `web--project.localhost`. Optional public aliases use the same leftmost label under the configured public base host.
|
|
|
|
`<NAME>` is normalized from the script name by uppercasing it, replacing each run of non-`A-Z0-9` characters with `_`, and trimming leading or trailing `_`. For example, `app-server` and `app.server` both normalize to `APP_SERVER`; that collision fails at spawn time with an actionable error.
|
|
|
|
`PORT` is not injected by default. If a framework requires `PORT`, set it in the command:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"web": {
|
|
"type": "service",
|
|
"command": "PORT=$PASEO_PORT npm run dev:web"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Built workspace packages
|
|
|
|
Package imports resolve through package exports to compiled `dist/` output, not sibling `src/` files. This is true in local dev and in published packages: the app, daemon, CLI, and SDK consumers should all exercise the same runtime paths.
|
|
|
|
`npm run dev:server` builds the server-side workspace packages once, then keeps `@getpaseo/protocol` and `@getpaseo/client` fresh with TypeScript watch builds while the daemon runs. If you change protocol schemas or client code outside that watch workflow, rebuild the producer before trusting runtime behavior.
|
|
|
|
Use the named root build targets instead of remembering workspace dependency chains:
|
|
|
|
```bash
|
|
npm run build:client # protocol -> client
|
|
npm run build:server-deps # highlight -> relay -> protocol -> client
|
|
npm run build:server # server-deps -> server -> cli
|
|
npm run build:app-deps # highlight -> protocol -> client -> expo-two-way-audio
|
|
```
|
|
|
|
Use `npm run build:server` whenever you have changed any daemon/server-facing package and need clean cross-package types or runtime behavior.
|
|
|
|
For tighter loops, you can rebuild a single workspace:
|
|
|
|
- Changed `packages/protocol/src/*` or `packages/client/src/*`: `npm run build:client`.
|
|
- Changed `packages/server/src/*`, `packages/cli/src/*`, `packages/relay/src/*`, or `packages/highlight/src/*`: `npm run build:server`.
|
|
- Changed app build dependencies: `npm run build:app-deps`.
|
|
|
|
## ACP provider catalog versions
|
|
|
|
The in-app ACP provider catalog pins package-runner entries (`npx`, `npm exec`,
|
|
and `uvx`) to exact package versions. Run the drift checker regularly — and
|
|
before releases — so catalog installs do not sit on stale agent versions:
|
|
|
|
```bash
|
|
npm run acp:version-drift # report stale/non-exact package pins
|
|
npm run acp:version-drift:check # same, exits non-zero on drift
|
|
npm run acp:version-drift:update # rewrite catalog pins to latest exact versions
|
|
```
|
|
|
|
The checker updates only package-runner catalog entries. Providers that use a
|
|
preinstalled binary such as `opencode acp`, `cursor-agent acp`, or `goose acp`
|
|
are reported as skipped because their versions are owned by the user's local
|
|
install.
|
|
|
|
## CLI reference
|
|
|
|
Use `npm run cli` to run the in-repo CLI from source (`npx tsx packages/cli/src/index.ts`). The script wraps the CLI with `scripts/dev-home.sh`, so it automatically uses this checkout's `.dev/paseo-home` and dev daemon endpoint unless you pass an explicit override. The globally installed `paseo` binary on macOS is a symlink into the installed Paseo desktop app, not this checkout — use it to drive the desktop's built-in daemon, but use `npm run cli` when you want to talk to the CLI you are editing.
|
|
|
|
```bash
|
|
npm run cli -- ls -a -g # List all agents globally
|
|
npm run cli -- ls -a -g --json # Same, as JSON
|
|
npm run cli -- inspect <id> # Show detailed agent info
|
|
npm run cli -- logs <id> # View agent timeline
|
|
npm run cli -- daemon status # Check daemon status
|
|
```
|
|
|
|
Use `--host <host:port>` to point the CLI at a different daemon:
|
|
|
|
```bash
|
|
npm run cli -- --host localhost:7777 ls -a
|
|
```
|
|
|
|
## Agent state
|
|
|
|
Agent data lives at:
|
|
|
|
```
|
|
$PASEO_HOME/agents/{cwd-with-dashes}/{agent-id}.json
|
|
```
|
|
|
|
Find an agent by ID:
|
|
|
|
```bash
|
|
find $PASEO_HOME/agents -name "{agent-id}.json"
|
|
```
|
|
|
|
Find by content:
|
|
|
|
```bash
|
|
rg -l "some title text" $PASEO_HOME/agents/
|
|
```
|
|
|
|
## Provider session files
|
|
|
|
Get the session ID from the agent JSON (`persistence.sessionId`), then:
|
|
|
|
**Claude:**
|
|
|
|
```
|
|
~/.claude/projects/{cwd-with-dashes}/{session-id}.jsonl
|
|
```
|
|
|
|
**Codex:**
|
|
|
|
```
|
|
~/.codex/sessions/{YYYY}/{MM}/{DD}/rollout-{timestamp}-{session-id}.jsonl
|
|
```
|
|
|
|
## Testing with Playwright MCP
|
|
|
|
Point Playwright MCP at the running Expo web target. For root checkout dev, `npm run dev:app` reserves `http://localhost:8081`. For Paseo-managed worktree app services, use the service URL or port shown by Paseo for that worktree.
|
|
|
|
Do NOT use browser history (back/forward). Always navigate by clicking UI elements or using `browser_navigate` with the full URL — the app uses client-side routing and browser history breaks state.
|
|
|
|
## App web deploys
|
|
|
|
`packages/app` exports a single-page Expo web app and deploys the `dist/`
|
|
directory to Cloudflare Pages with `npm run deploy:web --workspace=@getpaseo/app`.
|
|
|
|
PWA install metadata lives in `packages/app/public/manifest.json` and is linked
|
|
from `packages/app/public/index.html`. Keep the install icons in `public/` so
|
|
Cloudflare serves them from stable root URLs after `expo export`.
|
|
|
|
Do not add service-worker caching casually. Paseo is a live control surface for
|
|
agents, and an aggressive service worker can strand installed users on stale web
|
|
code. If offline behavior becomes a product requirement, add it deliberately
|
|
with an update strategy and test the installed-app upgrade path.
|
|
|
|
## Expo troubleshooting
|
|
|
|
```bash
|
|
npx expo-doctor
|
|
```
|
|
|
|
Diagnoses version mismatches and native module issues.
|
|
|
|
## Typecheck
|
|
|
|
Always run typecheck after changes:
|
|
|
|
```bash
|
|
npm run typecheck
|
|
```
|