chore: reformat codebase with oxfmt

Apply prettier-compatible formatting across the repo to match the
incoming oxfmt configuration. Mechanical reformat only — no logic
changes. Covers YAML quote normalization, package.json key sorting,
Markdown/TOML formatting, and minor TS whitespace tweaks.
This commit is contained in:
Mohamed Boudra
2026-04-23 21:44:48 +07:00
parent 9b25f8fc36
commit ce4b0d54c7
81 changed files with 1171 additions and 1009 deletions

View File

@@ -3,10 +3,10 @@ name: Deploy App
on:
push:
tags:
- 'v*'
- '!v*-beta.*'
- 'app-v*'
- '!app-v*-beta.*'
- "v*"
- "!v*-beta.*"
- "app-v*"
- "!app-v*-beta.*"
workflow_dispatch:
jobs:
@@ -18,10 +18,10 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
registry-url: 'https://npm.pkg.github.com'
scope: '@boudra'
node-version: "22"
cache: "npm"
registry-url: "https://npm.pkg.github.com"
scope: "@boudra"
- name: Install dependencies
run: npm install --workspace=@getpaseo/app --include-workspace-root

View File

@@ -4,8 +4,8 @@ on:
push:
branches: [main]
paths:
- 'packages/relay/**'
- '.github/workflows/deploy-relay.yml'
- "packages/relay/**"
- ".github/workflows/deploy-relay.yml"
workflow_dispatch:
jobs:
@@ -17,8 +17,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm install --workspace=@getpaseo/relay --include-workspace-root
@@ -30,4 +30,3 @@ jobs:
run: cd packages/relay && npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

View File

@@ -4,12 +4,12 @@ on:
push:
branches: [main]
paths:
- 'CHANGELOG.md'
- 'packages/website/**'
- 'package.json'
- 'package-lock.json'
- 'patches/**'
- '.github/workflows/deploy-website.yml'
- "CHANGELOG.md"
- "packages/website/**"
- "package.json"
- "package-lock.json"
- "patches/**"
- ".github/workflows/deploy-website.yml"
release:
types: [published]
workflow_dispatch:
@@ -24,8 +24,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm install --workspace=@getpaseo/website --include-workspace-root

View File

@@ -3,21 +3,21 @@ name: Desktop Release
on:
push:
tags:
- 'v*'
- 'desktop-v*'
- 'desktop-macos-v*'
- 'desktop-linux-v*'
- 'desktop-windows-v*'
- "v*"
- "desktop-v*"
- "desktop-macos-v*"
- "desktop-linux-v*"
- "desktop-windows-v*"
workflow_dispatch:
inputs:
tag:
description: 'Existing tag to build (e.g. v0.1.0)'
description: "Existing tag to build (e.g. v0.1.0)"
required: true
type: string
platform:
description: 'Optional desktop platform to build.'
description: "Optional desktop platform to build."
required: false
default: 'all'
default: "all"
type: choice
options:
- all
@@ -31,8 +31,8 @@ concurrency:
env:
SOURCE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
DESKTOP_WORKSPACE: '@getpaseo/desktop'
DESKTOP_PACKAGE_PATH: 'packages/desktop'
DESKTOP_WORKSPACE: "@getpaseo/desktop"
DESKTOP_PACKAGE_PATH: "packages/desktop"
jobs:
create-release:
@@ -100,11 +100,11 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com'
scope: '@boudra'
registry-url: "https://npm.pkg.github.com"
scope: "@boudra"
- name: Install JS dependencies
run: npm ci
@@ -278,11 +278,11 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com'
scope: '@boudra'
registry-url: "https://npm.pkg.github.com"
scope: "@boudra"
- name: Install JS dependencies
run: npm ci
@@ -346,11 +346,11 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com'
scope: '@boudra'
registry-url: "https://npm.pkg.github.com"
scope: "@boudra"
- name: Install JS dependencies
run: npm ci

View File

@@ -4,12 +4,12 @@ on:
push:
branches: [main]
paths:
- 'package.json'
- 'package-lock.json'
- "package.json"
- "package-lock.json"
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
- "package.json"
- "package-lock.json"
permissions:
contents: write
@@ -25,8 +25,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
node-version: "22"
cache: "npm"
- uses: cachix/install-nix-action@v31
with:

View File

@@ -4,27 +4,27 @@ on:
push:
branches: [main]
paths:
- 'nix/**'
- 'flake.nix'
- 'flake.lock'
- 'package.json'
- 'package-lock.json'
- 'packages/highlight/**'
- 'packages/server/**'
- 'packages/relay/**'
- 'packages/cli/**'
- "nix/**"
- "flake.nix"
- "flake.lock"
- "package.json"
- "package-lock.json"
- "packages/highlight/**"
- "packages/server/**"
- "packages/relay/**"
- "packages/cli/**"
pull_request:
branches: [main]
paths:
- 'nix/**'
- 'flake.nix'
- 'flake.lock'
- 'package.json'
- 'package-lock.json'
- 'packages/highlight/**'
- 'packages/server/**'
- 'packages/relay/**'
- 'packages/cli/**'
- "nix/**"
- "flake.nix"
- "flake.lock"
- "package.json"
- "package-lock.json"
- "packages/highlight/**"
- "packages/server/**"
- "packages/relay/**"
- "packages/cli/**"
permissions:
contents: read

View File

@@ -4,17 +4,17 @@ on:
push:
branches: [main]
paths:
- 'packages/server/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/server-ci.yml'
- "packages/server/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/server-ci.yml"
pull_request:
branches: [main]
paths:
- 'packages/server/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/server-ci.yml'
- "packages/server/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/server-ci.yml"
jobs:
test:
@@ -27,8 +27,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
node-version: "20"
cache: "npm"
- name: Fetch origin/main (worktree tests)
run: git fetch --no-tags origin main:refs/remotes/origin/main

View File

@@ -3,12 +3,15 @@
## 0.1.62 - 2026-04-23
### Added
- Sidebar warning when your app and daemon versions drift apart, with a shortcut to settings.
### Improved
- Workspaces appear in the sidebar immediately on startup instead of waiting for git registration.
### Fixed
- Pull request status resolves correctly for PRs opened from forks.
- Installing the paseo CLI from the macOS desktop app now works in packaged builds.
- Agents launched from the desktop app no longer inherit Electron-only environment variables.
@@ -16,10 +19,12 @@
## 0.1.61 - 2026-04-23
### Added
- `additionalModels` option in provider config lets you add or relabel models without replacing the full list — entries merge with runtime-discovered models (ACP) or your static `models` list. See the [Providers docs](https://paseo.sh/docs/providers).
- New [Providers docs page](https://paseo.sh/docs/providers) covering first-class providers and every custom provider config pattern in one place.
### Improved
- Pi loads your installed extensions on startup so their models show up in the model picker.
- Resizing the explorer sidebar no longer rerenders the rest of the workspace.
- Images in assistant messages (both file paths and inline data URLs) persist as local attachments and open in the file pane.
@@ -27,6 +32,7 @@
## 0.1.60 - 2026-04-22
### Added
- Scripts and services per worktree — define named commands in `paseo.json`, and long-running services get supervised with their own ports and nice proxy URLs like `http://web.my-app.localhost:6767`. See the [worktrees guide](https://paseo.sh/docs/worktrees).
- Launch scripts and services for a worktree directly from the workspace header.
- New Setup tab in every workspace showing setup, teardown, and script progress live.
@@ -41,6 +47,7 @@
- New-workspace picker ranks branches by recency with fast search.
### Improved
- Workspace and tab switching are dramatically faster on desktop and mobile — you can keep many workspaces open in parallel without lag.
- Agent streams render more smoothly during heavy tool output.
- App startup routes through a stable connection and lands on the right screen without flicker.
@@ -55,6 +62,7 @@
- GitHub branch and PR picker loads faster — queries are deferred until the picker opens.
### Fixed
- Composer textarea shrinks back down after sending on web.
- New workspace drafts clear after submit instead of sticking around.
- Replacing a running agent cleans up the previous one without leaving it behind.
@@ -76,18 +84,21 @@
## 0.1.59 - 2026-04-16
### Added
- Opus 4.7 in the Claude model picker, with a 1M-context variant.
- Extra High reasoning effort for Opus 4.7, between High and Max.
## 0.1.58 - 2026-04-16
### Added
- Markdown files render as formatted markdown in the file pane. ([#427](https://github.com/getpaseo/paseo/pull/427) by [@aaronflorey](https://github.com/aaronflorey))
- Cmd+L (Ctrl+L on Windows/Linux) focuses the agent message input.
- Provider models refresh on a freshness TTL; Settings shows last-updated time and any fetch errors. ([#426](https://github.com/getpaseo/paseo/pull/426))
- `disallowedTools` option in provider config to block specific tools from an agent.
### Improved
- Windows: agents launch reliably from npm `.cmd` shims, paths with spaces, and JSON config args — fixes `spawn EINVAL` startup errors. ([#454](https://github.com/getpaseo/paseo/pull/454))
- OpenCode permission prompts include the requesting tool's context. ([#398](https://github.com/getpaseo/paseo/pull/398) by [@aaronflorey](https://github.com/aaronflorey))
- OpenCode todo and compaction events render in the timeline. ([#429](https://github.com/getpaseo/paseo/pull/429) by [@aaronflorey](https://github.com/aaronflorey))
@@ -97,6 +108,7 @@
- Native scrollbars match the active theme across all web views. ([#399](https://github.com/getpaseo/paseo/pull/399) by [@ethersh](https://github.com/ethersh))
### Fixed
- Code file previews can be selected and copied on iOS. ([#447](https://github.com/getpaseo/paseo/pull/447) by [@muzhi1991](https://github.com/muzhi1991))
- File preview no longer shows stale content when reopening the same file. ([#411](https://github.com/getpaseo/paseo/pull/411) by [@muzhi1991](https://github.com/muzhi1991))
- File explorer reinitialises when the client reconnects after a page refresh. ([#442](https://github.com/getpaseo/paseo/pull/442) by [@1996fanrui](https://github.com/1996fanrui))
@@ -110,12 +122,14 @@
## 0.1.56 - 2026-04-14
### Fixed
- Projects with empty git repositories (no commits yet) no longer crash the app on startup.
- A single problematic project can no longer prevent the rest of your workspaces from loading.
## 0.1.55 - 2026-04-14
### Added
- Provider profiles — define custom providers in your Paseo config that appear alongside built-ins. Override a built-in's binary, env, or models, or create entirely new providers. See the [configuration guide](https://github.com/getpaseo/paseo/blob/main/docs/CUSTOM-PROVIDERS.md).
- ACP agent support — add any ACP-compatible agent to Paseo with `extends: "acp"` in your provider config. No code changes needed.
- Choose provider and model when creating scheduled agents.
@@ -123,12 +137,14 @@
- Cmd+, (Ctrl+, on Windows/Linux) opens settings.
### Improved
- Git operations are dramatically faster — workspace status, PR checks, and branch data all use a shared cached snapshot service instead of shelling out to git on every request. Running 20+ workspaces simultaneously is now smooth.
- Windows support — the daemon and CLI run natively on Windows with proper shell quoting, executable resolution, and path handling.
- iPad and tablet layouts work correctly across all screen sizes.
- IME composition (Chinese, Japanese, Korean input) no longer submits prematurely when pressing Enter.
### Fixed
- Creating a worktree no longer briefly flashes it as a standalone project before placing it under the correct repository.
- Worktree creation spinner stays visible throughout the process instead of disappearing on mouse-out.
- Workspace navigation updates correctly when switching between workspaces in the same project.
@@ -138,13 +154,16 @@
## 0.1.54 - 2026-04-12
### Added
- Inline image previews in agent messages — screenshots and images generated by agents render directly in the conversation instead of showing as raw markdown links.
### Improved
- Paseo tools are no longer injected into agents by default — opt in from Settings when you need agent-to-agent orchestration.
- Agent provider and mode are now resolved server-side, so CLI commands like `paseo run` use consistent defaults without client-side lookups.
### Fixed
- Shift+Enter now correctly inserts a newline in agent terminal input instead of submitting.
- Windows: MCP configuration is no longer mangled when spawning Claude agents.
- Branch ahead/behind count no longer errors for branches with no remote tracking branch.
@@ -152,6 +171,7 @@
## 0.1.53 - 2026-04-12
### Added
- Agents get Paseo tools automatically — every new agent gets access to terminals, schedules, worktrees, and other agents through MCP. Toggle it off in Settings under "Inject Paseo tools".
- Git pull — pull remote changes directly from the workspace header. Promoted to the primary action when your branch is behind origin.
- Child agent notifications — parent agents are automatically notified when a child agent finishes, errors, or needs permission approval.
@@ -160,6 +180,7 @@
- Keyboard shortcut to cycle themes.
### Improved
- Unavailable git actions now explain why in a toast instead of being silently greyed out.
- Streaming markdown on mobile renders significantly faster.
- Sidebar, branch switcher, and agent panel no longer re-render unnecessarily — noticeable on large workspaces.
@@ -167,6 +188,7 @@
- Relay and pairing URLs are stripped from daemon logs.
### Fixed
- Closed agent tabs no longer reappear after reconnecting.
- Desktop notification badge counts match across all workspaces.
- Host switcher status syncs correctly when switching between hosts.
@@ -174,11 +196,13 @@
## 0.1.52 - 2026-04-10
### Added
- Theme selector — choose from six themes including Midnight, Claude, and Ghostty dark variants.
- Branch switching — switch git branches directly from the workspace header, with automatic stash and restore for uncommitted changes.
- Auto-download updates — desktop updates download silently in the background so they're ready to install when you are.
### Fixed
- Layout now responds correctly when resizing the window or rotating a tablet — previously the app could get stuck in mobile layout on a large screen.
- Terminal no longer causes massive memory spikes from snapshot thrashing during heavy output.
- Typing in the terminal works reliably — special keys, Ctrl combos, and paste are handled natively by the terminal emulator.
@@ -191,11 +215,13 @@
## 0.1.51 - 2026-04-09
### Added
- Image attachments for OpenCode — attach screenshots and images to OpenCode agent prompts.
- WebStorm — added to the "Open in editor" list alongside Cursor, VS Code, and Zed.
- Send behavior setting — choose whether pressing Enter while an agent is running interrupts immediately or queues your message.
### Fixed
- Model selector no longer crashes on iPad.
- Pairing now uses the correct hostname, fixing connection failures on some network setups.
- OpenCode agents show the correct terminal state and refresh models reliably.
@@ -207,6 +233,7 @@
## 0.1.50 - 2026-04-07
### Added
- Context window meter — see how much of the context window your agent has used, with color thresholds at 70% and 90%. Works with Claude Code, Codex, and OpenCode.
- Open in editor — jump from any workspace straight into Cursor, VS Code, Zed, or your file manager. Paseo remembers your choice.
- Side-by-side diffs — toggle between unified and split-column diff views, with a whitespace visibility option.
@@ -215,6 +242,7 @@
- Background git fetch — ahead/behind counts in the Changes pane stay up to date automatically.
### Improved
- Workspaces load instantly on connect instead of waiting for a full sync.
- File explorer and diff pane remember which folders are expanded when you switch tabs.
- Closing a workspace tab is now instant.
@@ -222,6 +250,7 @@
- Reload agent moved away from the close button to prevent accidental taps.
### Fixed
- Voice mode no longer drifts into false speech detection during long sessions.
- Garbled overlapping text on plan cards.
- Changes pane could show stale diffs when working with git worktrees.
@@ -234,6 +263,7 @@
## 0.1.49 - 2026-04-07
### Fixed
- Models and providers now load reliably on first connect instead of requiring a manual refresh.
- Model picker only shows models from the agent's own provider, not every provider on the server.
- Model lists stay consistent regardless of which screen you open first.
@@ -241,17 +271,20 @@
## 0.1.48 - 2026-04-05
### Added
- Provider diagnostics — tap a provider in Settings to see binary path, version, model count, and status at a glance. Helps troubleshoot why an agent type isn't available.
- Provider snapshot system — daemon now pushes real-time provider availability and model lists to the app, replacing the old poll-based approach. Models and modes update live as providers come online or go offline.
- Codex question handling — Codex agents can now ask the user questions mid-session (e.g. "which file?") and receive answers inline, matching the Claude Code question flow.
- Reload tab action — right-click a workspace tab to reload its agent list without restarting the app.
### Improved
- Model selector redesigned — grouped by provider with status badges, search, and better touch targets on mobile.
- Enter key now submits question card answers and confirms dictation, matching the expected keyboard flow.
- Removed noisy agent lifecycle toasts that fired on every state change.
### Fixed
- Desktop app now resolves the user's full login shell environment at startup, fixing tools like `codex`, `node`, `bun`, and `direnv` not being found when Paseo is launched from Finder or Dock. Terminals spawned by Paseo now inherit the same PATH and environment variables as a normal terminal session. Approach adapted from VS Code's battle-tested shell environment resolution.
- Input field on running agent screens now correctly receives keyboard focus.
- Mobile model selector alignment and sizing.
@@ -259,6 +292,7 @@
## 0.1.47 - 2026-04-05
### Fixed
- Voice TTS in Electron — sherpa now requests copied buffers and the voice MCP bridge sets `ELECTRON_RUN_AS_NODE`, preventing "external buffers not allowed" crashes.
- QR pairing in desktop — CLI JSON output parsing now tolerates Node deprecation warnings in stdout.
- STT segment race condition — segment ID and audio buffer are snapshotted before the async transcription call, so rapid commits no longer interleave.
@@ -267,6 +301,7 @@
## 0.1.46 - 2026-04-04
### Fixed
- Voice activation in packaged builds — Silero VAD model is now copied out of the Electron asar archive so native code can read it.
- App version sent in probe client hello so the daemon's version gate no longer hides Pi/Copilot from reconnected sessions.
- `worktreeRoot` schema made backward-compatible for old clients and daemons that don't send the field.
@@ -275,6 +310,7 @@
## 0.1.45 - 2026-04-04
### Added
- Pi (pi.dev) agent provider — connect Pi as a new ACP-based agent type with thinking levels and tool call support.
- Copilot agent provider re-enabled after ACP compatibility fixes.
- `paseo .` and `paseo <path>` open the desktop app with the given project, similar to `code .`.
@@ -287,11 +323,13 @@
- Setup hint and paseo.sh link on the mobile welcome screen so new App Store users know what to do next.
### Improved
- Desktop startup is faster — existing daemon connections are raced against bootstrap so the app is usable sooner.
- Settings sections reordered for better grouping (integrations and daemon together).
- Sidebar projects and workspaces now persist across sessions, with a context menu to remove projects.
### Fixed
- Sidebar crash when switching iOS theme (Unistyles/Reanimated interaction).
- Silero VAD crash caused by external buffer mode in CircularBuffer.
- Bulk close now correctly archives stored agents instead of leaving orphans.
@@ -308,6 +346,7 @@
## 0.1.44 - 2026-04-03
### Fixed
- Desktop app now stops the daemon cleanly before auto-update restarts.
- Disabled claude-acp and copilot providers from the agent registry.
- Keyboard focus scope resolution now checks multiple candidates for broader compatibility.
@@ -317,16 +356,19 @@
## 0.1.43 - 2026-04-02
### Added
- Copilot agent support via ACP base provider — connect GitHub Copilot as a new agent type.
- Searchable model favorites — quickly find and pin preferred models.
- Slash command support for OpenCode agents.
### Improved
- Refined model selector UX with better mobile sheet behavior.
- Workspace status now uses amber alert styling for "needs input" state.
- Themed scrollbar on message input for consistent styling.
### Fixed
- Ctrl+C/V copy and paste now works correctly in the terminal on Windows and Linux.
- Shell arguments with spaces are now properly quoted on Windows.
- Claude models with 1M context support are now correctly reported.
@@ -334,11 +376,13 @@
## 0.1.42 - 2026-04-01
### Fixed
- Fixed Claude Code failing to launch on Windows when installed to a path with spaces (e.g. `C:\Program Files\...`).
## 0.1.41 - 2026-04-01
### Fixed
- Fixed agent spawning on Windows — all providers (Claude, Codex, OpenCode) now use shell mode so npm shims and `.cmd` wrappers resolve correctly.
- Fixed terminal creation on Windows defaulting to a Unix shell instead of `cmd.exe`.
- Fixed path handling across the app to support Windows drive-letter paths (`C:\...`) and UNC paths (`\\...`).
@@ -350,18 +394,22 @@
- Removed the 40-item cap on activity timeline output so long agent sessions display their full history.
### Improved
- Improved light mode theming with dedicated workspace background, scrollbar handle colors, and lighter shadows.
- Window controls overlay on Windows/Linux reduced from 48px to 29px height for a more compact titlebar.
## 0.1.40 - 2026-04-01
### Added
- Workspace tabs can now be closed in batches.
### Improved
- Provider model lists are now cached per server and provider, reducing redundant model lookups in the UI.
### Fixed
- OpenCode reasoning content no longer appears duplicated as assistant text.
- Daemon no longer crashes when a Codex binary is missing or fails to spawn.
- Archive tab now correctly reconciles agent visibility after archiving.
@@ -372,61 +420,73 @@
## 0.1.39 - 2026-03-30
### Added
- **Terminal management from the CLI** — new `paseo terminal` command group lets you list, create, and interact with workspace terminals without leaving your terminal.
- **Material file icons in the explorer** — the file explorer tree now shows language-specific icons (TypeScript, JSON, Markdown, etc.) so you can spot files at a glance.
### Fixed
- Fixed iOS sidebar scroll flicker caused by redundant overflow clipping.
- Centralized window controls padding into a shared hook, eliminating layout inconsistencies across platforms.
## 0.1.38 - 2026-03-30
### Fixed
- Fixed daemon startup race where the app could time out connecting on first launch because the PID file advertised a listen address before the server was ready.
- Fixed daemon log rotation losing startup traces — trace-level WebSocket logs no longer include full message payloads.
## 0.1.37 - 2026-03-29
### Added
- Custom window controls on Windows and Linux — the native titlebar is replaced with overlay controls that match the app's design.
- Desktop file logging with electron-log for easier debugging of daemon and app issues.
### Fixed
- Fixed broken PATH propagation and Claude binary resolution on Windows.
- Dictation errors now show a visible toast instead of failing silently.
## 0.1.36 - 2026-03-27
### Fixed
- Fixed Windows drive-letter path handling across the codebase.
- Fixed stale Nix hash with automatic lockfile-change detection.
### Added
- Added metrics collection and terminal performance tests.
## 0.1.35 - 2026-03-26
### Improved
- Faster app startup by redirecting to the welcome screen immediately and showing host connection status inline.
- Codex file deletions now display correctly as removed lines in diffs.
- OpenCode questions are now surfaced in the permission UI.
### Fixed
- Fixed queued prompt dispatch after idle transition.
- Replaced bash-only `mapfile` with a portable `while-read` loop in the chat script.
### Added
- Added support for Nix and NixOS installation.
## 0.1.34 - 2026-03-25
### Added
- Added `paseo archive` as a top-level alias for `paseo agent archive`.
- Added the `PASEO_AGENT_ID` environment variable for Claude and Codex agents.
- Added a redesigned command autocomplete with a detail card and dropdown styling.
- Linked Android download surfaces to the Google Play Store.
### Improved
- Autonomous turns now complete gracefully on interrupt instead of being canceled.
- Thinking/model selection now always resolves to a real option instead of showing a generic Default choice.
- Restored per-provider form preferences and removed the Auto model fallback.
@@ -435,6 +495,7 @@
- Improved chat transcript readability.
### Fixed
- Fixed `paseo send --no-wait` not taking effect.
- Fixed stale abort results contaminating replacement turns after an interrupt.
- Fixed Claude interrupt handling and autonomous wake reliability.
@@ -451,6 +512,7 @@
## 0.1.33 - 2026-03-23
### Fixed
- Fixed the desktop app failing to reopen after closing on macOS — the daemon and agent processes were registering with Launch Services as instances of the main app, blocking subsequent launches.
- Fixed dictation not working in the packaged desktop app — the microphone entitlement was missing from the hardened runtime configuration.
- Fixed leaked Claude Code child processes when agents were closed — the SDK query stream was not being properly shut down.
@@ -459,6 +521,7 @@
## 0.1.32 - 2026-03-23
### Added
- Fully rebindable keyboard shortcuts with chord support — all shortcuts are now declarative with proper Cmd (Mac) vs Ctrl (Windows/Linux) separation.
- Migrated the desktop app from Tauri to Electron, with macOS notarization, code signing, and Linux Wayland support.
- Added line numbers and word-wrap toggle to file previews.
@@ -468,6 +531,7 @@
- Added status bar tooltips for project and agent status.
### Improved
- Redesigned the mobile tab switcher as a compact header row with quick access to new agents and terminals.
- Streamlined workspace creation — worktrees are now created inline with a single action instead of a multi-step flow.
- Agent history now streams from disk on reconnect, so you see past messages immediately instead of a blank screen.
@@ -482,6 +546,7 @@
- Better error messages from the daemon — RPC errors now include the actual underlying details.
### Fixed
- Fixed user messages appearing as assistant output in the timeline when messages contained structured content blocks.
- Fixed archived workspace routing so navigating to an archived session no longer breaks the app.
- Fixed Linux AppImage failing to launch on Wayland-only desktops.
@@ -490,16 +555,19 @@
## 0.1.30 - 2026-03-19
### Added
- Added terminal tabs, split pane controls, and drop previews for workspace layouts.
- Added a combined model selector and agent mode visuals across key UI surfaces.
- Added Open Graph metadata improvements for richer website sharing previews.
### Improved
- Improved workspace navigation with better active-workspace tracking and keyboard-driven pane interactions.
- Improved terminal scrollbar behavior, pane focus handling, and status bar/message input spacing.
- Improved project picker path display and general workspace UI polish.
### Fixed
- Fixed agent startup reliability by tightening PATH resolution and surfacing missing provider binaries in status.
- Fixed workspace route syncing, drag hit areas, and git diff panel header styling regressions.
- Fixed website mobile horizontal scrolling and ensured the workspace audio module builds during EAS installs.
@@ -507,30 +575,36 @@
## 0.1.28 - 2026-03-15
### Added
- Added OpenCode build and plan modes.
- Added website landing pages for Claude Code, Codex, and OpenCode.
### Improved
- Improved the git action menu for more reliable repository actions.
- Improved the mobile settings screen, workspace header actions, and welcome screen presentation.
- Updated the website hero copy and added a sponsor callout section.
### Fixed
- Fixed assistant file links so they open the correct workspace files from chat.
## 0.1.27 - 2026-03-13
### Added
- Added voice runtime with new audio engine architecture for voice interactions.
- Added Grep tool support in Claude tool-call mapping.
- Added ability to open workspace files directly from agent chat messages.
- Added desktop notifications via a custom native bridge.
### Improved
- Improved image picker, markdown rendering, and UI interactions.
- Improved shell environment detection using shell-env.
### Fixed
- Fixed platform-specific markdown link rendering.
- Fixed Linux AppImage CLI resource paths.
- Fixed Codex replacement stream being killed by stale turn notifications.
@@ -538,15 +612,18 @@
## 0.1.26 - 2026-03-12
### Added
- Added single-instance desktop behavior, Android APK download access, and refreshed splash screen styling.
- Added bundled Codex and OpenCode binaries in the server so setup no longer depends on global installs.
- Added Windows support with improved cross-platform shell execution.
### Improved
- Improved desktop runtime behavior on Windows by suppressing console windows and defaulting app data to `~/.paseo`.
- Added a Discord link to the website navigation.
### Fixed
- Fixed desktop Claude agent startup from the managed runtime and rotated logs correctly on restart.
- Fixed the home route to hide browser chrome when appropriate.
- Fixed Expo Metro compatibility by updating the `exclusionList` import.
@@ -556,61 +633,79 @@
## 0.1.25 - 2026-03-11
### Fixed
- Fixed desktop app failing to start the built-in daemon on fresh macOS installs. The DMG was not notarized and code-signing stripped entitlements from the bundled Node runtime, causing Gatekeeper to block execution.
- Fixed Linux AppImage build by restoring the AppImage bundle format and stripping CUDA dependencies from onnxruntime.
## 0.1.24 - 2026-03-10
### Improved
- Improved command center keyboard navigation and new tab shortcut.
- Simplified desktop release pipeline for faster and more reliable builds.
## 0.1.21 - 2026-03-10
### Improved
- Improved desktop release reliability by fixing the Windows managed-runtime build path during GitHub Actions releases.
### Fixed
- Fixed a desktop release CI failure caused by a Unix-only server build script on Windows runners.
- Fixed server CI to build the relay dependency before running tests, restoring relay E2EE test coverage on clean runners.
- Fixed a Claude redesign test that depended on the local Claude CLI being installed.
## 0.1.20 - 2026-03-10
### Added
- Added workspace sidebar git actions with quick diff stats and archive controls.
- Added refreshed website downloads and homepage presentation for desktop installs.
### Improved
- Desktop release packaging now rebuilds and validates the bundled managed runtime during CI, improving installer reliability for macOS users.
- Improved desktop and web stream rendering, settings polish, and React 19.1.4 compatibility.
### Fixed
- Fixed Claude interrupt/restart regressions and strengthened managed-daemon smoke coverage for desktop releases.
## 0.1.19 - 2026-03-09
### Added
- Added a draft GitHub release flow so maintainers can upload and review desktop and Android release assets before publishing the final release.
## 0.1.18 - 2026-03-06
### Added
- Added a desktop `Mod+W` shortcut to close the current tab.
### Improved
- New and newly selected terminals now take focus automatically so you can type immediately.
- Kept newly created workspaces and projects in a more stable order in the sidebar.
- Improved project naming for GitHub remotes and expanded project icon discovery to Phoenix `priv/static` assets.
- Updated the website desktop download link to use the universal macOS DMG.
### Fixed
- Restored automatic agent metadata generation for Claude runs.
## 0.1.17 - 2026-03-06
### Added
- New workspace-first navigation model with workspace tabs, file tabs, and sortable tab groups.
- Keyboard shortcuts for workspace and tab navigation, with shortcut badges in the sidebar.
- Workspace-level archive actions with improved worktree archiving flow and context menu support.
- In-chat task notifications rendered as synthetic tool-call events for clearer status tracking.
### Improved
- Desktop builds now ship as a universal macOS binary (Apple Silicon + Intel).
- More reliable workspace routing and tab identity handling across refreshes and deep links.
- Better sidebar drag-and-drop behavior with explicit drag handles and nested list interactions.
@@ -618,6 +713,7 @@
- Stronger provider error surfacing and updated Claude model/runtime handling.
### Fixed
- Fixed orphan workspace runs caused by non-canonical tab routes.
- Fixed mobile terminal tab remount/routing restore issues.
- Fixed agent metadata title/branch update reliability.
@@ -625,7 +721,9 @@
- Fixed reversed edge-wheel scroll behavior in chat/tool stream views.
## 0.1.16 - 2026-02-22
### Added
- Update the Paseo desktop app and local daemon directly from Settings.
- Microphone and notification permission controls in Settings.
- Thinking/reasoning mode — agents can use extended thinking when the provider supports it.
@@ -633,6 +731,7 @@
- `paseo wait` now shows a snapshot of recent agent activity while you wait.
### Improved
- Smoother streaming with less UI flicker and scroll jumping during long agent runs.
- Faster agent sidebar list rendering.
- Archiving an agent now stops it first instead of archiving a half-running session.
@@ -640,16 +739,20 @@
- More reliable relay connections.
### Fixed
- Fixed Claude background tasks desyncing the chat.
- Fixed duplicate user messages appearing in the timeline.
- Fixed a startup crash caused by an OpenCode SDK update.
- Fixed spurious "needs attention" notifications from background agent activity.
## 0.1.15 - 2026-02-19
### Added
- Added a public changelog page on the website so users can browse release notes.
### Improved
- Redesigned the website get-started experience into a clearer two-step flow.
- Simplified website GitHub navigation and changelog headings.
- Improved app draft/new-agent UX with clearer working directory placeholder and empty-state messaging.
@@ -657,11 +760,14 @@
- Hid empty filter groups in the left sidebar.
### Fixed
- Fixed archived-agent navigation by redirecting archived agent routes to draft.
- Fixed duplicate `/rewind` user-message behavior.
## 0.1.14 - 2026-02-19
### Added
- Added Claude `/rewind` command support.
- Added slash command access in the draft agent composer.
- Added `@` workspace file autocomplete in chat prompts.
@@ -670,6 +776,7 @@
- Added shared desktop/web overlay scroll handles, including file preview panes.
### Improved
- Improved worktree flow after shipping, including better merged PR detection.
- Improved draft workflow by enabling the explorer sidebar immediately after CWD selection.
- Improved new worktree-agent defaults by prefilling CWD to the main repository.
@@ -679,6 +786,7 @@
- Improved scrollbar visibility, drag interactions, tracking, and animation timing on web/desktop.
### Fixed
- Fixed worktree archive/setup lifecycle issues, including terminal cleanup and archive timing.
- Fixed worktree path collisions by hashing CWD for collision-safe worktree roots.
- Fixed terminal sizing when switching back to an agent session.
@@ -692,25 +800,32 @@
- Fixed daemon version badge visibility in settings when daemon version data is unavailable.
## 0.1.9 - 2026-02-17
### Improved
- Unified structured-output generation through a single shared schema-validation and retry pipeline.
- Reused provider availability checks for structured generation fallback selection.
- Added structured generation waterfall ordering for internal metadata and git text generation: Claude Haiku, then Codex, then OpenCode.
### Fixed
- Fixed CLI `run --output-schema` to use the shared structured-output path instead of ad-hoc JSON parsing.
- Fixed `run --output-schema` failures where providers returned empty `lastMessage` by recovering from timeline assistant output.
- Fixed internal commit message, pull request text, and agent metadata generation to follow one consistent structured pipeline.
## 0.1.8 - 2026-02-17
### Added
- Added a cross-platform confirm dialog flow for daemon restarts.
### Improved
- Simplified local speech bootstrap and daemon startup locking behavior.
- Updated website hero copy to emphasize local execution.
### Fixed
- Fixed stuck "send while running" recovery across app and server session handling.
- Fixed Claude session identity preservation when reloading existing agents.
- Fixed combobox option behavior and related interactions.
@@ -718,73 +833,93 @@
- Fixed web tool-detail wheel event routing at scroll edges.
## 0.1.7 - 2026-02-16
### Added
- Improved agent workspace flows with better directory suggestions.
- Added iOS TestFlight and Android app access request forms on the website.
### Improved
- Unified daemon startup behavior between dev and CLI paths for more predictable local runs.
- Improved website app download and update guidance.
### Fixed
- Prevented an initial desktop combobox `0,0` position flash.
- Fixed CLI version output issues.
- Hardened server runtime loading for local speech dependencies.
## 0.1.6 - 2026-02-16
### Notes
- No major visible product changes in this patch release.
## 0.1.5 - 2026-02-16
### Added
- Added terminal reattach support and better worktree terminal handling.
- Added global keyboard shortcut help in the app.
- Added sidebar host filtering and improved agent workflow controls.
### Improved
- Improved worktree setup visibility by streaming setup progress.
- Improved terminal streaming reliability and lifecycle handling.
- Preserved explorer tab state so context survives navigation better.
## 0.1.4 - 2026-02-14
### Added
- Added voice capability status reporting in the client.
- Added background local speech model downloads with runtime gating.
- Added adaptive dictation finish timing based on server-provided budgets.
- Added relay reconnect behavior with grace periods and branch suggestions.
### Improved
- Improved connection selection and agent hydration reliability.
- Improved timeline loading with cursor-based fetch behavior.
- Improved first-run experience by bootstrapping a default localhost connection.
- Improved inline code rendering by auto-linkifying URLs.
### Fixed
- Fixed Linux checkout diff watch behavior to avoid recursive watches.
- Fixed stale relay client timer behavior.
- Fixed unnecessary git diff header auto-scroll on collapse.
## 0.1.3 - 2026-02-12
### Added
- Added CLI onboarding command.
- Added CLI `--output-schema` support for structured agent output.
- Added CLI agent metadata update support for names and labels.
- Added provider availability detection with normalization of legacy default model IDs.
### Improved
- Improved file explorer refresh feedback and unresolved checkout fallback handling.
- Added better voice interrupt handling with a speech-start grace period.
- Improved CLI defaults to list all non-archived agents by default.
- Improved website UX with clearer install CTA and privacy policy access.
### Fixed
- Fixed dev runner entry issues and sherpa TTS initialization behavior.
## 0.1.2 - 2026-02-11
### Notes
- No major visible product changes in this patch release.
## 0.1.1 - 2026-02-11
### Added
- Initial `0.1.x` release line.

View File

@@ -17,17 +17,17 @@ This is an npm workspace monorepo:
## Documentation
| Doc | What's in it |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design, package layering, WebSocket protocol, agent lifecycle, data flow |
| [docs/CODING_STANDARDS.md](docs/CODING_STANDARDS.md) | Type hygiene, error handling, state design, React patterns, file organization |
| [docs/TESTING.md](docs/TESTING.md) | TDD workflow, determinism, real dependencies over mocks, test organization |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP |
| [docs/RELEASE.md](docs/RELEASE.md) | Release playbook, draft releases, completion checklist |
| Doc | What's in it |
| ---------------------------------------------------- | --------------------------------------------------------------------------------- |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design, package layering, WebSocket protocol, agent lifecycle, data flow |
| [docs/CODING_STANDARDS.md](docs/CODING_STANDARDS.md) | Type hygiene, error handling, state design, React patterns, file organization |
| [docs/TESTING.md](docs/TESTING.md) | TDD workflow, determinism, real dependencies over mocks, test organization |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP |
| [docs/RELEASE.md](docs/RELEASE.md) | Release playbook, draft releases, completion checklist |
| [docs/CUSTOM-PROVIDERS.md](docs/CUSTOM-PROVIDERS.md) | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries |
| [docs/ANDROID.md](docs/ANDROID.md) | App variants, local/cloud builds, EAS workflows |
| [docs/DESIGN.md](docs/DESIGN.md) | How to design features before implementation |
| [SECURITY.md](SECURITY.md) | Relay threat model, E2E encryption, DNS rebinding, agent auth |
| [docs/ANDROID.md](docs/ANDROID.md) | App variants, local/cloud builds, EAS workflows |
| [docs/DESIGN.md](docs/DESIGN.md) | How to design features before implementation |
| [SECURITY.md](SECURITY.md) | Relay threat model, E2E encryption, DNS rebinding, agent auth |
## Quick start
@@ -68,23 +68,23 @@ The app runs on iOS, Android, web (browser), and web (Electron desktop). Code is
### The four gates
| Gate | Type | When to use |
|---|---|---|
| `isWeb` | constant | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. |
| `isNative` | constant | Native-only APIs — Haptics, `StatusBar.currentHeight`, push tokens, camera/scanner, `expo-av`. |
| `getIsElectron()` | cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges. |
| `useIsCompactFormFactor()` | hook | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`. |
| Gate | Type | When to use |
| -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |
| `isWeb` | constant | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. |
| `isNative` | constant | Native-only APIs — Haptics, `StatusBar.currentHeight`, push tokens, camera/scanner, `expo-av`. |
| `getIsElectron()` | cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges. |
| `useIsCompactFormFactor()` | hook | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`. |
### Decision matrix
| I need to... | Use |
|---|---|
| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)` |
| Use a native-only API (Haptics, push tokens, camera) | `if (isNative)` |
| Use an Electron bridge (file dialog, titlebar, updates) | `if (getIsElectron())` |
| Switch layout between phone and tablet/desktop | `useIsCompactFormFactor()` |
| Show something on hover, always-visible on native | `isHovered \|\| isNative \|\| isCompact` (hover only works on web) |
| Gate to iOS or Android specifically | `Platform.OS === "ios"` / `Platform.OS === "android"` (rare, keep inline) |
| I need to... | Use |
| -------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)` |
| Use a native-only API (Haptics, push tokens, camera) | `if (isNative)` |
| Use an Electron bridge (file dialog, titlebar, updates) | `if (getIsElectron())` |
| Switch layout between phone and tablet/desktop | `useIsCompactFormFactor()` |
| Show something on hover, always-visible on native | `isHovered \|\| isNative \|\| isCompact` (hover only works on web) |
| Gate to iOS or Android specifically | `Platform.OS === "ios"` / `Platform.OS === "android"` (rare, keep inline) |
### Rules
@@ -104,5 +104,4 @@ The app runs on iOS, Android, web (browser), and web (Electron desktop). Code is
## Debugging
Find the complete daemon logs and traces in the $PASEO_HOME/daemon.log

View File

@@ -69,6 +69,7 @@ paseo
This shows a QR code in the terminal. Connect from any client. This path is useful for servers and remote machines.
For full setup and configuration, see:
- [Docs](https://paseo.sh/docs)
- [Configuration reference](https://paseo.sh/docs/configuration)
@@ -116,6 +117,7 @@ Then use them in any agent conversation:
## Development
Quick monorepo package map:
- `packages/server`: Paseo daemon (agent process orchestration, WebSocket API, MCP server)
- `packages/app`: Expo client (iOS, Android, web)
- `packages/cli`: `paseo` CLI for daemon and agent workflows

View File

@@ -58,6 +58,7 @@ await rm(staticDir, { recursive: true, force: true });
```
Run with:
```bash
npx tsx packages/server/src/server/your-script.ts
```
@@ -107,6 +108,7 @@ const updated = await client.waitForAgentUpsert(agent.id, (s) => s.status === "i
The daemon hides non-legacy providers (anything other than claude, codex, opencode) from clients that don't send an `appVersion >= 0.1.45`. The `DaemonClient` sends no version by default, so custom providers like ACP-based ones will be invisible in snapshot responses.
Always pass `appVersion`:
```typescript
const client = new DaemonClient({
url: `ws://127.0.0.1:${port}/ws`,

View File

@@ -4,9 +4,9 @@
Controlled by `APP_VARIANT` in `packages/app/app.config.js` (vanilla Expo, no custom Gradle plugin):
| Variant | App name | Package ID |
|---|---|---|
| `production` | Paseo | `sh.paseo` |
| Variant | App name | Package ID |
| ------------- | ----------- | ---------------- |
| `production` | Paseo | `sh.paseo` |
| `development` | Paseo Debug | `sh.paseo.debug` |
EAS profiles: `development`, `production`, and `production-apk` in `packages/app/eas.json`.

View File

@@ -53,17 +53,17 @@ The heart of Paseo. A Node.js process that:
**Key modules:**
| Module | Responsibility |
|---|---|
| `bootstrap.ts` | Daemon initialization: HTTP server, WS server, agent manager, storage, relay |
| `websocket-server.ts` | WebSocket connection management, hello/welcome handshake, binary multiplexing |
| `session.ts` | Per-client session state, timeline subscriptions, terminal operations |
| `agent/agent-manager.ts` | Agent lifecycle state machine, timeline tracking, subscriber management |
| `agent/agent-storage.ts` | File-backed JSON persistence at `$PASEO_HOME/agents/` |
| `agent/mcp-server.ts` | MCP server for sub-agent creation, permissions, timeouts |
| `providers/` | Provider adapters: Claude (Agent SDK), Codex (AppServer), OpenCode |
| `relay-transport.ts` | Outbound relay connection with E2E encryption |
| `client/daemon-client.ts` | Client library for connecting to the daemon (used by CLI and app) |
| Module | Responsibility |
| ------------------------- | ----------------------------------------------------------------------------- |
| `bootstrap.ts` | Daemon initialization: HTTP server, WS server, agent manager, storage, relay |
| `websocket-server.ts` | WebSocket connection management, hello/welcome handshake, binary multiplexing |
| `session.ts` | Per-client session state, timeline subscriptions, terminal operations |
| `agent/agent-manager.ts` | Agent lifecycle state machine, timeline tracking, subscriber management |
| `agent/agent-storage.ts` | File-backed JSON persistence at `$PASEO_HOME/agents/` |
| `agent/mcp-server.ts` | MCP server for sub-agent creation, permissions, timeouts |
| `providers/` | Provider adapters: Claude (Agent SDK), Codex (AppServer), OpenCode |
| `relay-transport.ts` | Outbound relay connection with E2E encryption |
| `client/daemon-client.ts` | Client library for connecting to the daemon (used by CLI and app) |
### `packages/app` — Mobile + web client (Expo)
@@ -132,6 +132,7 @@ Server → Client: WSWelcomeMessage { clientId, daemonVersion, sessionId, capab
**Binary multiplexing:**
Terminal I/O and agent streaming share the same connection via `BinaryMuxFrame`:
- Channel 0: control messages
- Channel 1: terminal data
- 1-byte channel ID + 1-byte flags + variable payload
@@ -153,13 +154,14 @@ initializing → idle → running → idle (or error → closed)
Each provider implements a common `AgentClient` interface:
| Provider | Wraps | Session format |
|---|---|---|
| Claude | Anthropic Agent SDK | `~/.claude/projects/{cwd}/{session-id}.jsonl` |
| Codex | CodexAppServer | `~/.codex/sessions/{date}/rollout-{ts}-{id}.jsonl` |
| OpenCode | OpenCode CLI | Provider-managed |
| Provider | Wraps | Session format |
| -------- | ------------------- | -------------------------------------------------- |
| Claude | Anthropic Agent SDK | `~/.claude/projects/{cwd}/{session-id}.jsonl` |
| Codex | CodexAppServer | `~/.codex/sessions/{date}/rollout-{ts}-{id}.jsonl` |
| OpenCode | OpenCode CLI | Provider-managed |
All providers:
- Handle their own authentication (Paseo does not manage API keys)
- Support session resume via persistence handles
- Map tool calls to a normalized `ToolCallDetail` type

View File

@@ -40,7 +40,10 @@ No complex inline types in public function signatures.
function enqueueJob(input: { userId: string; priority: "low" | "normal" | "high" }) {}
// Good
interface EnqueueJobInput { userId: string; priority: "low" | "normal" | "high" }
interface EnqueueJobInput {
userId: string;
priority: "low" | "normal" | "high";
}
function enqueueJob(input: EnqueueJobInput) {}
```
@@ -53,7 +56,11 @@ If a function needs more than one argument, use a single object parameter.
function createToolCall(provider: string, toolName: string, payload: unknown) {}
// Good: object param
interface CreateToolCallInput { provider: string; toolName: string; payload: unknown }
interface CreateToolCallInput {
provider: string;
toolName: string;
payload: unknown;
}
function createToolCall(input: CreateToolCallInput) {}
```
@@ -78,7 +85,11 @@ Use discriminated unions instead of bags of booleans and optionals.
```typescript
// Bad
interface FetchState { isLoading: boolean; error?: Error; data?: Data }
interface FetchState {
isLoading: boolean;
error?: Error;
data?: Data;
}
// Good
type FetchState =
@@ -136,7 +147,10 @@ Avoid packing branching, lookup, and transformation into single dense expression
// Bad: nested ternaries + inline lookups
const billing = shouldUseLegacy(account)
? getLegacy(account)
: buildBilling(account, rates.find((r) => r.region === account.region));
: buildBilling(
account,
rates.find((r) => r.region === account.region),
);
// Good: named steps, then assemble
const rate = rates.find((r) => r.region === account.region);

View File

@@ -55,6 +55,7 @@ Use `extends` to create a new provider entry that inherits from a built-in provi
```
Required fields for custom providers:
- `extends` — which built-in provider to inherit from (or `"acp"`)
- `label` — display name in the UI
@@ -95,12 +96,12 @@ Required fields for custom providers:
### Available models
| Model | Tier |
|---|---|
| `glm-5.1` | Advanced (flagship) |
| `glm-5-turbo` | Advanced |
| `glm-4.7` | Standard |
| `glm-4.5-air` | Lightweight |
| Model | Tier |
| ------------- | ------------------- |
| `glm-5.1` | Advanced (flagship) |
| `glm-5-turbo` | Advanced |
| `glm-4.7` | Standard |
| `glm-4.5-air` | Lightweight |
### Notes
@@ -148,10 +149,10 @@ Required fields for custom providers:
### API endpoints
| Mode | Base URL |
|---|---|
| Coding plan (subscription) | `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic` |
| Pay-as-you-go (no subscription) | `https://dashscope-intl.aliyuncs.com/apps/anthropic` |
| Mode | Base URL |
| ------------------------------- | ----------------------------------------------------------- |
| Coding plan (subscription) | `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic` |
| Pay-as-you-go (no subscription) | `https://dashscope-intl.aliyuncs.com/apps/anthropic` |
For pay-as-you-go, use `ANTHROPIC_API_KEY` with a standard Model Studio key (`sk-xxxxx`) instead of `ANTHROPIC_AUTH_TOKEN`.
@@ -159,13 +160,13 @@ For pay-as-you-go, use `ANTHROPIC_API_KEY` with a standard Model Studio key (`sk
**Recommended for coding plan:**
| Model | Notes |
|---|---|
| `qwen3.5-plus` | Vision capable, recommended |
| `qwen3-coder-next` | Optimized for coding |
| `kimi-k2.5` | Vision capable |
| `glm-5` | Zhipu GLM |
| `MiniMax-M2.5` | MiniMax |
| Model | Notes |
| ------------------ | --------------------------- |
| `qwen3.5-plus` | Vision capable, recommended |
| `qwen3-coder-next` | Optimized for coding |
| `kimi-k2.5` | Vision capable |
| `glm-5` | Zhipu GLM |
| `MiniMax-M2.5` | MiniMax |
**Additional models (pay-as-you-go):**
`qwen3-max`, `qwen3.5-flash`, `qwen3-coder-plus`, `qwen3-coder-flash`, `qwen3-vl-plus`, `qwen3-vl-flash`
@@ -221,16 +222,12 @@ You can also combine profiles with model overrides to pin specific models per pr
"claude-fast": {
"extends": "claude",
"label": "Claude (Fast)",
"models": [
{ "id": "claude-sonnet-4-6", "label": "Sonnet 4.6", "isDefault": true }
]
"models": [{ "id": "claude-sonnet-4-6", "label": "Sonnet 4.6", "isDefault": true }]
},
"claude-smart": {
"extends": "claude",
"label": "Claude (Smart)",
"models": [
{ "id": "claude-opus-4-6", "label": "Opus 4.6", "isDefault": true }
]
"models": [{ "id": "claude-opus-4-6", "label": "Opus 4.6", "isDefault": true }]
}
}
}
@@ -338,6 +335,7 @@ Set `extends: "acp"` and provide a `command`:
```
Required fields for ACP providers:
- `extends: "acp"`
- `label`
- `command` — the command to spawn the agent process (must support ACP over stdio)
@@ -455,9 +453,7 @@ Example: relabel a discovered model without replacing the full list:
"extends": "acp",
"label": "My Agent",
"command": ["my-agent", "--acp"],
"additionalModels": [
{ "id": "provider/model-id", "label": "My Preferred Label" }
]
"additionalModels": [{ "id": "provider/model-id", "label": "My Preferred Label" }]
}
}
}
@@ -472,39 +468,39 @@ When an `additionalModels` entry has the same `id` as a discovered model, it upd
Every entry under `agents.providers` accepts these fields:
| Field | Type | Required | Description |
|---|---|---|---|
| `extends` | `string` | Yes (custom only) | Built-in provider ID to inherit from, or `"acp"` |
| `label` | `string` | Yes (custom only) | Display name in the UI |
| `description` | `string` | No | Short description shown in the UI |
| `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process |
| `env` | `Record<string, string>` | No | Environment variables to set for the agent process |
| `models` | `ProviderProfileModel[]` | No | Static model list (overrides runtime discovery) |
| `additionalModels` | `ProviderProfileModel[]` | No | Static model additions (merged with runtime discovery or `models`) |
| `disallowedTools` | `string[]` | No | Tool names to disable for this provider (e.g. `["WebSearch"]`) |
| `enabled` | `boolean` | No | Set to `false` to hide the provider (default: `true`) |
| `order` | `number` | No | Sort order in the provider list |
| Field | Type | Required | Description |
| ------------------ | ------------------------ | ----------------- | ------------------------------------------------------------------ |
| `extends` | `string` | Yes (custom only) | Built-in provider ID to inherit from, or `"acp"` |
| `label` | `string` | Yes (custom only) | Display name in the UI |
| `description` | `string` | No | Short description shown in the UI |
| `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process |
| `env` | `Record<string, string>` | No | Environment variables to set for the agent process |
| `models` | `ProviderProfileModel[]` | No | Static model list (overrides runtime discovery) |
| `additionalModels` | `ProviderProfileModel[]` | No | Static model additions (merged with runtime discovery or `models`) |
| `disallowedTools` | `string[]` | No | Tool names to disable for this provider (e.g. `["WebSearch"]`) |
| `enabled` | `boolean` | No | Set to `false` to hide the provider (default: `true`) |
| `order` | `number` | No | Sort order in the provider list |
### Model definition
Each entry in the `models` array:
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | Yes | Model identifier sent to the provider |
| `label` | `string` | Yes | Display name in the UI |
| `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default model selection |
| `thinkingOptions` | `ThinkingOption[]` | No | Available thinking/reasoning levels |
| Field | Type | Required | Description |
| ----------------- | ------------------ | -------- | ------------------------------------- |
| `id` | `string` | Yes | Model identifier sent to the provider |
| `label` | `string` | Yes | Display name in the UI |
| `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default model selection |
| `thinkingOptions` | `ThinkingOption[]` | No | Available thinking/reasoning levels |
### Thinking option
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | Yes | Thinking option identifier |
| `label` | `string` | Yes | Display name |
| `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default thinking option |
| Field | Type | Required | Description |
| ------------- | --------- | -------- | ----------------------------------- |
| `id` | `string` | Yes | Thinking option identifier |
| `label` | `string` | Yes | Display name |
| `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default thinking option |
### Gotcha: `extends: "claude"` with third-party endpoints

View File

@@ -34,88 +34,88 @@ $PASEO_HOME/
Each agent is stored as a separate JSON file, grouped by project directory.
| Field | Type | Description |
|---|---|---|
| `id` | `string` | UUID, primary key |
| `provider` | `string` | Agent provider (`"claude"`, `"codex"`, `"opencode"`, etc.) |
| `cwd` | `string` | Working directory the agent operates in |
| `createdAt` | `string` (ISO 8601) | Creation timestamp |
| `updatedAt` | `string` (ISO 8601) | Last update timestamp |
| `lastActivityAt` | `string?` (ISO 8601) | Last activity timestamp |
| `lastUserMessageAt` | `string?` (ISO 8601) | Last user message timestamp |
| `title` | `string?` | User-visible title |
| `labels` | `Record<string, string>` | Key-value labels (default `{}`) |
| `lastStatus` | `AgentStatus` | One of: `"initializing"`, `"idle"`, `"running"`, `"error"`, `"closed"` |
| `lastModeId` | `string?` | Last active mode ID |
| `config` | `SerializableConfig?` | Agent session configuration (see below) |
| `runtimeInfo` | `RuntimeInfo?` | Live runtime state (see below) |
| `features` | `AgentFeature[]?` | Provider-reported features (toggles/selects) |
| `persistence` | `PersistenceHandle?` | Handle for resuming sessions |
| `requiresAttention` | `boolean?` | Whether the agent needs user attention |
| `attentionReason` | `"finished" \| "error" \| "permission"?` | Why attention is needed |
| `attentionTimestamp` | `string?` (ISO 8601) | When attention was flagged |
| `internal` | `boolean?` | Whether this is a system-internal agent (loop workers, etc.) |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
| Field | Type | Description |
| -------------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| `id` | `string` | UUID, primary key |
| `provider` | `string` | Agent provider (`"claude"`, `"codex"`, `"opencode"`, etc.) |
| `cwd` | `string` | Working directory the agent operates in |
| `createdAt` | `string` (ISO 8601) | Creation timestamp |
| `updatedAt` | `string` (ISO 8601) | Last update timestamp |
| `lastActivityAt` | `string?` (ISO 8601) | Last activity timestamp |
| `lastUserMessageAt` | `string?` (ISO 8601) | Last user message timestamp |
| `title` | `string?` | User-visible title |
| `labels` | `Record<string, string>` | Key-value labels (default `{}`) |
| `lastStatus` | `AgentStatus` | One of: `"initializing"`, `"idle"`, `"running"`, `"error"`, `"closed"` |
| `lastModeId` | `string?` | Last active mode ID |
| `config` | `SerializableConfig?` | Agent session configuration (see below) |
| `runtimeInfo` | `RuntimeInfo?` | Live runtime state (see below) |
| `features` | `AgentFeature[]?` | Provider-reported features (toggles/selects) |
| `persistence` | `PersistenceHandle?` | Handle for resuming sessions |
| `requiresAttention` | `boolean?` | Whether the agent needs user attention |
| `attentionReason` | `"finished" \| "error" \| "permission"?` | Why attention is needed |
| `attentionTimestamp` | `string?` (ISO 8601) | When attention was flagged |
| `internal` | `boolean?` | Whether this is a system-internal agent (loop workers, etc.) |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
### Nested: SerializableConfig
| Field | Type | Description |
|---|---|---|
| `title` | `string?` | Configured title |
| `modeId` | `string?` | Configured mode |
| `model` | `string?` | Configured model |
| `thinkingOptionId` | `string?` | Thinking/reasoning level |
| `featureValues` | `Record<string, unknown>?` | Feature preference overrides |
| `extra` | `Record<string, any>?` | Provider-specific config |
| `systemPrompt` | `string?` | Custom system prompt |
| `mcpServers` | `Record<string, any>?` | MCP server configurations |
| Field | Type | Description |
| ------------------ | -------------------------- | ---------------------------- |
| `title` | `string?` | Configured title |
| `modeId` | `string?` | Configured mode |
| `model` | `string?` | Configured model |
| `thinkingOptionId` | `string?` | Thinking/reasoning level |
| `featureValues` | `Record<string, unknown>?` | Feature preference overrides |
| `extra` | `Record<string, any>?` | Provider-specific config |
| `systemPrompt` | `string?` | Custom system prompt |
| `mcpServers` | `Record<string, any>?` | MCP server configurations |
### Nested: RuntimeInfo
| Field | Type | Description |
|---|---|---|
| `provider` | `string` | Active provider |
| `sessionId` | `string?` | Active session ID |
| `model` | `string?` | Active model |
| `thinkingOptionId` | `string?` | Active thinking option |
| `modeId` | `string?` | Active mode |
| `extra` | `Record<string, unknown>?` | Provider-specific runtime data |
| Field | Type | Description |
| ------------------ | -------------------------- | ------------------------------ |
| `provider` | `string` | Active provider |
| `sessionId` | `string?` | Active session ID |
| `model` | `string?` | Active model |
| `thinkingOptionId` | `string?` | Active thinking option |
| `modeId` | `string?` | Active mode |
| `extra` | `Record<string, unknown>?` | Provider-specific runtime data |
### Nested: PersistenceHandle
| Field | Type | Description |
|---|---|---|
| `provider` | `string` | Provider that owns the session |
| `sessionId` | `string` | Session ID for resumption |
| `nativeHandle` | `any?` | Provider-specific handle (Codex thread ID, Claude resume token, etc.) |
| `metadata` | `Record<string, any>?` | Extra metadata |
| Field | Type | Description |
| -------------- | ---------------------- | --------------------------------------------------------------------- |
| `provider` | `string` | Provider that owns the session |
| `sessionId` | `string` | Session ID for resumption |
| `nativeHandle` | `any?` | Provider-specific handle (Codex thread ID, Claude resume token, etc.) |
| `metadata` | `Record<string, any>?` | Extra metadata |
### Nested: AgentFeature (discriminated union on `type`)
**Toggle:**
| Field | Type |
|---|---|
| `type` | `"toggle"` |
| `id` | `string` |
| `label` | `string` |
| `description` | `string?` |
| `tooltip` | `string?` |
| `icon` | `string?` |
| `value` | `boolean` |
| Field | Type |
| ------------- | ---------- |
| `type` | `"toggle"` |
| `id` | `string` |
| `label` | `string` |
| `description` | `string?` |
| `tooltip` | `string?` |
| `icon` | `string?` |
| `value` | `boolean` |
**Select:**
| Field | Type |
|---|---|
| `type` | `"select"` |
| `id` | `string` |
| `label` | `string` |
| `description` | `string?` |
| `tooltip` | `string?` |
| `icon` | `string?` |
| `value` | `string?` |
| `options` | `AgentSelectOption[]` |
| Field | Type |
| ------------- | --------------------- |
| `type` | `"select"` |
| `id` | `string` |
| `label` | `string` |
| `description` | `string?` |
| `tooltip` | `string?` |
| `icon` | `string?` |
| `value` | `string?` |
| `options` | `AgentSelectOption[]` |
---
@@ -172,22 +172,22 @@ All fields are optional with sensible defaults.
One file per schedule. ID is 8 hex characters.
| Field | Type | Description |
|---|---|---|
| `id` | `string` | 8-char hex ID |
| `name` | `string?` | Human-readable name |
| `prompt` | `string` | The prompt to send |
| `cadence` | `ScheduleCadence` | Timing (see below) |
| `target` | `ScheduleTarget` | What to run (see below) |
| `status` | `"active" \| "paused" \| "completed"` | Current state |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `nextRunAt` | `string?` (ISO 8601) | Next scheduled execution |
| `lastRunAt` | `string?` (ISO 8601) | Last execution time |
| `pausedAt` | `string?` (ISO 8601) | When paused |
| `expiresAt` | `string?` (ISO 8601) | Auto-expire time |
| `maxRuns` | `number?` | Max executions before completing |
| `runs` | `ScheduleRun[]` | Execution history |
| Field | Type | Description |
| ----------- | ------------------------------------- | -------------------------------- |
| `id` | `string` | 8-char hex ID |
| `name` | `string?` | Human-readable name |
| `prompt` | `string` | The prompt to send |
| `cadence` | `ScheduleCadence` | Timing (see below) |
| `target` | `ScheduleTarget` | What to run (see below) |
| `status` | `"active" \| "paused" \| "completed"` | Current state |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `nextRunAt` | `string?` (ISO 8601) | Next scheduled execution |
| `lastRunAt` | `string?` (ISO 8601) | Last execution time |
| `pausedAt` | `string?` (ISO 8601) | When paused |
| `expiresAt` | `string?` (ISO 8601) | Auto-expire time |
| `maxRuns` | `number?` | Max executions before completing |
| `runs` | `ScheduleRun[]` | Execution history |
### Nested: ScheduleCadence (discriminated union on `type`)
@@ -201,16 +201,16 @@ One file per schedule. ID is 8 hex characters.
### Nested: ScheduleRun
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Run ID |
| `scheduledFor` | `string` (ISO 8601) | Intended execution time |
| `startedAt` | `string` (ISO 8601) | |
| `endedAt` | `string?` (ISO 8601) | |
| `status` | `"running" \| "succeeded" \| "failed"` | |
| `agentId` | `string?` (UUID) | Agent used for this run |
| `output` | `string?` | Agent output text |
| `error` | `string?` | Error message if failed |
| Field | Type | Description |
| -------------- | -------------------------------------- | ----------------------- |
| `id` | `string` | Run ID |
| `scheduledFor` | `string` (ISO 8601) | Intended execution time |
| `startedAt` | `string` (ISO 8601) | |
| `endedAt` | `string?` (ISO 8601) | |
| `status` | `"running" \| "succeeded" \| "failed"` | |
| `agentId` | `string?` (UUID) | Agent used for this run |
| `output` | `string?` | Agent output text |
| `error` | `string?` | Error message if failed |
---
@@ -229,25 +229,25 @@ Single file containing all rooms and messages.
### ChatRoom
| Field | Type | Description |
|---|---|---|
| `id` | `string` (UUID) | |
| `name` | `string` | Unique room name (case-insensitive) |
| `purpose` | `string?` | Room description |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | Updated on each new message |
| Field | Type | Description |
| ----------- | ------------------- | ----------------------------------- |
| `id` | `string` (UUID) | |
| `name` | `string` | Unique room name (case-insensitive) |
| `purpose` | `string?` | Room description |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | Updated on each new message |
### ChatMessage
| Field | Type | Description |
|---|---|---|
| `id` | `string` (UUID) | |
| `roomId` | `string` | FK to ChatRoom.id |
| `authorAgentId` | `string` | Agent ID of the author |
| `body` | `string` | Message text (supports `@mentions`) |
| `replyToMessageId` | `string?` | FK to another ChatMessage.id |
| `mentionAgentIds` | `string[]` | Extracted `@mention` agent IDs |
| `createdAt` | `string` (ISO 8601) | |
| Field | Type | Description |
| ------------------ | ------------------- | ----------------------------------- |
| `id` | `string` (UUID) | |
| `roomId` | `string` | FK to ChatRoom.id |
| `authorAgentId` | `string` | Agent ID of the author |
| `body` | `string` | Message text (supports `@mentions`) |
| `replyToMessageId` | `string?` | FK to another ChatMessage.id |
| `mentionAgentIds` | `string[]` | Extracted `@mention` agent IDs |
| `createdAt` | `string` (ISO 8601) | |
---
@@ -257,84 +257,84 @@ Single file containing all rooms and messages.
Single file containing an array of all loop records.
| Field | Type | Description |
|---|---|---|
| `id` | `string` | 8-char UUID prefix |
| `name` | `string?` | Human-readable name |
| `prompt` | `string` | Worker prompt |
| `cwd` | `string` | Working directory |
| `provider` | `string` | Default provider |
| `model` | `string?` | Default model |
| `workerProvider` | `string?` | Override provider for workers |
| `workerModel` | `string?` | Override model for workers |
| `verifierProvider` | `string?` | Override provider for verifiers |
| `verifierModel` | `string?` | Override model for verifiers |
| `verifyPrompt` | `string?` | LLM verification prompt |
| `verifyChecks` | `string[]` | Shell commands to run as checks |
| `archive` | `boolean` | Whether to archive worker agents after use |
| `sleepMs` | `number` | Delay between iterations (ms) |
| `maxIterations` | `number?` | Cap on iterations |
| `maxTimeMs` | `number?` | Total time budget (ms) |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `startedAt` | `string` (ISO 8601) | |
| `completedAt` | `string?` (ISO 8601) | |
| `stopRequestedAt` | `string?` (ISO 8601) | |
| `iterations` | `LoopIteration[]` | |
| `logs` | `LoopLogEntry[]` | |
| `nextLogSeq` | `number` | Monotonic log sequence counter |
| `activeIteration` | `number?` | Currently executing iteration index |
| `activeWorkerAgentId` | `string?` | Currently running worker agent |
| `activeVerifierAgentId` | `string?` | Currently running verifier agent |
| Field | Type | Description |
| ----------------------- | --------------------------------------------------- | ------------------------------------------ |
| `id` | `string` | 8-char UUID prefix |
| `name` | `string?` | Human-readable name |
| `prompt` | `string` | Worker prompt |
| `cwd` | `string` | Working directory |
| `provider` | `string` | Default provider |
| `model` | `string?` | Default model |
| `workerProvider` | `string?` | Override provider for workers |
| `workerModel` | `string?` | Override model for workers |
| `verifierProvider` | `string?` | Override provider for verifiers |
| `verifierModel` | `string?` | Override model for verifiers |
| `verifyPrompt` | `string?` | LLM verification prompt |
| `verifyChecks` | `string[]` | Shell commands to run as checks |
| `archive` | `boolean` | Whether to archive worker agents after use |
| `sleepMs` | `number` | Delay between iterations (ms) |
| `maxIterations` | `number?` | Cap on iterations |
| `maxTimeMs` | `number?` | Total time budget (ms) |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `startedAt` | `string` (ISO 8601) | |
| `completedAt` | `string?` (ISO 8601) | |
| `stopRequestedAt` | `string?` (ISO 8601) | |
| `iterations` | `LoopIteration[]` | |
| `logs` | `LoopLogEntry[]` | |
| `nextLogSeq` | `number` | Monotonic log sequence counter |
| `activeIteration` | `number?` | Currently executing iteration index |
| `activeWorkerAgentId` | `string?` | Currently running worker agent |
| `activeVerifierAgentId` | `string?` | Currently running verifier agent |
### Nested: LoopIteration
| Field | Type | Description |
|---|---|---|
| `index` | `number` | 1-based iteration index |
| `workerAgentId` | `string?` | Agent ID of the worker |
| `workerStartedAt` | `string` (ISO 8601) | |
| `workerCompletedAt` | `string?` (ISO 8601) | |
| `verifierAgentId` | `string?` | Agent ID of the verifier |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `workerOutcome` | `"completed" \| "failed" \| "canceled"?` | |
| `failureReason` | `string?` | |
| `verifyChecks` | `LoopVerifyCheckResult[]` | Shell check results |
| `verifyPrompt` | `LoopVerifyPromptResult?` | LLM verification result |
| Field | Type | Description |
| ------------------- | --------------------------------------------------- | ------------------------ |
| `index` | `number` | 1-based iteration index |
| `workerAgentId` | `string?` | Agent ID of the worker |
| `workerStartedAt` | `string` (ISO 8601) | |
| `workerCompletedAt` | `string?` (ISO 8601) | |
| `verifierAgentId` | `string?` | Agent ID of the verifier |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `workerOutcome` | `"completed" \| "failed" \| "canceled"?` | |
| `failureReason` | `string?` | |
| `verifyChecks` | `LoopVerifyCheckResult[]` | Shell check results |
| `verifyPrompt` | `LoopVerifyPromptResult?` | LLM verification result |
### Nested: LoopLogEntry
| Field | Type |
|---|---|
| `seq` | `number` (monotonic) |
| `timestamp` | `string` (ISO 8601) |
| `iteration` | `number?` |
| `source` | `"loop" \| "worker" \| "verifier" \| "verify-check"` |
| `level` | `"info" \| "error"` |
| `text` | `string` |
| Field | Type |
| ----------- | ---------------------------------------------------- |
| `seq` | `number` (monotonic) |
| `timestamp` | `string` (ISO 8601) |
| `iteration` | `number?` |
| `source` | `"loop" \| "worker" \| "verifier" \| "verify-check"` |
| `level` | `"info" \| "error"` |
| `text` | `string` |
### Nested: LoopVerifyCheckResult
| Field | Type |
|---|---|
| `command` | `string` |
| `exitCode` | `number` |
| `passed` | `boolean` |
| `stdout` | `string` |
| `stderr` | `string` |
| `startedAt` | `string` (ISO 8601) |
| Field | Type |
| ------------- | ------------------- |
| `command` | `string` |
| `exitCode` | `number` |
| `passed` | `boolean` |
| `stdout` | `string` |
| `stderr` | `string` |
| `startedAt` | `string` (ISO 8601) |
| `completedAt` | `string` (ISO 8601) |
### Nested: LoopVerifyPromptResult
| Field | Type |
|---|---|
| `passed` | `boolean` |
| `reason` | `string` |
| `verifierAgentId` | `string?` |
| `startedAt` | `string` (ISO 8601) |
| `completedAt` | `string` (ISO 8601) |
| Field | Type |
| ----------------- | ------------------- |
| `passed` | `boolean` |
| `reason` | `string` |
| `verifierAgentId` | `string?` |
| `startedAt` | `string` (ISO 8601) |
| `completedAt` | `string` (ISO 8601) |
---
@@ -344,15 +344,15 @@ Single file containing an array of all loop records.
Array of project records.
| Field | Type | Description |
|---|---|---|
| `projectId` | `string` | Primary key |
| `rootPath` | `string` | Filesystem root of the project |
| `kind` | `"git" \| "non_git"` | |
| `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
| Field | Type | Description |
| ------------- | -------------------- | ------------------------------ |
| `projectId` | `string` | Primary key |
| `rootPath` | `string` | Filesystem root of the project |
| `kind` | `"git" \| "non_git"` | |
| `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
---
@@ -362,16 +362,16 @@ Array of project records.
Array of workspace records. A workspace is a specific working directory within a project.
| Field | Type | Description |
|---|---|---|
| `workspaceId` | `string` | Primary key |
| `projectId` | `string` | FK to Project.projectId |
| `cwd` | `string` | Filesystem path |
| `kind` | `"local_checkout" \| "worktree" \| "directory"` | |
| `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
| Field | Type | Description |
| ------------- | ----------------------------------------------- | ----------------------- |
| `workspaceId` | `string` | Primary key |
| `projectId` | `string` | FK to Project.projectId |
| `cwd` | `string` | Filesystem path |
| `kind` | `"local_checkout" \| "worktree" \| "directory"` | |
| `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
---
@@ -417,12 +417,12 @@ Stores binary attachment blobs keyed by attachment ID.
### AttachmentMetadata
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Unique attachment ID |
| `mimeType` | `string` | MIME type |
| `storageType` | `string` | Storage backend identifier |
| `storageKey` | `string` | Key within the storage backend |
| `createdAt` | `number` | Epoch ms |
| `fileName` | `string?` | Original filename |
| `byteSize` | `number?` | Size in bytes |
| Field | Type | Description |
| ------------- | --------- | ------------------------------ |
| `id` | `string` | Unique attachment ID |
| `mimeType` | `string` | MIME type |
| `storageType` | `string` | Storage backend identifier |
| `storageKey` | `string` | Key within the storage backend |
| `createdAt` | `number` | Epoch ms |
| `fileName` | `string?` | Original filename |
| `byteSize` | `number?` | Size in bytes |

View File

@@ -31,6 +31,7 @@ PASEO_HOME=~/.paseo-blue npm run dev
### Default ports
In the main checkout:
- Daemon: `localhost:6767`
- Expo app: `localhost:8081`
@@ -63,13 +64,13 @@ Pass either a DB directory or a `paseo.sqlite` file to `--db`. The script opens
Every `scripts` entry with `"type": "service"` receives these environment variables:
| Variable | Value |
|---|---|
| `PASEO_SERVICE_<NAME>_URL` | Proxied daemon URL for a declared peer service. Prefer this for peer discovery; it survives peer restarts. |
| Variable | Value |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `PASEO_SERVICE_<NAME>_URL` | Proxied daemon 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. |
| `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. |
`<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.
@@ -135,11 +136,13 @@ $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/
```
@@ -149,11 +152,13 @@ rg -l "some title text" $PASEO_HOME/agents/
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
```

View File

@@ -35,8 +35,8 @@ cat node_modules/material-icon-theme/icons/ICON_NAME.svg
```
3. Add two things to `material-file-icons.ts`:
- The SVG string in `SVG_ICONS`:
```ts
"icon_name": `<svg ...>...</svg>`,
```
@@ -52,58 +52,58 @@ cat node_modules/material-icon-theme/icons/ICON_NAME.svg
53 unique icons covering these extensions:
| Extension(s) | Icon |
|---|---|
| `ts` | typescript |
| `tsx` | react_ts |
| `js` | javascript |
| `jsx` | react |
| `py` | python |
| `go` | go |
| `rs` | rust |
| `rb` | ruby |
| `java` | java |
| `kt` | kotlin |
| `c` | c |
| `cpp` | cpp |
| `h` | h |
| `hpp` | hpp |
| `cs` | csharp |
| `swift` | swift |
| `dart` | dart |
| `ex`, `exs` | elixir |
| `erl` | erlang |
| `hs` | haskell |
| `clj` | clojure |
| `scala` | scala |
| `ml` | ocaml |
| `r` | r |
| `lua` | lua |
| `zig` | zig |
| `nix` | nix |
| `php` | php |
| `html` | html |
| `css` | css |
| `scss` | sass |
| `less` | less |
| `json` | json |
| `yml`, `yaml` | yaml |
| `xml` | xml |
| `toml` | toml |
| `md`, `markdown` | markdown |
| `sql` | database |
| `graphql`, `gql` | graphql |
| `sh`, `bash` | console |
| `tf` | terraform |
| `hcl` | hcl |
| `vue` | vue |
| `svelte` | svelte |
| `astro` | astro |
| `wasm` | webassembly |
| `svg` | svg |
| `png`, `jpg`, `jpeg`, `gif`, `webp`, `ico` | image |
| `txt` | document |
| `conf`, `cfg`, `ini` | settings |
| `lock` | lock |
| `groovy` | groovy |
| `gradle` | gradle |
| Extension(s) | Icon |
| ------------------------------------------ | ----------- |
| `ts` | typescript |
| `tsx` | react_ts |
| `js` | javascript |
| `jsx` | react |
| `py` | python |
| `go` | go |
| `rs` | rust |
| `rb` | ruby |
| `java` | java |
| `kt` | kotlin |
| `c` | c |
| `cpp` | cpp |
| `h` | h |
| `hpp` | hpp |
| `cs` | csharp |
| `swift` | swift |
| `dart` | dart |
| `ex`, `exs` | elixir |
| `erl` | erlang |
| `hs` | haskell |
| `clj` | clojure |
| `scala` | scala |
| `ml` | ocaml |
| `r` | r |
| `lua` | lua |
| `zig` | zig |
| `nix` | nix |
| `php` | php |
| `html` | html |
| `css` | css |
| `scss` | sass |
| `less` | less |
| `json` | json |
| `yml`, `yaml` | yaml |
| `xml` | xml |
| `toml` | toml |
| `md`, `markdown` | markdown |
| `sql` | database |
| `graphql`, `gql` | graphql |
| `sh`, `bash` | console |
| `tf` | terraform |
| `hcl` | hcl |
| `vue` | vue |
| `svelte` | svelte |
| `astro` | astro |
| `wasm` | webassembly |
| `svg` | svg |
| `png`, `jpg`, `jpeg`, `gif`, `webp`, `ico` | image |
| `txt` | document |
| `conf`, `cfg`, `ini` | settings |
| `lock` | lock |
| `groovy` | groovy |
| `gradle` | gradle |

View File

@@ -223,7 +223,7 @@ const styles = StyleSheet.create((theme) => ({
},
}));
<Animated.View style={[styles.sidebar, animatedStyle]} />
<Animated.View style={[styles.sidebar, animatedStyle]} />;
```
```tsx
@@ -244,7 +244,7 @@ const { theme } = useUnistyles();
<Animated.View
style={[staticStyles.sidebar, animatedStyle, { backgroundColor: theme.colors.surfaceSidebar }]}
/>
/>;
```
Regular `View` components can safely use Unistyles dynamic styles — the conflict is specific to `Animated.View`.

View File

@@ -30,6 +30,7 @@ Each project opens as a workspace. For git projects, the default workspace is th
### Inside a workspace
A workspace is a flexible canvas:
- Launch multiple agents side by side in split panes
- Open terminals alongside agents
- Mix and match providers within the same workspace
@@ -39,6 +40,7 @@ A workspace is a flexible canvas:
Paseo is a client-server system. The daemon (Node.js) runs on your machine, manages agent processes, and streams output in real time over WebSocket. Clients connect to the daemon — locally or remotely.
This architecture means:
- The daemon can run on any machine: laptop, VM, remote server
- Multiple clients can connect simultaneously
- Agents keep running when you close the app

View File

@@ -58,10 +58,10 @@ type MyProviderClientOptions = {
export class MyProviderACPAgentClient extends ACPAgentClient {
constructor(options: MyProviderClientOptions) {
super({
provider: "my-provider", // Must match the ID used everywhere else
provider: "my-provider", // Must match the ID used everywhere else
logger: options.logger,
runtimeSettings: options.runtimeSettings,
defaultCommand: ["my-agent-binary", "--acp"], // CLI command to spawn
defaultCommand: ["my-agent-binary", "--acp"], // CLI command to spawn
defaultModes: MY_PROVIDER_MODES,
capabilities: MY_PROVIDER_CAPABILITIES,
});
@@ -70,7 +70,7 @@ export class MyProviderACPAgentClient extends ACPAgentClient {
// Override isAvailable() if the provider needs specific auth/env vars
override async isAvailable(): Promise<boolean> {
if (!(await super.isAvailable())) {
return false; // Binary not found
return false; // Binary not found
}
return Boolean(process.env["MY_PROVIDER_API_KEY"]);
}
@@ -212,8 +212,8 @@ export const agentConfigs = {
provider: "my-provider",
model: "default-model-id",
modes: {
full: "autonomous", // Mode with no permission prompts
ask: "default", // Mode that requires permission approval
full: "autonomous", // Mode with no permission prompts
ask: "default", // Mode that requires permission approval
},
},
} as const satisfies Record<string, AgentTestConfig>;
@@ -264,8 +264,15 @@ If your agent does not speak ACP, implement the interfaces from `agent-sdk-types
interface AgentClient {
readonly provider: AgentProvider;
readonly capabilities: AgentCapabilityFlags;
createSession(config: AgentSessionConfig, launchContext?: AgentLaunchContext): Promise<AgentSession>;
resumeSession(handle: AgentPersistenceHandle, overrides?: Partial<AgentSessionConfig>, launchContext?: AgentLaunchContext): Promise<AgentSession>;
createSession(
config: AgentSessionConfig,
launchContext?: AgentLaunchContext,
): Promise<AgentSession>;
resumeSession(
handle: AgentPersistenceHandle,
overrides?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
): Promise<AgentSession>;
listModels(options?: ListModelsOptions): Promise<AgentModelDefinition[]>;
isAvailable(): Promise<boolean>;
// Optional:

View File

@@ -79,7 +79,9 @@ const realSender: EmailSender = { send: sendgrid.send };
function createTestEmailSender() {
const sent: Array<{ to: string; body: string }> = [];
return {
send: async (to: string, body: string) => { sent.push({ to, body }); },
send: async (to: string, body: string) => {
sent.push({ to, body });
},
sent,
};
}
@@ -115,6 +117,7 @@ The test output is the source of truth, not your reading of the code.
## Design for testability
If code isn't testable, refactor it. Signs:
- You want to reach for a mock
- You can't inject a dependency
- You need to test private internals

View File

@@ -19,7 +19,7 @@ The important detail: the automatic native path tracks `props.style`. It does no
Avoid this pattern when the style depends on the theme:
```tsx
<ScrollView contentContainerStyle={styles.container} />
<ScrollView contentContainerStyle={styles.container} />;
const styles = StyleSheet.create((theme) => ({
container: {
@@ -39,10 +39,8 @@ Preferred pattern: put themed backgrounds on a normal wrapper view, and keep `co
```tsx
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
{children}
</ScrollView>
</View>
<ScrollView contentContainerStyle={styles.contentContainer}>{children}</ScrollView>
</View>;
const styles = StyleSheet.create((theme) => ({
container: {
@@ -66,10 +64,7 @@ import { StyleSheet, withUnistyles } from "react-native-unistyles";
const ThemedScrollView = withUnistyles(ScrollView);
<ThemedScrollView
style={styles.scrollView}
contentContainerStyle={styles.contentContainer}
/>
<ThemedScrollView style={styles.scrollView} contentContainerStyle={styles.contentContainer} />;
```
`withUnistyles` extracts dependency metadata from both `style` and `contentContainerStyle`, subscribes to the relevant theme/runtime changes, and re-renders only that wrapped component when needed. Its [auto-mapping behavior for `style` and `contentContainerStyle`](https://www.unistyl.es/v3/references/with-unistyles#auto-mapping-for-style-and-contentcontainerstyle-props) is the reason it fixes themed `ScrollView` content containers. Reach for it when wrapper-view layout would be awkward or when a third-party component needs theme-aware non-`style` props mapped through Unistyles.
@@ -80,11 +75,8 @@ The smallest escape hatch is to use `useUnistyles()` and pass an inline value th
const { theme } = useUnistyles();
<ScrollView
contentContainerStyle={[
styles.contentContainer,
{ backgroundColor: theme.colors.surface0 },
]}
/>
contentContainerStyle={[styles.contentContainer, { backgroundColor: theme.colors.surface0 }]}
/>;
```
Use this sparingly. It works because React re-renders the prop, but it gives up the main Unistyles native-update path for that value.
@@ -98,8 +90,14 @@ The sharp edge: Unistyles hashes styles by value. If `withUnistyles` receives a
Concrete regression we hit: `welcome-screen.tsx` had `const ThemedScrollView = withUnistyles(ScrollView)` with `style={{ flex: 1, backgroundColor: theme.colors.surface0 }}`. `agent-panel.tsx` had `root` and `container` styles with the exact same value. All three collided on class `unistyles_j2k2iilhfz`, so the browser stylesheet contained:
```css
.unistyles_j2k2iilhfz { flex: 1 1 0%; background-color: var(--colors-surface0); }
.unistyles_j2k2iilhfz > * { flex: 1 1 0%; background-color: var(--colors-surface0); }
.unistyles_j2k2iilhfz {
flex: 1 1 0%;
background-color: var(--colors-surface0);
}
.unistyles_j2k2iilhfz > * {
flex: 1 1 0%;
background-color: var(--colors-surface0);
}
```
The child-selector rule forced `flex:1` and `background-color: surface0` onto the Composer's outer `Animated.View` (a direct child of `container`), stretching it to fill remaining space and leaving a large empty gap between the composer UI and the bottom of the screen. It also painted a `surface0` band behind the scroll-to-bottom button. The bug only appeared in the browser — Electron skips `WelcomeScreen` after pairing, so the `> *` rule was never injected there.
@@ -113,8 +111,10 @@ Symptoms to watch for:
Quick confirmation in DevTools console:
```js
[...document.styleSheets].flatMap(s => [...(s.cssRules || [])])
.map(r => r.cssText).filter(t => t.includes("unistyles") && t.includes("> *"));
[...document.styleSheets]
.flatMap((s) => [...(s.cssRules || [])])
.map((r) => r.cssText)
.filter((t) => t.includes("unistyles") && t.includes("> *"));
```
Any match beyond benign `r-pointerEvents-* > *` rules from react-native-web is a leak.
@@ -130,9 +130,7 @@ We saw this in `AdaptiveModalSheet`: the body text and buttons were dark-theme-c
```tsx
const { theme } = useUnistyles();
<Text style={[styles.title, { color: theme.colors.foreground }]}>
{title}
</Text>
<Text style={[styles.title, { color: theme.colors.foreground }]}>{title}</Text>;
```
Keep layout and typography in `StyleSheet.create`; move only the stale theme-dependent value through React. If a larger subtree shows the same behavior, consider remounting the sheet on theme changes or moving the themed paint onto a wrapper that is mounted with the visible content.
@@ -170,7 +168,7 @@ Use `useUnistyles()` inside the component instead:
```tsx
const { theme } = useUnistyles();
<ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
<ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />;
```
Importing `baseColors`, theme-name constants, or `type Theme` is fine when the value is intentionally static or type-only.
@@ -233,4 +231,4 @@ The welcome-screen investigation used this approach to prove the white layer was
- [GitHub issue #550: ScrollView sticky-header theme updates](https://github.com/jpudysz/react-native-unistyles/issues/550)
- [GitHub issue #817: `UnistylesRuntime.themeName` does not re-render](https://github.com/jpudysz/react-native-unistyles/issues/817)
- [GitHub issue #1030: `Image.tintColor` and native style update edge case](https://github.com/jpudysz/react-native-unistyles/issues/1030)
- [Local research note: welcome theme split](</Users/moboudra/.paseo/notes/welcome-theme-split-research.md>)
- [Local research note: welcome theme split](/Users/moboudra/.paseo/notes/welcome-theme-split-research.md)

View File

@@ -92,9 +92,7 @@ type DaemonClientConfig = {
};
async function loadDaemonClientConstructor(): Promise<
new (
config: DaemonClientConfig,
) => DaemonClientInstance
new (config: DaemonClientConfig) => DaemonClientInstance
> {
const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL(

View File

@@ -81,9 +81,7 @@ type ArchiveTabDaemonClientConfig = {
};
async function loadDaemonClientConstructor(): Promise<
new (
config: ArchiveTabDaemonClientConfig,
) => ArchiveTabDaemonClient
new (config: ArchiveTabDaemonClientConfig) => ArchiveTabDaemonClient
> {
const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL(

View File

@@ -49,9 +49,7 @@ function getDaemonWsUrl(): string {
}
async function loadDaemonClientConstructor(): Promise<
new (
config: NewWorkspaceDaemonClientConfig,
) => NewWorkspaceDaemonClient
new (config: NewWorkspaceDaemonClientConfig) => NewWorkspaceDaemonClient
> {
const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL(

View File

@@ -63,9 +63,7 @@ type TerminalPerfDaemonClientConfig = {
};
async function loadDaemonClientConstructor(): Promise<
new (
config: TerminalPerfDaemonClientConfig,
) => TerminalPerfDaemonClient
new (config: TerminalPerfDaemonClientConfig) => TerminalPerfDaemonClient
> {
const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL(

View File

@@ -24,4 +24,3 @@ appId: sh.paseo
commands:
- tapOn:
point: "50%,20%"

View File

@@ -3,4 +3,3 @@ appId: sh.paseo
- launchApp:
clearState: true
- runFlow: flows/dev-client.yaml

View File

@@ -1,8 +1,8 @@
{
"name": "@getpaseo/app",
"main": "index.ts",
"version": "0.1.62",
"private": true,
"main": "index.ts",
"scripts": {
"start": "APP_VARIANT=development expo start",
"reset-project": "node ./scripts/reset-project.js",

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="%LANG_ISO_CODE%">
<head>
<meta charset="utf-8" />
@@ -21,8 +21,9 @@
}
/* Prevent white flash before React mounts */
@media (prefers-color-scheme: dark) {
html, body {
background-color: #181B1A;
html,
body {
background-color: #181b1a;
}
}
/* These styles make the root element full-height */
@@ -38,17 +39,17 @@
input,
textarea,
select,
[role='button'],
[role='link'],
[role='textbox'],
[role='combobox'],
[role='tab'],
[role='switch'],
[role='checkbox'],
[role='slider'],
[role='menuitem'],
[role="button"],
[role="link"],
[role="textbox"],
[role="combobox"],
[role="tab"],
[role="switch"],
[role="checkbox"],
[role="slider"],
[role="menuitem"],
[tabindex],
[contenteditable='true'] {
[contenteditable="true"] {
-webkit-app-region: no-drag !important;
}
@@ -59,7 +60,7 @@
outline: none;
}
*:focus-visible {
outline: 2px solid #20744A;
outline: 2px solid #20744a;
outline-offset: 2px;
}
</style>

View File

@@ -7,9 +7,8 @@ let attachmentStorePromise: Promise<AttachmentStore> | null = null;
async function createAttachmentStore(): Promise<AttachmentStore> {
if (isWeb) {
if (isElectronRuntime()) {
const { createDesktopAttachmentStore } = await import(
"../desktop/attachments/desktop-attachment-store"
);
const { createDesktopAttachmentStore } =
await import("../desktop/attachments/desktop-attachment-store");
return createDesktopAttachmentStore();
}

View File

@@ -82,13 +82,7 @@ vi.mock("expo-image", () => ({
vi.mock("react-native", async () => {
const actual = await vi.importActual<Record<string, unknown>>("react-native");
const Modal = ({
visible = true,
children,
}: {
visible?: boolean;
children?: React.ReactNode;
}) =>
const Modal = ({ visible = true, children }: { visible?: boolean; children?: React.ReactNode }) =>
visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null;
return { ...actual, Modal };
});

View File

@@ -285,13 +285,7 @@ vi.mock("expo-image", () => ({
vi.mock("react-native", async () => {
const actual = await vi.importActual<Record<string, unknown>>("react-native");
const Modal = ({
visible = true,
children,
}: {
visible?: boolean;
children?: React.ReactNode;
}) =>
const Modal = ({ visible = true, children }: { visible?: boolean; children?: React.ReactNode }) =>
visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null;
return { ...actual, Modal };
});

View File

@@ -1954,8 +1954,8 @@ export function SidebarWorkspaceList({
select: toProjectIconDataUri,
enabled: Boolean(
getHostRuntimeStore().getClient(request.serverId) &&
isHostRuntimeConnected(getHostRuntimeStore().getSnapshot(request.serverId)) &&
request.cwd,
isHostRuntimeConnected(getHostRuntimeStore().getSnapshot(request.serverId)) &&
request.cwd,
),
staleTime: Infinity,
gcTime: 1000 * 60 * 60,

View File

@@ -134,18 +134,17 @@ interface SplitNodeViewProps extends Omit<SplitContainerProps, "layout"> {
tabDropPreview: TabDropPreview | null;
}
interface SplitPaneViewProps
extends Omit<
SplitNodeViewProps,
| "node"
| "workspaceKey"
| "focusedPaneId"
| "activeDragTabId"
| "showDropZones"
| "dropPreview"
| "onMoveTabToPane"
| "onResizeSplit"
> {
interface SplitPaneViewProps extends Omit<
SplitNodeViewProps,
| "node"
| "workspaceKey"
| "focusedPaneId"
| "activeDragTabId"
| "showDropZones"
| "dropPreview"
| "onMoveTabToPane"
| "onResizeSplit"
> {
pane: SplitPane;
uiTabs: WorkspaceTab[];
isFocused: boolean;

View File

@@ -237,23 +237,24 @@ describe("providers snapshot hook cache scope", () => {
it.each([
{ name: "missing", entries: [] },
{ name: "loading", entries: [codexEntry("loading")] },
])("ensures a selected provider snapshot on selector open when it is $name", async ({
entries,
}) => {
enableProvidersSnapshot();
mockClient.getProvidersSnapshot
.mockResolvedValueOnce(providersSnapshot(entries))
.mockResolvedValueOnce(providersSnapshot([codexEntry("ready", [readyCodexModel])]));
])(
"ensures a selected provider snapshot on selector open when it is $name",
async ({ entries }) => {
enableProvidersSnapshot();
mockClient.getProvidersSnapshot
.mockResolvedValueOnce(providersSnapshot(entries))
.mockResolvedValueOnce(providersSnapshot([codexEntry("ready", [readyCodexModel])]));
const { result } = renderProvidersSnapshotHook();
const { result } = renderProvidersSnapshotHook();
await waitForSnapshotEntries(result, entries);
await openSelectorForSelectedProvider(result);
await waitForSnapshotReads(2);
await waitForSnapshotEntries(result, entries);
await openSelectorForSelectedProvider(result);
await waitForSnapshotReads(2);
expect(mockClient.getProvidersSnapshot).toHaveBeenLastCalledWith({});
expect(mockClient.refreshProvidersSnapshot).not.toHaveBeenCalled();
});
expect(mockClient.getProvidersSnapshot).toHaveBeenLastCalledWith({});
expect(mockClient.refreshProvidersSnapshot).not.toHaveBeenCalled();
},
);
it("does not ensure a selected provider snapshot on selector open when the provider is ready with no models", async () => {
enableProvidersSnapshot();

View File

@@ -51,7 +51,7 @@ export function resolveKeyboardFocusScope(input: {
candidates.some((element) =>
Boolean(
element.closest("[data-testid='command-center-panel']") ||
element.closest("[data-testid='command-center-input']"),
element.closest("[data-testid='command-center-input']"),
),
)
) {

View File

@@ -289,23 +289,19 @@ describe("keyboard-shortcuts", () => {
},
];
it.each(matchingCases)("$name", ({
event,
context,
action,
payload,
preventDefault,
stopPropagation,
}) => {
expectShortcutResolution({
event,
context,
action,
...(payload !== undefined ? { payload } : {}),
...(preventDefault !== undefined ? { preventDefault } : {}),
...(stopPropagation !== undefined ? { stopPropagation } : {}),
});
});
it.each(matchingCases)(
"$name",
({ event, context, action, payload, preventDefault, stopPropagation }) => {
expectShortcutResolution({
event,
context,
action,
...(payload !== undefined ? { payload } : {}),
...(preventDefault !== undefined ? { preventDefault } : {}),
...(stopPropagation !== undefined ? { stopPropagation } : {}),
});
},
);
const nonMatchingCases: NonMatchingShortcutCase[] = [
{

View File

@@ -491,10 +491,7 @@ export class HostRuntimeController {
private probeRequestVersion = 0;
private probeCycleInFlight: Promise<void> | null = null;
constructor(input: {
host: HostProfile;
deps?: HostRuntimeControllerDeps;
}) {
constructor(input: { host: HostProfile; deps?: HostRuntimeControllerDeps }) {
this.host = input.host;
this.deps = input.deps ?? createDefaultDeps();
this.connectionMachineState = {
@@ -1155,9 +1152,7 @@ export class HostRuntimeStore {
private storageLoaded = false;
private bootstrapAttempted = false;
constructor(input?: {
deps?: HostRuntimeControllerDeps;
}) {
constructor(input?: { deps?: HostRuntimeControllerDeps }) {
this.deps = input?.deps ?? createDefaultDeps();
}

View File

@@ -230,11 +230,11 @@ export function WorkspaceDraftAgentTab({
const isReadyForPendingAutoSubmit = Boolean(
pendingAutoSubmit &&
draftInput.isHydrated &&
workspaceDirectory &&
client &&
!isSubmitting &&
!composerState.isModelLoading,
draftInput.isHydrated &&
workspaceDirectory &&
client &&
!isSubmitting &&
!composerState.isModelLoading,
);
const autoSubmitKeyRef = useRef<string | null>(null);
useEffect(() => {

View File

@@ -36,10 +36,10 @@ export function hasMeaningfulToolCallDetail(detail: ToolCallDetail | undefined):
case "search":
return Boolean(
detail.query.trim().length > 0 ||
detail.content ||
(detail.filePaths && detail.filePaths.length > 0) ||
(detail.webResults && detail.webResults.length > 0) ||
(detail.annotations && detail.annotations.length > 0),
detail.content ||
(detail.filePaths && detail.filePaths.length > 0) ||
(detail.webResults && detail.webResults.length > 0) ||
(detail.annotations && detail.annotations.length > 0),
);
case "fetch":
return Boolean(detail.url || detail.result || detail.codeText);

View File

@@ -2,17 +2,17 @@
"name": "@getpaseo/cli",
"version": "0.1.62",
"description": "Paseo CLI - control your AI coding agents from the command line",
"type": "module",
"bin": {
"paseo": "bin/paseo"
},
"files": [
"bin",
"dist"
],
"type": "module",
"publishConfig": {
"access": "public"
},
"bin": {
"paseo": "bin/paseo"
},
"scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build",

View File

@@ -3,6 +3,16 @@
"version": "0.1.62",
"private": true,
"description": "Paseo desktop app (Electron wrapper)",
"homepage": "https://paseo.sh",
"license": "AGPL-3.0-or-later",
"author": {
"name": "Mohamed Boudra",
"email": "hello@moboudra.com"
},
"repository": {
"type": "git",
"url": "https://github.com/getpaseo/paseo.git"
},
"main": "dist/main.js",
"scripts": {
"build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml",
@@ -27,15 +37,5 @@
"typescript": "5.9.3",
"unzip-crx-3": "^0.2.0",
"wait-on": "8.0.5"
},
"homepage": "https://paseo.sh",
"repository": {
"type": "git",
"url": "https://github.com/getpaseo/paseo.git"
},
"author": {
"name": "Mohamed Boudra",
"email": "hello@moboudra.com"
},
"license": "AGPL-3.0-or-later"
}
}

View File

@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@@ -125,4 +125,4 @@ enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
https://www.contributor-covenant.org/translations.

View File

@@ -18,18 +18,20 @@ This example showcases the basic functions of the expo-two-way-audio module:
In the `examples/basic-usage` directory:
1. Install dependencies:
```sh
npm install
```
```sh
npm install
```
2. Run the app:
```sh
# Run iOS
npm run ios
# Run Android
npm run android
```
```sh
# Run iOS
npm run ios
# Run Android
npm run android
```
## Notes

View File

@@ -1,6 +1,7 @@
{
"name": "expo-two-way-audio-example",
"version": "1.0.0",
"private": true,
"main": "expo/AppEntry.js",
"scripts": {
"start": "expo start",
@@ -11,17 +12,16 @@
"dependencies": {
"buffer": "^6.0.3",
"expo": "^52.0.0",
"react": "18.3.1",
"react-native": "0.76.2",
"expo-build-properties": "~0.13.1",
"expo-dev-client": "~5.0.3",
"expo-build-properties": "~0.13.1"
"react": "18.3.1",
"react-native": "0.76.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.3.12",
"typescript": "^5.1.3"
},
"private": true,
"expo": {
"autolinking": {
"nativeModulesDir": "../.."

View File

@@ -1,6 +1,7 @@
{
"name": "expo-two-way-audio-flow-example",
"version": "1.0.0",
"private": true,
"main": "expo/AppEntry.js",
"scripts": {
"start": "expo start",
@@ -25,7 +26,6 @@
"@types/react": "~18.3.12",
"typescript": "^5.1.3"
},
"private": true,
"expo": {
"autolinking": {
"nativeModulesDir": "../.."

View File

@@ -1,10 +1,10 @@
{
"name": "@getpaseo/highlight",
"version": "0.1.62",
"files": [
"dist"
],
"type": "module",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
@@ -14,9 +14,9 @@
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build",

View File

@@ -2,14 +2,11 @@
"name": "@getpaseo/relay",
"version": "0.1.62",
"description": "Paseo relay for bridging daemon and client connections",
"type": "module",
"publishConfig": {
"access": "public"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -30,6 +27,9 @@
"default": "./src/cloudflare-adapter.ts"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build",

View File

@@ -199,169 +199,176 @@ async function stopRelayProcess(relayProcess: ChildProcess): Promise<void> {
}
}, SHUTDOWN_TIMEOUT_MS);
it("full flow: daemon and client exchange encrypted messages through relay", {
timeout: 90_000,
}, async () => {
const serverId = "test-session-" + Date.now();
const connectionId = "clt_test_" + Date.now() + "_" + Math.random().toString(36).slice(2);
it(
"full flow: daemon and client exchange encrypted messages through relay",
{
timeout: 90_000,
},
async () => {
const serverId = "test-session-" + Date.now();
const connectionId = "clt_test_" + Date.now() + "_" + Math.random().toString(36).slice(2);
// === DAEMON SIDE ===
// Generate keypair (public key goes in QR)
const daemonKeyPair = await generateKeyPair();
const daemonPubKeyB64 = await exportPublicKey(daemonKeyPair.publicKey);
// === DAEMON SIDE ===
// Generate keypair (public key goes in QR)
const daemonKeyPair = await generateKeyPair();
const daemonPubKeyB64 = await exportPublicKey(daemonKeyPair.publicKey);
// QR would contain: { serverId, daemonPubKeyB64, relay: { endpoint } }
// QR would contain: { serverId, daemonPubKeyB64, relay: { endpoint } }
// Daemon connects to relay as "server" control role
const daemonControlWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&v=2`,
);
// Daemon connects to relay as "server" control role
const daemonControlWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&v=2`,
);
await new Promise<void>((resolve, reject) => {
daemonControlWs.on("open", resolve);
daemonControlWs.on("error", reject);
});
await new Promise<void>((resolve, reject) => {
daemonControlWs.on("open", resolve);
daemonControlWs.on("error", reject);
});
// === CLIENT SIDE ===
// Client scans QR, gets daemon's public key and session ID
// Client generates own keypair
const clientKeyPair = await generateKeyPair();
const clientPubKeyB64 = await exportPublicKey(clientKeyPair.publicKey);
// === CLIENT SIDE ===
// Client scans QR, gets daemon's public key and session ID
// Client generates own keypair
const clientKeyPair = await generateKeyPair();
const clientPubKeyB64 = await exportPublicKey(clientKeyPair.publicKey);
// Client imports daemon's public key and derives shared secret
const daemonPubKeyOnClient = await importPublicKey(daemonPubKeyB64);
const clientSharedKey = await deriveSharedKey(clientKeyPair.secretKey, daemonPubKeyOnClient);
// Client imports daemon's public key and derives shared secret
const daemonPubKeyOnClient = await importPublicKey(daemonPubKeyB64);
const clientSharedKey = await deriveSharedKey(clientKeyPair.secretKey, daemonPubKeyOnClient);
const waitForClientSeen = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("timed out waiting for connected")), 5000);
const onMessage = (raw: unknown) => {
try {
const text =
typeof raw === "string"
? raw
: raw && typeof (raw as any).toString === "function"
? (raw as any).toString()
: "";
const msg = JSON.parse(text);
if (msg?.type === "connected" && msg.connectionId === connectionId) {
clearTimeout(timeout);
daemonControlWs.off("message", onMessage);
resolve();
return;
const waitForClientSeen = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error("timed out waiting for connected")),
5000,
);
const onMessage = (raw: unknown) => {
try {
const text =
typeof raw === "string"
? raw
: raw && typeof (raw as any).toString === "function"
? (raw as any).toString()
: "";
const msg = JSON.parse(text);
if (msg?.type === "connected" && msg.connectionId === connectionId) {
clearTimeout(timeout);
daemonControlWs.off("message", onMessage);
resolve();
return;
}
if (
msg?.type === "sync" &&
Array.isArray(msg.connectionIds) &&
msg.connectionIds.includes(connectionId)
) {
clearTimeout(timeout);
daemonControlWs.off("message", onMessage);
resolve();
}
} catch {
// ignore
}
if (
msg?.type === "sync" &&
Array.isArray(msg.connectionIds) &&
msg.connectionIds.includes(connectionId)
) {
clearTimeout(timeout);
daemonControlWs.off("message", onMessage);
resolve();
}
} catch {
// ignore
}
};
daemonControlWs.on("message", onMessage);
});
};
daemonControlWs.on("message", onMessage);
});
// Client connects to relay as "client" role (must include connectionId)
const clientWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=client&connectionId=${connectionId}&v=2`,
);
// Client connects to relay as "client" role (must include connectionId)
const clientWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=client&connectionId=${connectionId}&v=2`,
);
await new Promise<void>((resolve, reject) => {
clientWs.on("open", resolve);
clientWs.on("error", reject);
});
await new Promise<void>((resolve, reject) => {
clientWs.on("open", resolve);
clientWs.on("error", reject);
});
await waitForClientSeen;
await waitForClientSeen;
const daemonWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&connectionId=${connectionId}&v=2`,
);
await new Promise<void>((resolve, reject) => {
daemonWs.on("open", resolve);
daemonWs.on("error", reject);
});
const daemonWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&connectionId=${connectionId}&v=2`,
);
await new Promise<void>((resolve, reject) => {
daemonWs.on("open", resolve);
daemonWs.on("error", reject);
});
// Client sends hello with its public key (this message is NOT encrypted - it's the handshake)
const helloMsg = JSON.stringify({ type: "hello", key: clientPubKeyB64 });
clientWs.send(helloMsg);
// Client sends hello with its public key (this message is NOT encrypted - it's the handshake)
const helloMsg = JSON.stringify({ type: "hello", key: clientPubKeyB64 });
clientWs.send(helloMsg);
// === DAEMON RECEIVES HELLO ===
const daemonReceivedHello = await new Promise<string>((resolve) => {
daemonWs.once("message", (data) => resolve(data.toString()));
});
// === DAEMON RECEIVES HELLO ===
const daemonReceivedHello = await new Promise<string>((resolve) => {
daemonWs.once("message", (data) => resolve(data.toString()));
});
const hello = JSON.parse(daemonReceivedHello);
expect(hello.type).toBe("hello");
expect(hello.key).toBe(clientPubKeyB64);
const hello = JSON.parse(daemonReceivedHello);
expect(hello.type).toBe("hello");
expect(hello.key).toBe(clientPubKeyB64);
// Daemon imports client's public key and derives shared secret
const clientPubKeyOnDaemon = await importPublicKey(hello.key);
const daemonSharedKey = await deriveSharedKey(daemonKeyPair.secretKey, clientPubKeyOnDaemon);
// Daemon imports client's public key and derives shared secret
const clientPubKeyOnDaemon = await importPublicKey(hello.key);
const daemonSharedKey = await deriveSharedKey(daemonKeyPair.secretKey, clientPubKeyOnDaemon);
// === VERIFY BOTH HAVE SAME KEY - Exchange encrypted messages ===
// === VERIFY BOTH HAVE SAME KEY - Exchange encrypted messages ===
// Daemon sends encrypted "ready" message
const readyPlaintext = JSON.stringify({ type: "ready" });
const readyCiphertext = await encrypt(daemonSharedKey, readyPlaintext);
daemonWs.send(Buffer.from(readyCiphertext));
// Daemon sends encrypted "ready" message
const readyPlaintext = JSON.stringify({ type: "ready" });
const readyCiphertext = await encrypt(daemonSharedKey, readyPlaintext);
daemonWs.send(Buffer.from(readyCiphertext));
// Client receives and decrypts
const clientReceivedReady = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedReady = await decrypt(
clientSharedKey,
clientReceivedReady.buffer.slice(
clientReceivedReady.byteOffset,
clientReceivedReady.byteOffset + clientReceivedReady.byteLength,
),
);
expect(JSON.parse(decryptedReady as string)).toEqual({ type: "ready" });
// Client receives and decrypts
const clientReceivedReady = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedReady = await decrypt(
clientSharedKey,
clientReceivedReady.buffer.slice(
clientReceivedReady.byteOffset,
clientReceivedReady.byteOffset + clientReceivedReady.byteLength,
),
);
expect(JSON.parse(decryptedReady as string)).toEqual({ type: "ready" });
// Client sends encrypted message
const clientMessage = "Hello from client!";
const clientCiphertext = await encrypt(clientSharedKey, clientMessage);
clientWs.send(Buffer.from(clientCiphertext));
// Client sends encrypted message
const clientMessage = "Hello from client!";
const clientCiphertext = await encrypt(clientSharedKey, clientMessage);
clientWs.send(Buffer.from(clientCiphertext));
// Daemon receives and decrypts
const daemonReceivedMsg = await new Promise<Buffer>((resolve) => {
daemonWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedClientMsg = await decrypt(
daemonSharedKey,
daemonReceivedMsg.buffer.slice(
daemonReceivedMsg.byteOffset,
daemonReceivedMsg.byteOffset + daemonReceivedMsg.byteLength,
),
);
expect(decryptedClientMsg).toBe(clientMessage);
// Daemon receives and decrypts
const daemonReceivedMsg = await new Promise<Buffer>((resolve) => {
daemonWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedClientMsg = await decrypt(
daemonSharedKey,
daemonReceivedMsg.buffer.slice(
daemonReceivedMsg.byteOffset,
daemonReceivedMsg.byteOffset + daemonReceivedMsg.byteLength,
),
);
expect(decryptedClientMsg).toBe(clientMessage);
// Daemon sends encrypted response
const daemonMessage = "Hello from daemon!";
const daemonCiphertext = await encrypt(daemonSharedKey, daemonMessage);
daemonWs.send(Buffer.from(daemonCiphertext));
// Daemon sends encrypted response
const daemonMessage = "Hello from daemon!";
const daemonCiphertext = await encrypt(daemonSharedKey, daemonMessage);
daemonWs.send(Buffer.from(daemonCiphertext));
// Client receives and decrypts
const clientReceivedMsg = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedDaemonMsg = await decrypt(
clientSharedKey,
clientReceivedMsg.buffer.slice(
clientReceivedMsg.byteOffset,
clientReceivedMsg.byteOffset + clientReceivedMsg.byteLength,
),
);
expect(decryptedDaemonMsg).toBe(daemonMessage);
// Client receives and decrypts
const clientReceivedMsg = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer));
});
const decryptedDaemonMsg = await decrypt(
clientSharedKey,
clientReceivedMsg.buffer.slice(
clientReceivedMsg.byteOffset,
clientReceivedMsg.byteOffset + clientReceivedMsg.byteLength,
),
);
expect(decryptedDaemonMsg).toBe(daemonMessage);
// Cleanup
daemonWs.close();
clientWs.close();
});
// Cleanup
daemonWs.close();
clientWs.close();
},
);
it("relay only sees opaque bytes after handshake", { timeout: 90_000 }, async () => {
const serverId = "opaque-test-" + Date.now();

View File

@@ -1,8 +1,4 @@
export {
createClientChannel,
createDaemonChannel,
EncryptedChannel,
} from "./encrypted-channel.js";
export { createClientChannel, createDaemonChannel, EncryptedChannel } from "./encrypted-channel.js";
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
export {

View File

@@ -1,7 +1,4 @@
export type {
ConnectionRole,
RelaySessionAttachment,
} from "./types.js";
export type { ConnectionRole, RelaySessionAttachment } from "./types.js";
export {
generateKeyPair,
@@ -12,9 +9,5 @@ export {
decrypt,
} from "./crypto.js";
export {
createClientChannel,
createDaemonChannel,
EncryptedChannel,
} from "./encrypted-channel.js";
export { createClientChannel, createDaemonChannel, EncryptedChannel } from "./encrypted-channel.js";
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";

View File

@@ -3,9 +3,7 @@ account_id = "10ed39a1dbf316e30abd0c409bed40d6"
main = "src/cloudflare-adapter.ts"
compatibility_date = "2024-12-01"
routes = [
{ pattern = "relay.paseo.sh", custom_domain = true }
]
routes = [{ pattern = "relay.paseo.sh", custom_domain = true }]
[observability]
enabled = true

View File

@@ -11,6 +11,7 @@ Paseo is a mobile + CLI app for monitoring and controlling local AI coding agent
## Build / Lint / Test Commands
### Root (monorepo)
```bash
npm run dev # Start daemon + Expo in Tmux
npm run build:daemon # Build: highlight + relay + server + cli
@@ -20,6 +21,7 @@ npm run format # Format with Biome (in-place)
```
### Server package (`packages/server`)
```bash
npm run dev # Start dev daemon (tsx watch)
npm run build # Build lib + scripts to dist/
@@ -45,6 +47,7 @@ npm run test:ui # Vitest UI at localhost:51204
```
### Other useful commands
```bash
npm run build --workspace=@getpaseo/relay # Rebuild relay before daemon
npm run build --workspace=@getpaseo/server # Rebuild server
@@ -58,13 +61,22 @@ npm run cli -- daemon status # Check daemon status
## Code Style
### Biome (formatting only, no linting)
```json
{ "indentStyle": "space", "indentWidth": 2, "lineWidth": 100, "quoteStyle": "double", "trailingCommas": "all", "semicolons": "always" }
{
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"quoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
```
### TypeScript
- **Fully strict** — no `any`, no implicit `any`
- **`interface`** over `type`** when possible
- **`interface`** over `type`\*\* when possible
- **`function` declarations** over arrow function assignments
- **Named types** — no complex inline types in public signatures
- **Object parameters** — use single object param when >1 argument
@@ -72,15 +84,18 @@ npm run cli -- daemon status # Check daemon status
- `noUnusedLocals: true`, `noUnusedParameters: true`, `noFallthroughCasesInSwitch: true`
### Imports
- Use path alias `@server/*` in server package (maps to `./src/`)
- No barrel `index.ts` re-exports — they create unnecessary indirection
### Naming
- Files: `kebab-case.ts` named after the main export (`create-tool-call.ts`)
- Tests: collocated with implementation (`thing.test.ts`)
- No prefixes like `RpcX`, `DbX`, `UiX` — keep one canonical type per concept
### Error Handling
- **Fail explicitly** — throw instead of silently returning defaults
- **Typed domain errors** — extend `Error` with structured metadata
@@ -97,10 +112,16 @@ class TimeoutError extends Error {
```
### State Design
Discriminated unions over bags of booleans/optionals:
```typescript
// Bad
interface FetchState { isLoading: boolean; error?: Error; data?: Data }
interface FetchState {
isLoading: boolean;
error?: Error;
data?: Data;
}
// Good
type FetchState =
@@ -168,10 +189,10 @@ npm run db:query -- "SELECT * FROM agent_timeline_rows..."
## Relevant Docs
| File | What it covers |
|---|---|
| [../CLAUDE.md](../CLAUDE.md) | Repository overview, critical rules, quick start |
| [../docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) | System design, WebSocket protocol, data flow |
| [../docs/CODING_STANDARDS.md](../docs/CODING_STANDARDS.md) | Type hygiene, error handling, React patterns |
| [../docs/TESTING.md](../docs/TESTING.md) | TDD workflow, determinism, real deps over mocks |
| [../SECURITY.md](../SECURITY.md) | Relay threat model, E2E encryption |
| File | What it covers |
| ---------------------------------------------------------- | ------------------------------------------------ |
| [../CLAUDE.md](../CLAUDE.md) | Repository overview, critical rules, quick start |
| [../docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) | System design, WebSocket protocol, data flow |
| [../docs/CODING_STANDARDS.md](../docs/CODING_STANDARDS.md) | Type hygiene, error handling, React patterns |
| [../docs/TESTING.md](../docs/TESTING.md) | TDD workflow, determinism, real deps over mocks |
| [../SECURITY.md](../SECURITY.md) | Relay threat model, E2E encryption |

View File

@@ -49,15 +49,18 @@ npm start
## Project Status
**✅ Completed** (Phases 1-2):
- Package setup and configuration
- Express server with WebSocket
- React UI with Vite
- WebSocket client with ping/pong testing
**⏳ In Progress** (Phase 3):
- Terminal control (tmux integration)
**📋 Planned** (Phases 4-9):
- LLM integration (OpenAI GPT-4)
- Agent orchestrator
- Speech-to-Text (Deepgram)
@@ -95,6 +98,7 @@ PASEO_HOME=~/.paseo-blue PASEO_LISTEN=127.0.0.1:7777 npm run dev
## Testing
Currently manual testing via:
1. Start servers: `npm run dev`
2. Open http://localhost:5173
3. Test WebSocket connection (green status indicator)

View File

@@ -2,11 +2,6 @@
"name": "@getpaseo/server",
"version": "0.1.62",
"description": "Paseo backend server",
"type": "module",
"publishConfig": {
"access": "public"
},
"types": "./dist/server/server/exports.d.ts",
"files": [
"dist/server",
"dist/src",
@@ -15,6 +10,8 @@
"README.md",
".env.example"
],
"type": "module",
"types": "./dist/server/server/exports.d.ts",
"exports": {
".": {
"types": "./dist/server/server/exports.d.ts",
@@ -27,6 +24,9 @@
"default": "./dist/server/utils/tool-call-parsers.js"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "cross-env NODE_ENV=development node --import tsx scripts/dev-runner.ts",
"dev:tsx": "cross-env NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' src/server/index.ts",

View File

@@ -232,11 +232,10 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
labels?: Record<string, string>;
}
export interface CreatePaseoWorktreeInput
extends Pick<
CreatePaseoWorktreeRequest,
"cwd" | "worktreeSlug" | "attachments" | "refName" | "action" | "githubPrNumber"
> {}
export interface CreatePaseoWorktreeInput extends Pick<
CreatePaseoWorktreeRequest,
"cwd" | "worktreeSlug" | "attachments" | "refName" | "action" | "githubPrNumber"
> {}
type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
type SubscribeCheckoutDiffPayload = Extract<

View File

@@ -130,9 +130,7 @@ function buildZodValidator<T>(schema: z.ZodTypeAny, schemaName: string): SchemaV
function buildJsonSchemaValidator<T>(schema: JsonSchema): SchemaValidator<T> {
const AjvConstructor = Ajv as unknown as {
new (
options?: AjvOptions,
): {
new (options?: AjvOptions): {
compile: (input: JsonSchema) => ((value: unknown) => boolean) & {
errors?: ErrorObject[] | null;
};

View File

@@ -1203,9 +1203,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
? (
await Promise.all(
terminalManager.listDirectories().map(async (directory) =>
(
await terminalManager.getTerminals(directory)
).map((terminal) => ({
(await terminalManager.getTerminals(directory)).map((terminal) => ({
id: terminal.id,
name: terminal.name,
cwd: terminal.cwd,

View File

@@ -8,4 +8,3 @@
* This file is kept as a stub to document the migration.
* It can be deleted once the refactoring is confirmed working.
*/

View File

@@ -49,9 +49,7 @@ export class ClaudeSidechainTracker {
private readonly activeSidechains = new Map<string, SubAgentActivityState>();
private readonly getToolInput: (toolUseId: string) => AgentMetadata | null | undefined;
constructor(input: {
getToolInput: (toolUseId: string) => AgentMetadata | null | undefined;
}) {
constructor(input: { getToolInput: (toolUseId: string) => AgentMetadata | null | undefined }) {
this.getToolInput = input.getToolInput;
}

View File

@@ -291,11 +291,7 @@ export class MockLoadTestAgentSession implements AgentSession {
private modeId: string | null;
private modelId: string | null;
constructor(options: {
config: AgentSessionConfig;
sessionId: string;
logger?: Logger;
}) {
constructor(options: { config: AgentSessionConfig; sessionId: string; logger?: Logger }) {
this.id = options.sessionId;
this.logger = options.logger;
this.modeId = options.config.modeId ?? MOCK_LOAD_TEST_MODE_ID;

View File

@@ -192,26 +192,23 @@ describe.runIf(process.platform === "win32")("Windows provider launch parity", (
});
});
test.each(
providerLaunchCases,
)("$provider launches a cmd shim from a path with spaces through spawnProcess", async ({
binaryName,
args,
shell,
}) => {
const fixture = makeFixture(binaryName, args);
test.each(providerLaunchCases)(
"$provider launches a cmd shim from a path with spaces through spawnProcess",
async ({ binaryName, args, shell }) => {
const fixture = makeFixture(binaryName, args);
const result = await runProviderFixture({
command: fixture.shim,
args: fixture.expectedArgs,
expectedArgs: fixture.expectedArgs,
shell,
});
const result = await runProviderFixture({
command: fixture.shim,
args: fixture.expectedArgs,
expectedArgs: fixture.expectedArgs,
shell,
});
expect(result.error).toBeNull();
expect(result.code).toBe(0);
expect(result.signal).toBeNull();
expect(result.stderr).toBe("");
expect(result.stdout.trim()).toBe("ARGV_OK");
});
expect(result.error).toBeNull();
expect(result.code).toBe(0);
expect(result.signal).toBeNull();
expect(result.stderr).toBe("");
expect(result.stdout.trim()).toBe("ARGV_OK");
},
);
});

View File

@@ -78,52 +78,51 @@ describe("daemon E2E", () => {
await ctx.cleanup();
}, 60000);
describe.each([
"claude",
"codex",
"opencode",
] as const)("live model switching (%s)", (provider) => {
const shouldRun =
provider === "claude" ||
(provider === "codex" && hasCodex) ||
(provider === "opencode" && hasOpenCode);
describe.each(["claude", "codex", "opencode"] as const)(
"live model switching (%s)",
(provider) => {
const shouldRun =
provider === "claude" ||
(provider === "codex" && hasCodex) ||
(provider === "opencode" && hasOpenCode);
test.runIf(shouldRun)(
"updates agent model without restarting",
async () => {
const cwd = tmpCwd();
try {
const modelList = await ctx.client.listProviderModels(provider);
if (!modelList.models || modelList.models.length === 0) {
throw new Error(`No models returned for provider ${provider}`);
test.runIf(shouldRun)(
"updates agent model without restarting",
async () => {
const cwd = tmpCwd();
try {
const modelList = await ctx.client.listProviderModels(provider);
if (!modelList.models || modelList.models.length === 0) {
throw new Error(`No models returned for provider ${provider}`);
}
const [modelA, modelB] = pickTwoDistinctModels(modelList.models);
const agent = await ctx.client.createAgent({
provider,
cwd,
title: `Model Switch (${provider})`,
model: modelA,
});
const startIndex = messages.length;
await ctx.client.setAgentModel(agent.id, modelB);
const updated = await waitForAgentUpdate(
messages,
startIndex,
(a) => a.id === agent.id && a.model === modelB,
{ timeoutMs: 20000 },
);
expect(updated.model).toBe(modelB);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
const [modelA, modelB] = pickTwoDistinctModels(modelList.models);
const agent = await ctx.client.createAgent({
provider,
cwd,
title: `Model Switch (${provider})`,
model: modelA,
});
const startIndex = messages.length;
await ctx.client.setAgentModel(agent.id, modelB);
const updated = await waitForAgentUpdate(
messages,
startIndex,
(a) => a.id === agent.id && a.model === modelB,
{ timeoutMs: 20000 },
);
expect(updated.model).toBe(modelB);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
},
180000,
);
});
},
180000,
);
},
);
test("live thinking switching works for Claude (off -> on)", async () => {
const cwd = tmpCwd();

View File

@@ -35,11 +35,7 @@ export {
type ProviderOverride,
type ProviderProfileModel,
} from "./agent/provider-launch-config.js";
export {
findExecutable,
quoteWindowsArgument,
quoteWindowsCommand,
} from "../utils/executable.js";
export { findExecutable, quoteWindowsArgument, quoteWindowsCommand } from "../utils/executable.js";
export { execCommand, spawnProcess } from "../utils/spawn.js";
// Provider manifest (source of truth for provider definitions)

View File

@@ -1776,9 +1776,9 @@ describe("workspace aggregation", () => {
rmSync(tempDir, { recursive: true, force: true });
}
const response = emitted.find((message) => message.type === "create_paseo_worktree_response") as
| { type: "create_paseo_worktree_response"; payload: any }
| undefined;
const response = emitted.find(
(message) => message.type === "create_paseo_worktree_response",
) as { type: "create_paseo_worktree_response"; payload: any } | undefined;
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace).toMatchObject({

View File

@@ -7,8 +7,5 @@ export {
type DaemonEvent,
type DaemonEventHandler,
} from "./daemon-client.js";
export {
createDaemonTestContext,
type DaemonTestContext,
} from "./daemon-test-context.js";
export { createDaemonTestContext, type DaemonTestContext } from "./daemon-test-context.js";
export { useTempClaudeConfigDir } from "./claude-config.js";

View File

@@ -172,20 +172,19 @@ describe("pull request timeline message schemas", () => {
expect(parsed.payload.error).toEqual({ kind: "unknown", message: "x" });
});
test.each([
"rate_limited",
"",
undefined,
])("normalizes malformed error kind %j to unknown", (kind) => {
const parsed = PullRequestTimelineResponseSchema.parse({
type: "pull_request_timeline_response",
payload: {
error: { kind },
},
});
test.each(["rate_limited", "", undefined])(
"normalizes malformed error kind %j to unknown",
(kind) => {
const parsed = PullRequestTimelineResponseSchema.parse({
type: "pull_request_timeline_response",
payload: {
error: { kind },
},
});
expect(parsed.payload.error).toEqual({ kind: "unknown", message: "" });
});
expect(parsed.payload.error).toEqual({ kind: "unknown", message: "" });
},
);
});
describe("checkout PR status compatibility", () => {

View File

@@ -6,7 +6,7 @@ main = "@tanstack/react-start/server-entry"
routes = [
{ pattern = "paseo.sh", custom_domain = true },
{ pattern = "www.paseo.sh", custom_domain = true }
{ pattern = "www.paseo.sh", custom_domain = true },
]
[observability]

View File

@@ -19,6 +19,7 @@ Load the **Paseo skill** first if you need CLI guidance for launching or messagi
## Rules
When using chat:
- create a room with `paseo chat create` if you need a new room
- inspect available rooms with `paseo chat ls` and `paseo chat inspect`
- post with `paseo chat post`
@@ -27,6 +28,7 @@ When using chat:
- check chat often while working
Mentions are active:
- write mentions inline in the message body as `@<agent-id>` to notify a specific agent immediately
- use `@everyone` to notify all non-archived, non-internal agents
- notifications are sent to the target agent without blocking the chat post
@@ -99,10 +101,12 @@ paseo chat wait issue-456 --timeout 60s
## Defaults
When creating a room:
- choose a short slug: `issue-456`, `pr-143-review`, `relay-cleanup`
- give it a clear purpose
When using a room:
- read only a bounded window before acting
- post updates when they would help another agent or your future self
- use `--reply-to` when responding to a specific message
@@ -112,6 +116,7 @@ When using a room:
- your own agent ID is available via `$PASEO_AGENT_ID`
Typical things to post:
- status updates
- blockers
- handoffs

View File

@@ -30,13 +30,13 @@ Parse `$ARGUMENTS` to determine:
### Provider Resolution
| User says | --provider | Mode |
|---|---|---|
| *(nothing)* | `codex/gpt-5.4` | `full-access` |
| `codex` | `codex/gpt-5.4` | `full-access` |
| `claude` | `claude/opus` | `bypass` |
| `opus` | `claude/opus` | `bypass` |
| `sonnet` | `claude/sonnet` | `bypass` |
| User says | --provider | Mode |
| ----------- | --------------- | ------------- |
| _(nothing)_ | `codex/gpt-5.4` | `full-access` |
| `codex` | `codex/gpt-5.4` | `full-access` |
| `claude` | `claude/opus` | `bypass` |
| `opus` | `claude/opus` | `bypass` |
| `sonnet` | `claude/sonnet` | `bypass` |
Default is **Codex** with `gpt-5.4`.

View File

@@ -108,6 +108,7 @@ paseo loop stop <id> # Stop a running loop
### Worker prompt
The worker prompt must be:
- self-contained
- concrete about commands, files, branches, tests, PRs, or systems to inspect
- explicit about what counts as progress this iteration
@@ -115,6 +116,7 @@ The worker prompt must be:
### Verifier prompt
The verifier prompt should:
- check facts, not offer fixes
- cite commands, outputs, or file evidence
- be specific about what "done" means

View File

@@ -17,6 +17,7 @@ You are an end-to-end implementation orchestrator. You take a task from understa
## Prerequisites
Load these skills before proceeding:
1. **e2e-playwright** — if the task involves frontend/UI work
## Guard
@@ -46,13 +47,13 @@ Merge with defaults for any missing fields. The file maps role categories to `<a
- The part before `/` is the `agentType` (e.g., `codex`, `claude`, `opencode`)
- The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`)
| Category | Roles covered | Default |
|----------|--------------|---------|
| `impl` | impl, tester, refactorer | `codex/gpt-5.4` |
| `ui` | impl agents doing UI/styling work | `claude/opus` |
| `research` | researcher | `codex/gpt-5.4` |
| `planning` | planner, plan-reviewer | `codex/gpt-5.4` |
| `audit` | auditor, qa | `codex/gpt-5.4` |
| Category | Roles covered | Default |
| ---------- | --------------------------------- | --------------- |
| `impl` | impl, tester, refactorer | `codex/gpt-5.4` |
| `ui` | impl agents doing UI/styling work | `claude/opus` |
| `research` | researcher | `codex/gpt-5.4` |
| `planning` | planner, plan-reviewer | `codex/gpt-5.4` |
| `audit` | auditor, qa | `codex/gpt-5.4` |
The file also has a `preferences` array of freeform natural language strings. Read these at startup and weave them into your behavior contextually. When the user says "store my preference: X", update the file.
@@ -90,11 +91,13 @@ To archive: Paseo **archive agent**.
### How to Write Agent Prompts
**Describe the problem, not the solution.** Your prompt should tell the agent:
- What's wrong or what needs to be built (the goal)
- How it currently fails (error output, test output, user-visible behavior)
- The acceptance criteria (what "done" looks like)
**Do NOT tell the agent:**
- Which specific lines to change
- What code to write
- Which functions to call or which patterns to use
@@ -141,43 +144,43 @@ State the order and briefly why: "Order 3 — touches server session management
**Order 1 — Single file, single concern.** A contained change: fix a bug in one function, add a field to one type, update one component.
| Phase | Agents |
|-------|--------|
| Research | 1 researcher |
| Planning | 0 — orchestrator plans inline |
| Implement | 1 impl |
| Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer |
| Phase | Agents |
| --------- | ----------------------------- |
| Research | 1 researcher |
| Planning | 0 — orchestrator plans inline |
| Implement | 1 impl |
| Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer |
**Order 2 — Single module, few files.** A feature or fix within one package that touches 3-8 files.
| Phase | Agents |
|-------|--------|
| Research | 2 researchers |
| Planning | 1 planner |
| Phase | Agents |
| --------- | ---------------- |
| Research | 2 researchers |
| Planning | 1 planner |
| Implement | 1 impl per phase |
| Verify | 2-3 auditors |
| Cleanup | 1 refactorer |
| Verify | 2-3 auditors |
| Cleanup | 1 refactorer |
**Order 3 — Cross-module, multiple packages.** A feature that spans packages.
| Phase | Agents |
|-------|--------|
| Research | 3-4 researchers |
| Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors |
| Cleanup | 1-2 refactorers |
| Phase | Agents |
| --------- | ---------------------------- |
| Research | 3-4 researchers |
| Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors |
| Cleanup | 1-2 refactorers |
**Order 4 — Architectural, system-wide.** A new subsystem, major refactor, or system-wide change.
| Phase | Agents |
|-------|--------|
| Research | 5+ researchers |
| Planning | 2+ planners + 2 plan-reviewers |
| Implement | 2+ impl agents per phase |
| Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers |
| Phase | Agents |
| --------- | ------------------------------ |
| Research | 5+ researchers |
| Planning | 2+ planners + 2 plan-reviewers |
| Implement | 2+ impl agents per phase |
| Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers |
---
@@ -250,6 +253,7 @@ Deploy planners to create an implementation plan informed by research findings.
Every planner prompt must emphasize this: the default agent instinct is to bolt new code on top of existing code. Resist this.
The right approach:
1. Study the existing code — understand why it's shaped the way it is
2. Design the target shape — what would the code look like if this feature had always existed?
3. Identify the refactoring gap — what needs to change so the new feature slots in cleanly?
@@ -344,6 +348,7 @@ Present the plan to the user. Wait for explicit confirmation before proceeding.
Persist the plan to disk and set up the heartbeat:
Use the Paseo **create schedule** tool with:
- `name`: `"heartbeat-<task-slug>"`
- `target`: `"self"`
- `every`: `"5m"`
@@ -394,6 +399,7 @@ Do the following steps in order:
## Phase 7: Implement
Execute phases from the plan sequentially. For each phase:
1. Launch impl agent(s) with `background: true, notifyOnFinish: true`
2. Wait for notification
3. Verify (Phase 8)
@@ -406,6 +412,7 @@ UI passes use `providers.ui` from preferences. All other impl work uses `provide
### TDD — Not Optional
Every impl agent works TDD:
1. Write a failing test that defines the expected behavior
2. Make it pass
3. Refactor if needed
@@ -462,6 +469,7 @@ Run typecheck when done. Do NOT commit."
### Handling Blockers
If an impl agent reports a blocker:
- Do NOT ask the user (in either mode)
- Spin up a researcher to investigate
- Spin up an impl agent to fix it
@@ -477,13 +485,13 @@ After every implementation phase, deploy auditors to verify the work. Auditors a
### Which Auditors to Deploy
| Phase type | Auditors |
|-----------|----------|
| Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Phase type | Auditors |
| ------------------ | ------------------------------------------------------ |
| Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) |
| UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` |
| UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` |
Deploy all relevant auditors in parallel — they're read-only so they don't conflict.
@@ -622,6 +630,7 @@ Do NOT edit files."
### Interpreting Findings
If any auditor reports issues:
1. Check the auditor's activity with Paseo **get agent activity** for details
2. Direct the impl agent to fix them via Paseo **send agent prompt**, or launch a new impl agent if the old one is stale
3. Re-deploy the same auditor after fixes
@@ -841,16 +850,16 @@ Every agent has exactly one role. The role determines what the agent does, wheth
**Naming:** `<role>-<scope>[-<specialization>]` in kebab-case.
| Role | Job | Edits? | Prompt emphasis |
|------|-----|--------|----------------|
| `researcher` | Gathers info: codebase, docs, web, scripts | No | "Report what you find. Do not suggest solutions. Do not edit files." |
| `planner` | Creates implementation plan from research | No | "Think refactor-first. Design the target shape, not the steps." |
| `plan-reviewer` | Adversarially challenges a plan | No | "Challenge the plan. Find what's wrong, missing, or over-engineered." |
| `impl` | Writes code, works TDD | Yes | "Work TDD. Reshape existing code. Run typecheck AND run any tests you modified. Both must pass. Do NOT commit." |
| `tester` | Writes/fixes tests | Yes | "Verify behavior, not implementation. Run every test you modified and confirm it passes. A test change without running the test is not done." |
| `auditor` | Read-only verification | No | "Check [specialization]. Report YES/NO with evidence. Do NOT edit files." |
| `refactorer` | Targeted cleanup | Yes | "Fix [specialization] only. Run typecheck and any tests you touch. Do NOT commit." |
| `qa` | End-to-end QA, browser testing | No | "Test the actual user experience. Report with evidence." |
| Role | Job | Edits? | Prompt emphasis |
| --------------- | ------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `researcher` | Gathers info: codebase, docs, web, scripts | No | "Report what you find. Do not suggest solutions. Do not edit files." |
| `planner` | Creates implementation plan from research | No | "Think refactor-first. Design the target shape, not the steps." |
| `plan-reviewer` | Adversarially challenges a plan | No | "Challenge the plan. Find what's wrong, missing, or over-engineered." |
| `impl` | Writes code, works TDD | Yes | "Work TDD. Reshape existing code. Run typecheck AND run any tests you modified. Both must pass. Do NOT commit." |
| `tester` | Writes/fixes tests | Yes | "Verify behavior, not implementation. Run every test you modified and confirm it passes. A test change without running the test is not done." |
| `auditor` | Read-only verification | No | "Check [specialization]. Report YES/NO with evidence. Do NOT edit files." |
| `refactorer` | Targeted cleanup | Yes | "Fix [specialization] only. Run typecheck and any tests you touch. Do NOT commit." |
| `qa` | End-to-end QA, browser testing | No | "Test the actual user experience. Report with evidence." |
Auditor specializations: `overeng`, `dry`, `tests`, `regression`, `types`, `browser`, `parity`
Refactorer specializations: `dry`, `dead-code`, `naming`

View File

@@ -81,6 +81,7 @@ Do NOT edit files."
## If Issues Are Found
If any final QA agent reports issues:
1. Launch an impl or refactorer agent to fix them
2. Re-run the specific QA check that failed
3. Repeat until all checks pass

View File

@@ -5,6 +5,7 @@ Deploy impl agents to execute the plan phase by phase. Each phase is independent
## TDD — Not Optional
Every impl agent works TDD:
1. Write a failing test that defines the expected behavior
2. Make it pass
3. Refactor if needed
@@ -17,6 +18,7 @@ If an impl agent finds a broken test, it fixes it. No "pre-existing failures." N
Execute phases sequentially from the plan. Refactoring phases first, then feature phases, then UI passes.
After each phase:
1. Verify (see verification.md)
2. Fix any issues found
3. Re-verify
@@ -29,11 +31,13 @@ Use the Paseo **create agent** tool with `background: true` and `notifyOnFinish:
### How to describe the work
**Describe the problem, not the solution.** Your prompt should tell the agent:
- What's wrong or what needs to be built (the goal)
- How it currently fails (error output, test output, user-visible behavior)
- The acceptance criteria (what "done" looks like)
**Do NOT tell the agent:**
- Which specific lines to change
- What code to write
- Which functions to call or which patterns to use
@@ -95,6 +99,7 @@ Run typecheck when done. Do NOT commit."
## Handling Blockers
If an impl agent reports a blocker:
- Do NOT ask the user (in either mode)
- Spin up a researcher to investigate
- Spin up an impl agent to fix it

View File

@@ -7,6 +7,7 @@ Deploy planners to create an implementation plan informed by research findings.
Every planner prompt must emphasize this: the default agent instinct is to bolt new code on top of existing code. Resist this.
The right approach:
1. Study the existing code — understand why it's shaped the way it is
2. Design the target shape — what would the code look like if this feature had always existed?
3. Identify the refactoring gap — what needs to change so the new feature slots in cleanly?
@@ -65,6 +66,7 @@ Challenge the plan:
## Multiple Planners (Order 3+)
For cross-module tasks, deploy planners focusing on different slices:
- One for backend phases
- One for frontend phases
- One for test strategy
@@ -74,6 +76,7 @@ Then deploy a plan-reviewer to challenge the combined plan.
## Iteration
If the plan-reviewer finds significant issues, either:
1. Send follow-up instructions via the Paseo **send agent prompt** tool to the planner
2. Launch a new planner if the original is stale

View File

@@ -24,15 +24,16 @@ Maps role categories to `<agent-type>/<model>` strings. These map directly to th
- The part before `/` is the `agentType` (e.g., `codex`, `claude`, `opencode`)
- The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`)
| Category | Roles covered |
|----------|--------------|
| `impl` | impl, tester, refactorer |
| `ui` | impl agents doing UI/styling work |
| `research` | researcher |
| `planning` | planner, plan-reviewer |
| `audit` | auditor, qa |
| Category | Roles covered |
| ---------- | --------------------------------- |
| `impl` | impl, tester, refactorer |
| `ui` | impl agents doing UI/styling work |
| `research` | researcher |
| `planning` | planner, plan-reviewer |
| `audit` | auditor, qa |
If a category is missing, use these defaults:
- `impl` -> `codex/gpt-5.4`
- `ui` -> `claude/opus`
- `research` -> `codex/gpt-5.4`
@@ -44,6 +45,7 @@ If a category is missing, use these defaults:
Freeform array of natural language strings. The user states preferences and the orchestrator appends them here. Read these at startup and weave them into your behavior contextually.
Examples:
- "Prefer small, focused PRs over large bundled ones"
- "Run E2E tests with Maestro, not Playwright"
- "Always check mobile responsiveness"

View File

@@ -18,46 +18,46 @@ State the order and briefly why: "Order 3 — touches server session management
A contained change: fix a bug in one function, add a field to one type, update one component.
| Phase | Agents |
|-------|--------|
| Research | 1 researcher |
| Planning | 0 — orchestrator plans inline |
| Implement | 1 impl |
| Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer |
| Phase | Agents |
| --------- | ----------------------------- |
| Research | 1 researcher |
| Planning | 0 — orchestrator plans inline |
| Implement | 1 impl |
| Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer |
### Order 2 — Single module, few files
A feature or fix within one package that touches 3-8 files. Might involve new types, new tests, a few component changes.
| Phase | Agents |
|-------|--------|
| Research | 2 researchers |
| Planning | 1 planner |
| Phase | Agents |
| --------- | ---------------- |
| Research | 2 researchers |
| Planning | 1 planner |
| Implement | 1 impl per phase |
| Verify | 2-3 auditors |
| Cleanup | 1 refactorer |
| Verify | 2-3 auditors |
| Cleanup | 1 refactorer |
### Order 3 — Cross-module, multiple packages
A feature that spans packages (e.g., server + app, or CLI + server). Multiple concerns, multiple file groups, likely needs interface changes between layers.
| Phase | Agents |
|-------|--------|
| Research | 3-4 researchers (one per area: backend, frontend, tests, external docs) |
| Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors (overeng, tests, regression, types) |
| Cleanup | 1-2 refactorers |
| Phase | Agents |
| --------- | ----------------------------------------------------------------------- |
| Research | 3-4 researchers (one per area: backend, frontend, tests, external docs) |
| Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors (overeng, tests, regression, types) |
| Cleanup | 1-2 refactorers |
### Order 4 — Architectural, system-wide
A new subsystem, major refactor, or change that touches most of the codebase. New abstractions, new patterns, potentially breaking changes that need migration.
| Phase | Agents |
|-------|--------|
| Research | 5+ researchers across all relevant areas |
| Planning | 2+ planners (one per major area) + 2 plan-reviewers |
| Implement | 2+ impl agents per phase, sequenced carefully |
| Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers with different specializations |
| Phase | Agents |
| --------- | --------------------------------------------------- |
| Research | 5+ researchers across all relevant areas |
| Planning | 2+ planners (one per major area) + 2 plan-reviewers |
| Implement | 2+ impl agents per phase, sequenced carefully |
| Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers with different specializations |

View File

@@ -6,13 +6,13 @@ After every implementation phase, deploy auditors to verify the work. Auditors a
Not every phase needs every auditor. Match auditors to the work:
| Phase type | Auditors |
|-----------|----------|
| Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Phase type | Auditors |
| ------------------ | ------------------------------------------------------ |
| Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) |
| UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` |
| UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` |
Deploy all relevant auditors in parallel — they're read-only so they don't conflict.
@@ -151,6 +151,7 @@ Do NOT edit files."
## Interpreting Findings
If any auditor reports issues:
1. Check the auditor's activity with the Paseo **get agent activity** tool for details
2. Direct the impl agent to fix them via the Paseo **send agent prompt** tool, or launch a new impl agent if the old one is stale
3. Re-deploy the same auditor after fixes

View File

@@ -193,6 +193,7 @@ paseo terminal send-keys <terminal-id> --literal "raw text" # No special token
`Enter`, `Tab`, `Escape`, `Space`, `BSpace`, `C-c`, `C-d`, `C-z`, `C-l`, `C-a`, `C-e`
**Common pattern — launch a process and interact with it:**
```bash
id=$(paseo terminal create --name "my-shell" -q)
paseo terminal send-keys "$id" "claude" Enter
@@ -208,11 +209,13 @@ paseo terminal kill "$id"
## Available Models
**Claude (default provider):**
- `--provider claude/haiku` — Fast/cheap, ONLY for tests (not for real work)
- `--provider claude/sonnet` — Good for most tasks
- `--provider claude/opus` — For harder reasoning, complex debugging
**Codex:**
- `--provider codex/gpt-5.4` — Latest frontier agentic coding model (preferred for all engineering tasks)
- `--provider codex/gpt-5.4-mini` — Cheaper, faster, but less capable
@@ -236,6 +239,7 @@ Both `paseo run` and `paseo wait` block until the agent completes. Trust them.
`paseo run` blocks by default and `--output-schema` returns structured JSON, making it easy to compose agents in bash loops and pipelines.
**Detach + wait pattern for parallel work:**
```bash
api_id=$(paseo run -d --name "impl-api" "implement the API" -q)
ui_id=$(paseo run -d --name "impl-ui" "implement the UI" -q)