mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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:
16
.github/workflows/deploy-app.yml
vendored
16
.github/workflows/deploy-app.yml
vendored
@@ -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
|
||||
|
||||
9
.github/workflows/deploy-relay.yml
vendored
9
.github/workflows/deploy-relay.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
16
.github/workflows/deploy-website.yml
vendored
16
.github/workflows/deploy-website.yml
vendored
@@ -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
|
||||
|
||||
44
.github/workflows/desktop-release.yml
vendored
44
.github/workflows/desktop-release.yml
vendored
@@ -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
|
||||
|
||||
12
.github/workflows/fix-nix-hash.yml
vendored
12
.github/workflows/fix-nix-hash.yml
vendored
@@ -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:
|
||||
|
||||
36
.github/workflows/nix-build.yml
vendored
36
.github/workflows/nix-build.yml
vendored
@@ -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
|
||||
|
||||
20
.github/workflows/server-ci.yml
vendored
20
.github/workflows/server-ci.yml
vendored
@@ -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
|
||||
|
||||
135
CHANGELOG.md
135
CHANGELOG.md
@@ -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.
|
||||
|
||||
49
CLAUDE.md
49
CLAUDE.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -24,4 +24,3 @@ appId: sh.paseo
|
||||
commands:
|
||||
- tapOn:
|
||||
point: "50%,20%"
|
||||
|
||||
|
||||
@@ -3,4 +3,3 @@ appId: sh.paseo
|
||||
- launchApp:
|
||||
clearState: true
|
||||
- runFlow: flows/dev-client.yaml
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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']"),
|
||||
),
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -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[] = [
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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": "../.."
|
||||
|
||||
@@ -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": "../.."
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user