chore: reformat codebase with oxfmt

Apply prettier-compatible formatting across the repo to match the
incoming oxfmt configuration. Mechanical reformat only — no logic
changes. Covers YAML quote normalization, package.json key sorting,
Markdown/TOML formatting, and minor TS whitespace tweaks.
This commit is contained in:
Mohamed Boudra
2026-04-23 21:44:48 +07:00
parent 9b25f8fc36
commit ce4b0d54c7
81 changed files with 1171 additions and 1009 deletions
+8 -8
View File
@@ -3,10 +3,10 @@ name: Deploy App
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
- '!v*-beta.*' - "!v*-beta.*"
- 'app-v*' - "app-v*"
- '!app-v*-beta.*' - "!app-v*-beta.*"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -18,10 +18,10 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
registry-url: 'https://npm.pkg.github.com' registry-url: "https://npm.pkg.github.com"
scope: '@boudra' scope: "@boudra"
- name: Install dependencies - name: Install dependencies
run: npm install --workspace=@getpaseo/app --include-workspace-root run: npm install --workspace=@getpaseo/app --include-workspace-root
+4 -5
View File
@@ -4,8 +4,8 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'packages/relay/**' - "packages/relay/**"
- '.github/workflows/deploy-relay.yml' - ".github/workflows/deploy-relay.yml"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -17,8 +17,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
- name: Install dependencies - name: Install dependencies
run: npm install --workspace=@getpaseo/relay --include-workspace-root run: npm install --workspace=@getpaseo/relay --include-workspace-root
@@ -30,4 +30,3 @@ jobs:
run: cd packages/relay && npx wrangler deploy run: cd packages/relay && npx wrangler deploy
env: env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+8 -8
View File
@@ -4,12 +4,12 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'CHANGELOG.md' - "CHANGELOG.md"
- 'packages/website/**' - "packages/website/**"
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
- 'patches/**' - "patches/**"
- '.github/workflows/deploy-website.yml' - ".github/workflows/deploy-website.yml"
release: release:
types: [published] types: [published]
workflow_dispatch: workflow_dispatch:
@@ -24,8 +24,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
- name: Install dependencies - name: Install dependencies
run: npm install --workspace=@getpaseo/website --include-workspace-root run: npm install --workspace=@getpaseo/website --include-workspace-root
+22 -22
View File
@@ -3,21 +3,21 @@ name: Desktop Release
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
- 'desktop-v*' - "desktop-v*"
- 'desktop-macos-v*' - "desktop-macos-v*"
- 'desktop-linux-v*' - "desktop-linux-v*"
- 'desktop-windows-v*' - "desktop-windows-v*"
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
description: 'Existing tag to build (e.g. v0.1.0)' description: "Existing tag to build (e.g. v0.1.0)"
required: true required: true
type: string type: string
platform: platform:
description: 'Optional desktop platform to build.' description: "Optional desktop platform to build."
required: false required: false
default: 'all' default: "all"
type: choice type: choice
options: options:
- all - all
@@ -31,8 +31,8 @@ concurrency:
env: env:
SOURCE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }} SOURCE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
DESKTOP_WORKSPACE: '@getpaseo/desktop' DESKTOP_WORKSPACE: "@getpaseo/desktop"
DESKTOP_PACKAGE_PATH: 'packages/desktop' DESKTOP_PACKAGE_PATH: "packages/desktop"
jobs: jobs:
create-release: create-release:
@@ -100,11 +100,11 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com' registry-url: "https://npm.pkg.github.com"
scope: '@boudra' scope: "@boudra"
- name: Install JS dependencies - name: Install JS dependencies
run: npm ci run: npm ci
@@ -278,11 +278,11 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com' registry-url: "https://npm.pkg.github.com"
scope: '@boudra' scope: "@boudra"
- name: Install JS dependencies - name: Install JS dependencies
run: npm ci run: npm ci
@@ -346,11 +346,11 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
registry-url: 'https://npm.pkg.github.com' registry-url: "https://npm.pkg.github.com"
scope: '@boudra' scope: "@boudra"
- name: Install JS dependencies - name: Install JS dependencies
run: npm ci run: npm ci
+6 -6
View File
@@ -4,12 +4,12 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
pull_request: pull_request:
paths: paths:
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
permissions: permissions:
contents: write contents: write
@@ -25,8 +25,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: "22"
cache: 'npm' cache: "npm"
- uses: cachix/install-nix-action@v31 - uses: cachix/install-nix-action@v31
with: with:
+18 -18
View File
@@ -4,27 +4,27 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'nix/**' - "nix/**"
- 'flake.nix' - "flake.nix"
- 'flake.lock' - "flake.lock"
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
- 'packages/highlight/**' - "packages/highlight/**"
- 'packages/server/**' - "packages/server/**"
- 'packages/relay/**' - "packages/relay/**"
- 'packages/cli/**' - "packages/cli/**"
pull_request: pull_request:
branches: [main] branches: [main]
paths: paths:
- 'nix/**' - "nix/**"
- 'flake.nix' - "flake.nix"
- 'flake.lock' - "flake.lock"
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
- 'packages/highlight/**' - "packages/highlight/**"
- 'packages/server/**' - "packages/server/**"
- 'packages/relay/**' - "packages/relay/**"
- 'packages/cli/**' - "packages/cli/**"
permissions: permissions:
contents: read contents: read
+10 -10
View File
@@ -4,17 +4,17 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'packages/server/**' - "packages/server/**"
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
- '.github/workflows/server-ci.yml' - ".github/workflows/server-ci.yml"
pull_request: pull_request:
branches: [main] branches: [main]
paths: paths:
- 'packages/server/**' - "packages/server/**"
- 'package.json' - "package.json"
- 'package-lock.json' - "package-lock.json"
- '.github/workflows/server-ci.yml' - ".github/workflows/server-ci.yml"
jobs: jobs:
test: test:
@@ -27,8 +27,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: "20"
cache: 'npm' cache: "npm"
- name: Fetch origin/main (worktree tests) - name: Fetch origin/main (worktree tests)
run: git fetch --no-tags origin main:refs/remotes/origin/main run: git fetch --no-tags origin main:refs/remotes/origin/main
+135
View File
@@ -3,12 +3,15 @@
## 0.1.62 - 2026-04-23 ## 0.1.62 - 2026-04-23
### Added ### Added
- Sidebar warning when your app and daemon versions drift apart, with a shortcut to settings. - Sidebar warning when your app and daemon versions drift apart, with a shortcut to settings.
### Improved ### Improved
- Workspaces appear in the sidebar immediately on startup instead of waiting for git registration. - Workspaces appear in the sidebar immediately on startup instead of waiting for git registration.
### Fixed ### Fixed
- Pull request status resolves correctly for PRs opened from forks. - Pull request status resolves correctly for PRs opened from forks.
- Installing the paseo CLI from the macOS desktop app now works in packaged builds. - 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. - Agents launched from the desktop app no longer inherit Electron-only environment variables.
@@ -16,10 +19,12 @@
## 0.1.61 - 2026-04-23 ## 0.1.61 - 2026-04-23
### Added ### 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). - `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. - New [Providers docs page](https://paseo.sh/docs/providers) covering first-class providers and every custom provider config pattern in one place.
### Improved ### Improved
- Pi loads your installed extensions on startup so their models show up in the model picker. - 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. - 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. - 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 ## 0.1.60 - 2026-04-22
### Added ### 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). - 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. - 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. - 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. - New-workspace picker ranks branches by recency with fast search.
### Improved ### Improved
- Workspace and tab switching are dramatically faster on desktop and mobile — you can keep many workspaces open in parallel without lag. - 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. - Agent streams render more smoothly during heavy tool output.
- App startup routes through a stable connection and lands on the right screen without flicker. - 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. - GitHub branch and PR picker loads faster — queries are deferred until the picker opens.
### Fixed ### Fixed
- Composer textarea shrinks back down after sending on web. - Composer textarea shrinks back down after sending on web.
- New workspace drafts clear after submit instead of sticking around. - New workspace drafts clear after submit instead of sticking around.
- Replacing a running agent cleans up the previous one without leaving it behind. - Replacing a running agent cleans up the previous one without leaving it behind.
@@ -76,18 +84,21 @@
## 0.1.59 - 2026-04-16 ## 0.1.59 - 2026-04-16
### Added ### Added
- Opus 4.7 in the Claude model picker, with a 1M-context variant. - 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. - Extra High reasoning effort for Opus 4.7, between High and Max.
## 0.1.58 - 2026-04-16 ## 0.1.58 - 2026-04-16
### Added ### 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)) - 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. - 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)) - 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. - `disallowedTools` option in provider config to block specific tools from an agent.
### Improved ### 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)) - 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 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)) - 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)) - 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 ### 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)) - 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 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)) - 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 ## 0.1.56 - 2026-04-14
### Fixed ### Fixed
- Projects with empty git repositories (no commits yet) no longer crash the app on startup. - 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. - A single problematic project can no longer prevent the rest of your workspaces from loading.
## 0.1.55 - 2026-04-14 ## 0.1.55 - 2026-04-14
### Added ### 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). - 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. - 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. - Choose provider and model when creating scheduled agents.
@@ -123,12 +137,14 @@
- Cmd+, (Ctrl+, on Windows/Linux) opens settings. - Cmd+, (Ctrl+, on Windows/Linux) opens settings.
### Improved ### 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. - 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. - 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. - iPad and tablet layouts work correctly across all screen sizes.
- IME composition (Chinese, Japanese, Korean input) no longer submits prematurely when pressing Enter. - IME composition (Chinese, Japanese, Korean input) no longer submits prematurely when pressing Enter.
### Fixed ### Fixed
- Creating a worktree no longer briefly flashes it as a standalone project before placing it under the correct repository. - 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. - 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. - Workspace navigation updates correctly when switching between workspaces in the same project.
@@ -138,13 +154,16 @@
## 0.1.54 - 2026-04-12 ## 0.1.54 - 2026-04-12
### Added ### 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. - 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 ### Improved
- Paseo tools are no longer injected into agents by default — opt in from Settings when you need agent-to-agent orchestration. - 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. - Agent provider and mode are now resolved server-side, so CLI commands like `paseo run` use consistent defaults without client-side lookups.
### Fixed ### Fixed
- Shift+Enter now correctly inserts a newline in agent terminal input instead of submitting. - 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. - 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. - Branch ahead/behind count no longer errors for branches with no remote tracking branch.
@@ -152,6 +171,7 @@
## 0.1.53 - 2026-04-12 ## 0.1.53 - 2026-04-12
### Added ### 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". - 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. - 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. - 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. - Keyboard shortcut to cycle themes.
### Improved ### Improved
- Unavailable git actions now explain why in a toast instead of being silently greyed out. - Unavailable git actions now explain why in a toast instead of being silently greyed out.
- Streaming markdown on mobile renders significantly faster. - Streaming markdown on mobile renders significantly faster.
- Sidebar, branch switcher, and agent panel no longer re-render unnecessarily — noticeable on large workspaces. - 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. - Relay and pairing URLs are stripped from daemon logs.
### Fixed ### Fixed
- Closed agent tabs no longer reappear after reconnecting. - Closed agent tabs no longer reappear after reconnecting.
- Desktop notification badge counts match across all workspaces. - Desktop notification badge counts match across all workspaces.
- Host switcher status syncs correctly when switching between hosts. - Host switcher status syncs correctly when switching between hosts.
@@ -174,11 +196,13 @@
## 0.1.52 - 2026-04-10 ## 0.1.52 - 2026-04-10
### Added ### Added
- Theme selector — choose from six themes including Midnight, Claude, and Ghostty dark variants. - 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. - 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. - Auto-download updates — desktop updates download silently in the background so they're ready to install when you are.
### Fixed ### 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. - 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. - 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. - 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 ## 0.1.51 - 2026-04-09
### Added ### Added
- Image attachments for OpenCode — attach screenshots and images to OpenCode agent prompts. - 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. - 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. - Send behavior setting — choose whether pressing Enter while an agent is running interrupts immediately or queues your message.
### Fixed ### Fixed
- Model selector no longer crashes on iPad. - Model selector no longer crashes on iPad.
- Pairing now uses the correct hostname, fixing connection failures on some network setups. - Pairing now uses the correct hostname, fixing connection failures on some network setups.
- OpenCode agents show the correct terminal state and refresh models reliably. - OpenCode agents show the correct terminal state and refresh models reliably.
@@ -207,6 +233,7 @@
## 0.1.50 - 2026-04-07 ## 0.1.50 - 2026-04-07
### Added ### 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. - 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. - 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. - 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. - Background git fetch — ahead/behind counts in the Changes pane stay up to date automatically.
### Improved ### Improved
- Workspaces load instantly on connect instead of waiting for a full sync. - 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. - File explorer and diff pane remember which folders are expanded when you switch tabs.
- Closing a workspace tab is now instant. - Closing a workspace tab is now instant.
@@ -222,6 +250,7 @@
- Reload agent moved away from the close button to prevent accidental taps. - Reload agent moved away from the close button to prevent accidental taps.
### Fixed ### Fixed
- Voice mode no longer drifts into false speech detection during long sessions. - Voice mode no longer drifts into false speech detection during long sessions.
- Garbled overlapping text on plan cards. - Garbled overlapping text on plan cards.
- Changes pane could show stale diffs when working with git worktrees. - Changes pane could show stale diffs when working with git worktrees.
@@ -234,6 +263,7 @@
## 0.1.49 - 2026-04-07 ## 0.1.49 - 2026-04-07
### Fixed ### Fixed
- Models and providers now load reliably on first connect instead of requiring a manual refresh. - 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 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. - Model lists stay consistent regardless of which screen you open first.
@@ -241,17 +271,20 @@
## 0.1.48 - 2026-04-05 ## 0.1.48 - 2026-04-05
### Added ### 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 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. - 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. - 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. - Reload tab action — right-click a workspace tab to reload its agent list without restarting the app.
### Improved ### Improved
- Model selector redesigned — grouped by provider with status badges, search, and better touch targets on mobile. - 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. - 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. - Removed noisy agent lifecycle toasts that fired on every state change.
### Fixed ### 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. - 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. - Input field on running agent screens now correctly receives keyboard focus.
- Mobile model selector alignment and sizing. - Mobile model selector alignment and sizing.
@@ -259,6 +292,7 @@
## 0.1.47 - 2026-04-05 ## 0.1.47 - 2026-04-05
### Fixed ### 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. - 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. - 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. - 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 ## 0.1.46 - 2026-04-04
### Fixed ### Fixed
- Voice activation in packaged builds — Silero VAD model is now copied out of the Electron asar archive so native code can read it. - 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. - 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. - `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 ## 0.1.45 - 2026-04-04
### Added ### Added
- Pi (pi.dev) agent provider — connect Pi as a new ACP-based agent type with thinking levels and tool call support. - 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. - Copilot agent provider re-enabled after ACP compatibility fixes.
- `paseo .` and `paseo <path>` open the desktop app with the given project, similar to `code .`. - `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. - Setup hint and paseo.sh link on the mobile welcome screen so new App Store users know what to do next.
### Improved ### Improved
- Desktop startup is faster — existing daemon connections are raced against bootstrap so the app is usable sooner. - 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). - 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. - Sidebar projects and workspaces now persist across sessions, with a context menu to remove projects.
### Fixed ### Fixed
- Sidebar crash when switching iOS theme (Unistyles/Reanimated interaction). - Sidebar crash when switching iOS theme (Unistyles/Reanimated interaction).
- Silero VAD crash caused by external buffer mode in CircularBuffer. - Silero VAD crash caused by external buffer mode in CircularBuffer.
- Bulk close now correctly archives stored agents instead of leaving orphans. - Bulk close now correctly archives stored agents instead of leaving orphans.
@@ -308,6 +346,7 @@
## 0.1.44 - 2026-04-03 ## 0.1.44 - 2026-04-03
### Fixed ### Fixed
- Desktop app now stops the daemon cleanly before auto-update restarts. - Desktop app now stops the daemon cleanly before auto-update restarts.
- Disabled claude-acp and copilot providers from the agent registry. - Disabled claude-acp and copilot providers from the agent registry.
- Keyboard focus scope resolution now checks multiple candidates for broader compatibility. - Keyboard focus scope resolution now checks multiple candidates for broader compatibility.
@@ -317,16 +356,19 @@
## 0.1.43 - 2026-04-02 ## 0.1.43 - 2026-04-02
### Added ### Added
- Copilot agent support via ACP base provider — connect GitHub Copilot as a new agent type. - Copilot agent support via ACP base provider — connect GitHub Copilot as a new agent type.
- Searchable model favorites — quickly find and pin preferred models. - Searchable model favorites — quickly find and pin preferred models.
- Slash command support for OpenCode agents. - Slash command support for OpenCode agents.
### Improved ### Improved
- Refined model selector UX with better mobile sheet behavior. - Refined model selector UX with better mobile sheet behavior.
- Workspace status now uses amber alert styling for "needs input" state. - Workspace status now uses amber alert styling for "needs input" state.
- Themed scrollbar on message input for consistent styling. - Themed scrollbar on message input for consistent styling.
### Fixed ### Fixed
- Ctrl+C/V copy and paste now works correctly in the terminal on Windows and Linux. - 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. - Shell arguments with spaces are now properly quoted on Windows.
- Claude models with 1M context support are now correctly reported. - Claude models with 1M context support are now correctly reported.
@@ -334,11 +376,13 @@
## 0.1.42 - 2026-04-01 ## 0.1.42 - 2026-04-01
### Fixed ### Fixed
- Fixed Claude Code failing to launch on Windows when installed to a path with spaces (e.g. `C:\Program Files\...`). - 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 ## 0.1.41 - 2026-04-01
### Fixed ### Fixed
- Fixed agent spawning on Windows — all providers (Claude, Codex, OpenCode) now use shell mode so npm shims and `.cmd` wrappers resolve correctly. - 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 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 (`\\...`). - 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. - Removed the 40-item cap on activity timeline output so long agent sessions display their full history.
### Improved ### Improved
- Improved light mode theming with dedicated workspace background, scrollbar handle colors, and lighter shadows. - 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. - Window controls overlay on Windows/Linux reduced from 48px to 29px height for a more compact titlebar.
## 0.1.40 - 2026-04-01 ## 0.1.40 - 2026-04-01
### Added ### Added
- Workspace tabs can now be closed in batches. - Workspace tabs can now be closed in batches.
### Improved ### Improved
- Provider model lists are now cached per server and provider, reducing redundant model lookups in the UI. - Provider model lists are now cached per server and provider, reducing redundant model lookups in the UI.
### Fixed ### Fixed
- OpenCode reasoning content no longer appears duplicated as assistant text. - OpenCode reasoning content no longer appears duplicated as assistant text.
- Daemon no longer crashes when a Codex binary is missing or fails to spawn. - Daemon no longer crashes when a Codex binary is missing or fails to spawn.
- Archive tab now correctly reconciles agent visibility after archiving. - Archive tab now correctly reconciles agent visibility after archiving.
@@ -372,61 +420,73 @@
## 0.1.39 - 2026-03-30 ## 0.1.39 - 2026-03-30
### Added ### Added
- **Terminal management from the CLI** — new `paseo terminal` command group lets you list, create, and interact with workspace terminals without leaving your terminal. - **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. - **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
- Fixed iOS sidebar scroll flicker caused by redundant overflow clipping. - Fixed iOS sidebar scroll flicker caused by redundant overflow clipping.
- Centralized window controls padding into a shared hook, eliminating layout inconsistencies across platforms. - Centralized window controls padding into a shared hook, eliminating layout inconsistencies across platforms.
## 0.1.38 - 2026-03-30 ## 0.1.38 - 2026-03-30
### Fixed ### 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 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. - Fixed daemon log rotation losing startup traces — trace-level WebSocket logs no longer include full message payloads.
## 0.1.37 - 2026-03-29 ## 0.1.37 - 2026-03-29
### Added ### Added
- Custom window controls on Windows and Linux — the native titlebar is replaced with overlay controls that match the app's design. - 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. - Desktop file logging with electron-log for easier debugging of daemon and app issues.
### Fixed ### Fixed
- Fixed broken PATH propagation and Claude binary resolution on Windows. - Fixed broken PATH propagation and Claude binary resolution on Windows.
- Dictation errors now show a visible toast instead of failing silently. - Dictation errors now show a visible toast instead of failing silently.
## 0.1.36 - 2026-03-27 ## 0.1.36 - 2026-03-27
### Fixed ### Fixed
- Fixed Windows drive-letter path handling across the codebase. - Fixed Windows drive-letter path handling across the codebase.
- Fixed stale Nix hash with automatic lockfile-change detection. - Fixed stale Nix hash with automatic lockfile-change detection.
### Added ### Added
- Added metrics collection and terminal performance tests. - Added metrics collection and terminal performance tests.
## 0.1.35 - 2026-03-26 ## 0.1.35 - 2026-03-26
### Improved ### Improved
- Faster app startup by redirecting to the welcome screen immediately and showing host connection status inline. - 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. - Codex file deletions now display correctly as removed lines in diffs.
- OpenCode questions are now surfaced in the permission UI. - OpenCode questions are now surfaced in the permission UI.
### Fixed ### Fixed
- Fixed queued prompt dispatch after idle transition. - Fixed queued prompt dispatch after idle transition.
- Replaced bash-only `mapfile` with a portable `while-read` loop in the chat script. - Replaced bash-only `mapfile` with a portable `while-read` loop in the chat script.
### Added ### Added
- Added support for Nix and NixOS installation. - Added support for Nix and NixOS installation.
## 0.1.34 - 2026-03-25 ## 0.1.34 - 2026-03-25
### Added ### Added
- Added `paseo archive` as a top-level alias for `paseo agent archive`. - 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 the `PASEO_AGENT_ID` environment variable for Claude and Codex agents.
- Added a redesigned command autocomplete with a detail card and dropdown styling. - Added a redesigned command autocomplete with a detail card and dropdown styling.
- Linked Android download surfaces to the Google Play Store. - Linked Android download surfaces to the Google Play Store.
### Improved ### Improved
- Autonomous turns now complete gracefully on interrupt instead of being canceled. - 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. - 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. - Restored per-provider form preferences and removed the Auto model fallback.
@@ -435,6 +495,7 @@
- Improved chat transcript readability. - Improved chat transcript readability.
### Fixed ### Fixed
- Fixed `paseo send --no-wait` not taking effect. - Fixed `paseo send --no-wait` not taking effect.
- Fixed stale abort results contaminating replacement turns after an interrupt. - Fixed stale abort results contaminating replacement turns after an interrupt.
- Fixed Claude interrupt handling and autonomous wake reliability. - Fixed Claude interrupt handling and autonomous wake reliability.
@@ -451,6 +512,7 @@
## 0.1.33 - 2026-03-23 ## 0.1.33 - 2026-03-23
### Fixed ### 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 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 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. - 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 ## 0.1.32 - 2026-03-23
### Added ### Added
- Fully rebindable keyboard shortcuts with chord support — all shortcuts are now declarative with proper Cmd (Mac) vs Ctrl (Windows/Linux) separation. - 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. - 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. - Added line numbers and word-wrap toggle to file previews.
@@ -468,6 +531,7 @@
- Added status bar tooltips for project and agent status. - Added status bar tooltips for project and agent status.
### Improved ### Improved
- Redesigned the mobile tab switcher as a compact header row with quick access to new agents and terminals. - 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. - 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. - 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. - Better error messages from the daemon — RPC errors now include the actual underlying details.
### Fixed ### Fixed
- Fixed user messages appearing as assistant output in the timeline when messages contained structured content blocks. - 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 archived workspace routing so navigating to an archived session no longer breaks the app.
- Fixed Linux AppImage failing to launch on Wayland-only desktops. - Fixed Linux AppImage failing to launch on Wayland-only desktops.
@@ -490,16 +555,19 @@
## 0.1.30 - 2026-03-19 ## 0.1.30 - 2026-03-19
### Added ### Added
- Added terminal tabs, split pane controls, and drop previews for workspace layouts. - 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 a combined model selector and agent mode visuals across key UI surfaces.
- Added Open Graph metadata improvements for richer website sharing previews. - Added Open Graph metadata improvements for richer website sharing previews.
### Improved ### Improved
- Improved workspace navigation with better active-workspace tracking and keyboard-driven pane interactions. - 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 terminal scrollbar behavior, pane focus handling, and status bar/message input spacing.
- Improved project picker path display and general workspace UI polish. - Improved project picker path display and general workspace UI polish.
### Fixed ### Fixed
- Fixed agent startup reliability by tightening PATH resolution and surfacing missing provider binaries in status. - 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 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. - 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 ## 0.1.28 - 2026-03-15
### Added ### Added
- Added OpenCode build and plan modes. - Added OpenCode build and plan modes.
- Added website landing pages for Claude Code, Codex, and OpenCode. - Added website landing pages for Claude Code, Codex, and OpenCode.
### Improved ### Improved
- Improved the git action menu for more reliable repository actions. - Improved the git action menu for more reliable repository actions.
- Improved the mobile settings screen, workspace header actions, and welcome screen presentation. - Improved the mobile settings screen, workspace header actions, and welcome screen presentation.
- Updated the website hero copy and added a sponsor callout section. - Updated the website hero copy and added a sponsor callout section.
### Fixed ### Fixed
- Fixed assistant file links so they open the correct workspace files from chat. - Fixed assistant file links so they open the correct workspace files from chat.
## 0.1.27 - 2026-03-13 ## 0.1.27 - 2026-03-13
### Added ### Added
- Added voice runtime with new audio engine architecture for voice interactions. - Added voice runtime with new audio engine architecture for voice interactions.
- Added Grep tool support in Claude tool-call mapping. - Added Grep tool support in Claude tool-call mapping.
- Added ability to open workspace files directly from agent chat messages. - Added ability to open workspace files directly from agent chat messages.
- Added desktop notifications via a custom native bridge. - Added desktop notifications via a custom native bridge.
### Improved ### Improved
- Improved image picker, markdown rendering, and UI interactions. - Improved image picker, markdown rendering, and UI interactions.
- Improved shell environment detection using shell-env. - Improved shell environment detection using shell-env.
### Fixed ### Fixed
- Fixed platform-specific markdown link rendering. - Fixed platform-specific markdown link rendering.
- Fixed Linux AppImage CLI resource paths. - Fixed Linux AppImage CLI resource paths.
- Fixed Codex replacement stream being killed by stale turn notifications. - Fixed Codex replacement stream being killed by stale turn notifications.
@@ -538,15 +612,18 @@
## 0.1.26 - 2026-03-12 ## 0.1.26 - 2026-03-12
### Added ### Added
- Added single-instance desktop behavior, Android APK download access, and refreshed splash screen styling. - 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 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. - Added Windows support with improved cross-platform shell execution.
### Improved ### Improved
- Improved desktop runtime behavior on Windows by suppressing console windows and defaulting app data to `~/.paseo`. - Improved desktop runtime behavior on Windows by suppressing console windows and defaulting app data to `~/.paseo`.
- Added a Discord link to the website navigation. - Added a Discord link to the website navigation.
### Fixed ### Fixed
- Fixed desktop Claude agent startup from the managed runtime and rotated logs correctly on restart. - 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 the home route to hide browser chrome when appropriate.
- Fixed Expo Metro compatibility by updating the `exclusionList` import. - Fixed Expo Metro compatibility by updating the `exclusionList` import.
@@ -556,61 +633,79 @@
## 0.1.25 - 2026-03-11 ## 0.1.25 - 2026-03-11
### Fixed ### 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 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. - Fixed Linux AppImage build by restoring the AppImage bundle format and stripping CUDA dependencies from onnxruntime.
## 0.1.24 - 2026-03-10 ## 0.1.24 - 2026-03-10
### Improved ### Improved
- Improved command center keyboard navigation and new tab shortcut. - Improved command center keyboard navigation and new tab shortcut.
- Simplified desktop release pipeline for faster and more reliable builds. - Simplified desktop release pipeline for faster and more reliable builds.
## 0.1.21 - 2026-03-10 ## 0.1.21 - 2026-03-10
### Improved ### Improved
- Improved desktop release reliability by fixing the Windows managed-runtime build path during GitHub Actions releases. - Improved desktop release reliability by fixing the Windows managed-runtime build path during GitHub Actions releases.
### Fixed ### Fixed
- Fixed a desktop release CI failure caused by a Unix-only server build script on Windows runners. - 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 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. - Fixed a Claude redesign test that depended on the local Claude CLI being installed.
## 0.1.20 - 2026-03-10 ## 0.1.20 - 2026-03-10
### Added ### Added
- Added workspace sidebar git actions with quick diff stats and archive controls. - Added workspace sidebar git actions with quick diff stats and archive controls.
- Added refreshed website downloads and homepage presentation for desktop installs. - Added refreshed website downloads and homepage presentation for desktop installs.
### Improved ### Improved
- Desktop release packaging now rebuilds and validates the bundled managed runtime during CI, improving installer reliability for macOS users. - 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. - Improved desktop and web stream rendering, settings polish, and React 19.1.4 compatibility.
### Fixed ### Fixed
- Fixed Claude interrupt/restart regressions and strengthened managed-daemon smoke coverage for desktop releases. - Fixed Claude interrupt/restart regressions and strengthened managed-daemon smoke coverage for desktop releases.
## 0.1.19 - 2026-03-09 ## 0.1.19 - 2026-03-09
### Added ### Added
- Added a draft GitHub release flow so maintainers can upload and review desktop and Android release assets before publishing the final release. - 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 ## 0.1.18 - 2026-03-06
### Added ### Added
- Added a desktop `Mod+W` shortcut to close the current tab. - Added a desktop `Mod+W` shortcut to close the current tab.
### Improved ### Improved
- New and newly selected terminals now take focus automatically so you can type immediately. - 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. - 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. - 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. - Updated the website desktop download link to use the universal macOS DMG.
### Fixed ### Fixed
- Restored automatic agent metadata generation for Claude runs. - Restored automatic agent metadata generation for Claude runs.
## 0.1.17 - 2026-03-06 ## 0.1.17 - 2026-03-06
### Added ### Added
- New workspace-first navigation model with workspace tabs, file tabs, and sortable tab groups. - 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. - 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. - 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. - In-chat task notifications rendered as synthetic tool-call events for clearer status tracking.
### Improved ### Improved
- Desktop builds now ship as a universal macOS binary (Apple Silicon + Intel). - 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. - 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. - 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. - Stronger provider error surfacing and updated Claude model/runtime handling.
### Fixed ### Fixed
- Fixed orphan workspace runs caused by non-canonical tab routes. - Fixed orphan workspace runs caused by non-canonical tab routes.
- Fixed mobile terminal tab remount/routing restore issues. - Fixed mobile terminal tab remount/routing restore issues.
- Fixed agent metadata title/branch update reliability. - Fixed agent metadata title/branch update reliability.
@@ -625,7 +721,9 @@
- Fixed reversed edge-wheel scroll behavior in chat/tool stream views. - Fixed reversed edge-wheel scroll behavior in chat/tool stream views.
## 0.1.16 - 2026-02-22 ## 0.1.16 - 2026-02-22
### Added ### Added
- Update the Paseo desktop app and local daemon directly from Settings. - Update the Paseo desktop app and local daemon directly from Settings.
- Microphone and notification permission controls in Settings. - Microphone and notification permission controls in Settings.
- Thinking/reasoning mode — agents can use extended thinking when the provider supports it. - 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. - `paseo wait` now shows a snapshot of recent agent activity while you wait.
### Improved ### Improved
- Smoother streaming with less UI flicker and scroll jumping during long agent runs. - Smoother streaming with less UI flicker and scroll jumping during long agent runs.
- Faster agent sidebar list rendering. - Faster agent sidebar list rendering.
- Archiving an agent now stops it first instead of archiving a half-running session. - Archiving an agent now stops it first instead of archiving a half-running session.
@@ -640,16 +739,20 @@
- More reliable relay connections. - More reliable relay connections.
### Fixed ### Fixed
- Fixed Claude background tasks desyncing the chat. - Fixed Claude background tasks desyncing the chat.
- Fixed duplicate user messages appearing in the timeline. - Fixed duplicate user messages appearing in the timeline.
- Fixed a startup crash caused by an OpenCode SDK update. - Fixed a startup crash caused by an OpenCode SDK update.
- Fixed spurious "needs attention" notifications from background agent activity. - Fixed spurious "needs attention" notifications from background agent activity.
## 0.1.15 - 2026-02-19 ## 0.1.15 - 2026-02-19
### Added ### Added
- Added a public changelog page on the website so users can browse release notes. - Added a public changelog page on the website so users can browse release notes.
### Improved ### Improved
- Redesigned the website get-started experience into a clearer two-step flow. - Redesigned the website get-started experience into a clearer two-step flow.
- Simplified website GitHub navigation and changelog headings. - Simplified website GitHub navigation and changelog headings.
- Improved app draft/new-agent UX with clearer working directory placeholder and empty-state messaging. - 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. - Hid empty filter groups in the left sidebar.
### Fixed ### Fixed
- Fixed archived-agent navigation by redirecting archived agent routes to draft. - Fixed archived-agent navigation by redirecting archived agent routes to draft.
- Fixed duplicate `/rewind` user-message behavior. - Fixed duplicate `/rewind` user-message behavior.
## 0.1.14 - 2026-02-19 ## 0.1.14 - 2026-02-19
### Added ### Added
- Added Claude `/rewind` command support. - Added Claude `/rewind` command support.
- Added slash command access in the draft agent composer. - Added slash command access in the draft agent composer.
- Added `@` workspace file autocomplete in chat prompts. - Added `@` workspace file autocomplete in chat prompts.
@@ -670,6 +776,7 @@
- Added shared desktop/web overlay scroll handles, including file preview panes. - Added shared desktop/web overlay scroll handles, including file preview panes.
### Improved ### Improved
- Improved worktree flow after shipping, including better merged PR detection. - Improved worktree flow after shipping, including better merged PR detection.
- Improved draft workflow by enabling the explorer sidebar immediately after CWD selection. - Improved draft workflow by enabling the explorer sidebar immediately after CWD selection.
- Improved new worktree-agent defaults by prefilling CWD to the main repository. - 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. - Improved scrollbar visibility, drag interactions, tracking, and animation timing on web/desktop.
### Fixed ### Fixed
- Fixed worktree archive/setup lifecycle issues, including terminal cleanup and archive timing. - 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 worktree path collisions by hashing CWD for collision-safe worktree roots.
- Fixed terminal sizing when switching back to an agent session. - 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. - Fixed daemon version badge visibility in settings when daemon version data is unavailable.
## 0.1.9 - 2026-02-17 ## 0.1.9 - 2026-02-17
### Improved ### Improved
- Unified structured-output generation through a single shared schema-validation and retry pipeline. - Unified structured-output generation through a single shared schema-validation and retry pipeline.
- Reused provider availability checks for structured generation fallback selection. - 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. - Added structured generation waterfall ordering for internal metadata and git text generation: Claude Haiku, then Codex, then OpenCode.
### Fixed ### Fixed
- Fixed CLI `run --output-schema` to use the shared structured-output path instead of ad-hoc JSON parsing. - 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 `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. - Fixed internal commit message, pull request text, and agent metadata generation to follow one consistent structured pipeline.
## 0.1.8 - 2026-02-17 ## 0.1.8 - 2026-02-17
### Added ### Added
- Added a cross-platform confirm dialog flow for daemon restarts. - Added a cross-platform confirm dialog flow for daemon restarts.
### Improved ### Improved
- Simplified local speech bootstrap and daemon startup locking behavior. - Simplified local speech bootstrap and daemon startup locking behavior.
- Updated website hero copy to emphasize local execution. - Updated website hero copy to emphasize local execution.
### Fixed ### Fixed
- Fixed stuck "send while running" recovery across app and server session handling. - Fixed stuck "send while running" recovery across app and server session handling.
- Fixed Claude session identity preservation when reloading existing agents. - Fixed Claude session identity preservation when reloading existing agents.
- Fixed combobox option behavior and related interactions. - Fixed combobox option behavior and related interactions.
@@ -718,73 +833,93 @@
- Fixed web tool-detail wheel event routing at scroll edges. - Fixed web tool-detail wheel event routing at scroll edges.
## 0.1.7 - 2026-02-16 ## 0.1.7 - 2026-02-16
### Added ### Added
- Improved agent workspace flows with better directory suggestions. - Improved agent workspace flows with better directory suggestions.
- Added iOS TestFlight and Android app access request forms on the website. - Added iOS TestFlight and Android app access request forms on the website.
### Improved ### Improved
- Unified daemon startup behavior between dev and CLI paths for more predictable local runs. - Unified daemon startup behavior between dev and CLI paths for more predictable local runs.
- Improved website app download and update guidance. - Improved website app download and update guidance.
### Fixed ### Fixed
- Prevented an initial desktop combobox `0,0` position flash. - Prevented an initial desktop combobox `0,0` position flash.
- Fixed CLI version output issues. - Fixed CLI version output issues.
- Hardened server runtime loading for local speech dependencies. - Hardened server runtime loading for local speech dependencies.
## 0.1.6 - 2026-02-16 ## 0.1.6 - 2026-02-16
### Notes ### Notes
- No major visible product changes in this patch release. - No major visible product changes in this patch release.
## 0.1.5 - 2026-02-16 ## 0.1.5 - 2026-02-16
### Added ### Added
- Added terminal reattach support and better worktree terminal handling. - Added terminal reattach support and better worktree terminal handling.
- Added global keyboard shortcut help in the app. - Added global keyboard shortcut help in the app.
- Added sidebar host filtering and improved agent workflow controls. - Added sidebar host filtering and improved agent workflow controls.
### Improved ### Improved
- Improved worktree setup visibility by streaming setup progress. - Improved worktree setup visibility by streaming setup progress.
- Improved terminal streaming reliability and lifecycle handling. - Improved terminal streaming reliability and lifecycle handling.
- Preserved explorer tab state so context survives navigation better. - Preserved explorer tab state so context survives navigation better.
## 0.1.4 - 2026-02-14 ## 0.1.4 - 2026-02-14
### Added ### Added
- Added voice capability status reporting in the client. - Added voice capability status reporting in the client.
- Added background local speech model downloads with runtime gating. - Added background local speech model downloads with runtime gating.
- Added adaptive dictation finish timing based on server-provided budgets. - Added adaptive dictation finish timing based on server-provided budgets.
- Added relay reconnect behavior with grace periods and branch suggestions. - Added relay reconnect behavior with grace periods and branch suggestions.
### Improved ### Improved
- Improved connection selection and agent hydration reliability. - Improved connection selection and agent hydration reliability.
- Improved timeline loading with cursor-based fetch behavior. - Improved timeline loading with cursor-based fetch behavior.
- Improved first-run experience by bootstrapping a default localhost connection. - Improved first-run experience by bootstrapping a default localhost connection.
- Improved inline code rendering by auto-linkifying URLs. - Improved inline code rendering by auto-linkifying URLs.
### Fixed ### Fixed
- Fixed Linux checkout diff watch behavior to avoid recursive watches. - Fixed Linux checkout diff watch behavior to avoid recursive watches.
- Fixed stale relay client timer behavior. - Fixed stale relay client timer behavior.
- Fixed unnecessary git diff header auto-scroll on collapse. - Fixed unnecessary git diff header auto-scroll on collapse.
## 0.1.3 - 2026-02-12 ## 0.1.3 - 2026-02-12
### Added ### Added
- Added CLI onboarding command. - Added CLI onboarding command.
- Added CLI `--output-schema` support for structured agent output. - Added CLI `--output-schema` support for structured agent output.
- Added CLI agent metadata update support for names and labels. - Added CLI agent metadata update support for names and labels.
- Added provider availability detection with normalization of legacy default model IDs. - Added provider availability detection with normalization of legacy default model IDs.
### Improved ### Improved
- Improved file explorer refresh feedback and unresolved checkout fallback handling. - Improved file explorer refresh feedback and unresolved checkout fallback handling.
- Added better voice interrupt handling with a speech-start grace period. - Added better voice interrupt handling with a speech-start grace period.
- Improved CLI defaults to list all non-archived agents by default. - Improved CLI defaults to list all non-archived agents by default.
- Improved website UX with clearer install CTA and privacy policy access. - Improved website UX with clearer install CTA and privacy policy access.
### Fixed ### Fixed
- Fixed dev runner entry issues and sherpa TTS initialization behavior. - Fixed dev runner entry issues and sherpa TTS initialization behavior.
## 0.1.2 - 2026-02-11 ## 0.1.2 - 2026-02-11
### Notes ### Notes
- No major visible product changes in this patch release. - No major visible product changes in this patch release.
## 0.1.1 - 2026-02-11 ## 0.1.1 - 2026-02-11
### Added ### Added
- Initial `0.1.x` release line. - Initial `0.1.x` release line.
+24 -25
View File
@@ -17,17 +17,17 @@ This is an npm workspace monorepo:
## Documentation ## Documentation
| Doc | What's in it | | Doc | What's in it |
|---|---| | ---------------------------------------------------- | --------------------------------------------------------------------------------- |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design, package layering, WebSocket protocol, agent lifecycle, data flow | | [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/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/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/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/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/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/ANDROID.md](docs/ANDROID.md) | App variants, local/cloud builds, EAS workflows |
| [docs/DESIGN.md](docs/DESIGN.md) | How to design features before implementation | | [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 | | [SECURITY.md](SECURITY.md) | Relay threat model, E2E encryption, DNS rebinding, agent auth |
## Quick start ## Quick start
@@ -68,23 +68,23 @@ The app runs on iOS, Android, web (browser), and web (Electron desktop). Code is
### The four gates ### The four gates
| Gate | Type | When to use | | Gate | Type | When to use |
|---|---|---| | -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |
| `isWeb` | constant | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. | | `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`. | | `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. | | `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`. | | `useIsCompactFormFactor()` | hook | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`. |
### Decision matrix ### Decision matrix
| I need to... | Use | | I need to... | Use |
|---|---| | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)` | | Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)` |
| Use a native-only API (Haptics, push tokens, camera) | `if (isNative)` | | Use a native-only API (Haptics, push tokens, camera) | `if (isNative)` |
| Use an Electron bridge (file dialog, titlebar, updates) | `if (getIsElectron())` | | Use an Electron bridge (file dialog, titlebar, updates) | `if (getIsElectron())` |
| Switch layout between phone and tablet/desktop | `useIsCompactFormFactor()` | | Switch layout between phone and tablet/desktop | `useIsCompactFormFactor()` |
| Show something on hover, always-visible on native | `isHovered \|\| isNative \|\| isCompact` (hover only works on web) | | 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) | | Gate to iOS or Android specifically | `Platform.OS === "ios"` / `Platform.OS === "android"` (rare, keep inline) |
### Rules ### Rules
@@ -104,5 +104,4 @@ The app runs on iOS, Android, web (browser), and web (Electron desktop). Code is
## Debugging ## Debugging
Find the complete daemon logs and traces in the $PASEO_HOME/daemon.log Find the complete daemon logs and traces in the $PASEO_HOME/daemon.log
+2
View File
@@ -69,6 +69,7 @@ paseo
This shows a QR code in the terminal. Connect from any client. This path is useful for servers and remote machines. 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: For full setup and configuration, see:
- [Docs](https://paseo.sh/docs) - [Docs](https://paseo.sh/docs)
- [Configuration reference](https://paseo.sh/docs/configuration) - [Configuration reference](https://paseo.sh/docs/configuration)
@@ -116,6 +117,7 @@ Then use them in any agent conversation:
## Development ## Development
Quick monorepo package map: Quick monorepo package map:
- `packages/server`: Paseo daemon (agent process orchestration, WebSocket API, MCP server) - `packages/server`: Paseo daemon (agent process orchestration, WebSocket API, MCP server)
- `packages/app`: Expo client (iOS, Android, web) - `packages/app`: Expo client (iOS, Android, web)
- `packages/cli`: `paseo` CLI for daemon and agent workflows - `packages/cli`: `paseo` CLI for daemon and agent workflows
+2
View File
@@ -58,6 +58,7 @@ await rm(staticDir, { recursive: true, force: true });
``` ```
Run with: Run with:
```bash ```bash
npx tsx packages/server/src/server/your-script.ts 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. 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`: Always pass `appVersion`:
```typescript ```typescript
const client = new DaemonClient({ const client = new DaemonClient({
url: `ws://127.0.0.1:${port}/ws`, url: `ws://127.0.0.1:${port}/ws`,
+3 -3
View File
@@ -4,9 +4,9 @@
Controlled by `APP_VARIANT` in `packages/app/app.config.js` (vanilla Expo, no custom Gradle plugin): Controlled by `APP_VARIANT` in `packages/app/app.config.js` (vanilla Expo, no custom Gradle plugin):
| Variant | App name | Package ID | | Variant | App name | Package ID |
|---|---|---| | ------------- | ----------- | ---------------- |
| `production` | Paseo | `sh.paseo` | | `production` | Paseo | `sh.paseo` |
| `development` | Paseo Debug | `sh.paseo.debug` | | `development` | Paseo Debug | `sh.paseo.debug` |
EAS profiles: `development`, `production`, and `production-apk` in `packages/app/eas.json`. EAS profiles: `development`, `production`, and `production-apk` in `packages/app/eas.json`.
+18 -16
View File
@@ -53,17 +53,17 @@ The heart of Paseo. A Node.js process that:
**Key modules:** **Key modules:**
| Module | Responsibility | | Module | Responsibility |
|---|---| | ------------------------- | ----------------------------------------------------------------------------- |
| `bootstrap.ts` | Daemon initialization: HTTP server, WS server, agent manager, storage, relay | | `bootstrap.ts` | Daemon initialization: HTTP server, WS server, agent manager, storage, relay |
| `websocket-server.ts` | WebSocket connection management, hello/welcome handshake, binary multiplexing | | `websocket-server.ts` | WebSocket connection management, hello/welcome handshake, binary multiplexing |
| `session.ts` | Per-client session state, timeline subscriptions, terminal operations | | `session.ts` | Per-client session state, timeline subscriptions, terminal operations |
| `agent/agent-manager.ts` | Agent lifecycle state machine, timeline tracking, subscriber management | | `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/agent-storage.ts` | File-backed JSON persistence at `$PASEO_HOME/agents/` |
| `agent/mcp-server.ts` | MCP server for sub-agent creation, permissions, timeouts | | `agent/mcp-server.ts` | MCP server for sub-agent creation, permissions, timeouts |
| `providers/` | Provider adapters: Claude (Agent SDK), Codex (AppServer), OpenCode | | `providers/` | Provider adapters: Claude (Agent SDK), Codex (AppServer), OpenCode |
| `relay-transport.ts` | Outbound relay connection with E2E encryption | | `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) | | `client/daemon-client.ts` | Client library for connecting to the daemon (used by CLI and app) |
### `packages/app` — Mobile + web client (Expo) ### `packages/app` — Mobile + web client (Expo)
@@ -132,6 +132,7 @@ Server → Client: WSWelcomeMessage { clientId, daemonVersion, sessionId, capab
**Binary multiplexing:** **Binary multiplexing:**
Terminal I/O and agent streaming share the same connection via `BinaryMuxFrame`: Terminal I/O and agent streaming share the same connection via `BinaryMuxFrame`:
- Channel 0: control messages - Channel 0: control messages
- Channel 1: terminal data - Channel 1: terminal data
- 1-byte channel ID + 1-byte flags + variable payload - 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: Each provider implements a common `AgentClient` interface:
| Provider | Wraps | Session format | | Provider | Wraps | Session format |
|---|---|---| | -------- | ------------------- | -------------------------------------------------- |
| Claude | Anthropic Agent SDK | `~/.claude/projects/{cwd}/{session-id}.jsonl` | | Claude | Anthropic Agent SDK | `~/.claude/projects/{cwd}/{session-id}.jsonl` |
| Codex | CodexAppServer | `~/.codex/sessions/{date}/rollout-{ts}-{id}.jsonl` | | Codex | CodexAppServer | `~/.codex/sessions/{date}/rollout-{ts}-{id}.jsonl` |
| OpenCode | OpenCode CLI | Provider-managed | | OpenCode | OpenCode CLI | Provider-managed |
All providers: All providers:
- Handle their own authentication (Paseo does not manage API keys) - Handle their own authentication (Paseo does not manage API keys)
- Support session resume via persistence handles - Support session resume via persistence handles
- Map tool calls to a normalized `ToolCallDetail` type - Map tool calls to a normalized `ToolCallDetail` type
+18 -4
View File
@@ -40,7 +40,10 @@ No complex inline types in public function signatures.
function enqueueJob(input: { userId: string; priority: "low" | "normal" | "high" }) {} function enqueueJob(input: { userId: string; priority: "low" | "normal" | "high" }) {}
// Good // Good
interface EnqueueJobInput { userId: string; priority: "low" | "normal" | "high" } interface EnqueueJobInput {
userId: string;
priority: "low" | "normal" | "high";
}
function enqueueJob(input: EnqueueJobInput) {} 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) {} function createToolCall(provider: string, toolName: string, payload: unknown) {}
// Good: object param // Good: object param
interface CreateToolCallInput { provider: string; toolName: string; payload: unknown } interface CreateToolCallInput {
provider: string;
toolName: string;
payload: unknown;
}
function createToolCall(input: CreateToolCallInput) {} function createToolCall(input: CreateToolCallInput) {}
``` ```
@@ -78,7 +85,11 @@ Use discriminated unions instead of bags of booleans and optionals.
```typescript ```typescript
// Bad // Bad
interface FetchState { isLoading: boolean; error?: Error; data?: Data } interface FetchState {
isLoading: boolean;
error?: Error;
data?: Data;
}
// Good // Good
type FetchState = type FetchState =
@@ -136,7 +147,10 @@ Avoid packing branching, lookup, and transformation into single dense expression
// Bad: nested ternaries + inline lookups // Bad: nested ternaries + inline lookups
const billing = shouldUseLegacy(account) const billing = shouldUseLegacy(account)
? getLegacy(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 // Good: named steps, then assemble
const rate = rates.find((r) => r.region === account.region); const rate = rates.find((r) => r.region === account.region);
+47 -51
View File
@@ -55,6 +55,7 @@ Use `extends` to create a new provider entry that inherits from a built-in provi
``` ```
Required fields for custom providers: Required fields for custom providers:
- `extends` — which built-in provider to inherit from (or `"acp"`) - `extends` — which built-in provider to inherit from (or `"acp"`)
- `label` — display name in the UI - `label` — display name in the UI
@@ -95,12 +96,12 @@ Required fields for custom providers:
### Available models ### Available models
| Model | Tier | | Model | Tier |
|---|---| | ------------- | ------------------- |
| `glm-5.1` | Advanced (flagship) | | `glm-5.1` | Advanced (flagship) |
| `glm-5-turbo` | Advanced | | `glm-5-turbo` | Advanced |
| `glm-4.7` | Standard | | `glm-4.7` | Standard |
| `glm-4.5-air` | Lightweight | | `glm-4.5-air` | Lightweight |
### Notes ### Notes
@@ -148,10 +149,10 @@ Required fields for custom providers:
### API endpoints ### API endpoints
| Mode | Base URL | | Mode | Base URL |
|---|---| | ------------------------------- | ----------------------------------------------------------- |
| Coding plan (subscription) | `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic` | | Coding plan (subscription) | `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic` |
| Pay-as-you-go (no subscription) | `https://dashscope-intl.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`. 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:** **Recommended for coding plan:**
| Model | Notes | | Model | Notes |
|---|---| | ------------------ | --------------------------- |
| `qwen3.5-plus` | Vision capable, recommended | | `qwen3.5-plus` | Vision capable, recommended |
| `qwen3-coder-next` | Optimized for coding | | `qwen3-coder-next` | Optimized for coding |
| `kimi-k2.5` | Vision capable | | `kimi-k2.5` | Vision capable |
| `glm-5` | Zhipu GLM | | `glm-5` | Zhipu GLM |
| `MiniMax-M2.5` | MiniMax | | `MiniMax-M2.5` | MiniMax |
**Additional models (pay-as-you-go):** **Additional models (pay-as-you-go):**
`qwen3-max`, `qwen3.5-flash`, `qwen3-coder-plus`, `qwen3-coder-flash`, `qwen3-vl-plus`, `qwen3-vl-flash` `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": { "claude-fast": {
"extends": "claude", "extends": "claude",
"label": "Claude (Fast)", "label": "Claude (Fast)",
"models": [ "models": [{ "id": "claude-sonnet-4-6", "label": "Sonnet 4.6", "isDefault": true }]
{ "id": "claude-sonnet-4-6", "label": "Sonnet 4.6", "isDefault": true }
]
}, },
"claude-smart": { "claude-smart": {
"extends": "claude", "extends": "claude",
"label": "Claude (Smart)", "label": "Claude (Smart)",
"models": [ "models": [{ "id": "claude-opus-4-6", "label": "Opus 4.6", "isDefault": true }]
{ "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: Required fields for ACP providers:
- `extends: "acp"` - `extends: "acp"`
- `label` - `label`
- `command` — the command to spawn the agent process (must support ACP over stdio) - `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", "extends": "acp",
"label": "My Agent", "label": "My Agent",
"command": ["my-agent", "--acp"], "command": ["my-agent", "--acp"],
"additionalModels": [ "additionalModels": [{ "id": "provider/model-id", "label": "My Preferred Label" }]
{ "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: Every entry under `agents.providers` accepts these fields:
| Field | Type | Required | Description | | Field | Type | Required | Description |
|---|---|---|---| | ------------------ | ------------------------ | ----------------- | ------------------------------------------------------------------ |
| `extends` | `string` | Yes (custom only) | Built-in provider ID to inherit from, or `"acp"` | | `extends` | `string` | Yes (custom only) | Built-in provider ID to inherit from, or `"acp"` |
| `label` | `string` | Yes (custom only) | Display name in the UI | | `label` | `string` | Yes (custom only) | Display name in the UI |
| `description` | `string` | No | Short description shown in the UI | | `description` | `string` | No | Short description shown in the UI |
| `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process | | `command` | `string[]` | Yes (ACP only) | Command to spawn the agent process |
| `env` | `Record<string, string>` | No | Environment variables to set for 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) | | `models` | `ProviderProfileModel[]` | No | Static model list (overrides runtime discovery) |
| `additionalModels` | `ProviderProfileModel[]` | No | Static model additions (merged with runtime discovery or `models`) | | `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"]`) | | `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`) | | `enabled` | `boolean` | No | Set to `false` to hide the provider (default: `true`) |
| `order` | `number` | No | Sort order in the provider list | | `order` | `number` | No | Sort order in the provider list |
### Model definition ### Model definition
Each entry in the `models` array: Each entry in the `models` array:
| Field | Type | Required | Description | | Field | Type | Required | Description |
|---|---|---|---| | ----------------- | ------------------ | -------- | ------------------------------------- |
| `id` | `string` | Yes | Model identifier sent to the provider | | `id` | `string` | Yes | Model identifier sent to the provider |
| `label` | `string` | Yes | Display name in the UI | | `label` | `string` | Yes | Display name in the UI |
| `description` | `string` | No | Short description | | `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default model selection | | `isDefault` | `boolean` | No | Mark as the default model selection |
| `thinkingOptions` | `ThinkingOption[]` | No | Available thinking/reasoning levels | | `thinkingOptions` | `ThinkingOption[]` | No | Available thinking/reasoning levels |
### Thinking option ### Thinking option
| Field | Type | Required | Description | | Field | Type | Required | Description |
|---|---|---|---| | ------------- | --------- | -------- | ----------------------------------- |
| `id` | `string` | Yes | Thinking option identifier | | `id` | `string` | Yes | Thinking option identifier |
| `label` | `string` | Yes | Display name | | `label` | `string` | Yes | Display name |
| `description` | `string` | No | Short description | | `description` | `string` | No | Short description |
| `isDefault` | `boolean` | No | Mark as the default thinking option | | `isDefault` | `boolean` | No | Mark as the default thinking option |
### Gotcha: `extends: "claude"` with third-party endpoints ### Gotcha: `extends: "claude"` with third-party endpoints
+200 -200
View File
@@ -34,88 +34,88 @@ $PASEO_HOME/
Each agent is stored as a separate JSON file, grouped by project directory. Each agent is stored as a separate JSON file, grouped by project directory.
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | -------------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| `id` | `string` | UUID, primary key | | `id` | `string` | UUID, primary key |
| `provider` | `string` | Agent provider (`"claude"`, `"codex"`, `"opencode"`, etc.) | | `provider` | `string` | Agent provider (`"claude"`, `"codex"`, `"opencode"`, etc.) |
| `cwd` | `string` | Working directory the agent operates in | | `cwd` | `string` | Working directory the agent operates in |
| `createdAt` | `string` (ISO 8601) | Creation timestamp | | `createdAt` | `string` (ISO 8601) | Creation timestamp |
| `updatedAt` | `string` (ISO 8601) | Last update timestamp | | `updatedAt` | `string` (ISO 8601) | Last update timestamp |
| `lastActivityAt` | `string?` (ISO 8601) | Last activity timestamp | | `lastActivityAt` | `string?` (ISO 8601) | Last activity timestamp |
| `lastUserMessageAt` | `string?` (ISO 8601) | Last user message timestamp | | `lastUserMessageAt` | `string?` (ISO 8601) | Last user message timestamp |
| `title` | `string?` | User-visible title | | `title` | `string?` | User-visible title |
| `labels` | `Record<string, string>` | Key-value labels (default `{}`) | | `labels` | `Record<string, string>` | Key-value labels (default `{}`) |
| `lastStatus` | `AgentStatus` | One of: `"initializing"`, `"idle"`, `"running"`, `"error"`, `"closed"` | | `lastStatus` | `AgentStatus` | One of: `"initializing"`, `"idle"`, `"running"`, `"error"`, `"closed"` |
| `lastModeId` | `string?` | Last active mode ID | | `lastModeId` | `string?` | Last active mode ID |
| `config` | `SerializableConfig?` | Agent session configuration (see below) | | `config` | `SerializableConfig?` | Agent session configuration (see below) |
| `runtimeInfo` | `RuntimeInfo?` | Live runtime state (see below) | | `runtimeInfo` | `RuntimeInfo?` | Live runtime state (see below) |
| `features` | `AgentFeature[]?` | Provider-reported features (toggles/selects) | | `features` | `AgentFeature[]?` | Provider-reported features (toggles/selects) |
| `persistence` | `PersistenceHandle?` | Handle for resuming sessions | | `persistence` | `PersistenceHandle?` | Handle for resuming sessions |
| `requiresAttention` | `boolean?` | Whether the agent needs user attention | | `requiresAttention` | `boolean?` | Whether the agent needs user attention |
| `attentionReason` | `"finished" \| "error" \| "permission"?` | Why attention is needed | | `attentionReason` | `"finished" \| "error" \| "permission"?` | Why attention is needed |
| `attentionTimestamp` | `string?` (ISO 8601) | When attention was flagged | | `attentionTimestamp` | `string?` (ISO 8601) | When attention was flagged |
| `internal` | `boolean?` | Whether this is a system-internal agent (loop workers, etc.) | | `internal` | `boolean?` | Whether this is a system-internal agent (loop workers, etc.) |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp | | `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
### Nested: SerializableConfig ### Nested: SerializableConfig
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------------ | -------------------------- | ---------------------------- |
| `title` | `string?` | Configured title | | `title` | `string?` | Configured title |
| `modeId` | `string?` | Configured mode | | `modeId` | `string?` | Configured mode |
| `model` | `string?` | Configured model | | `model` | `string?` | Configured model |
| `thinkingOptionId` | `string?` | Thinking/reasoning level | | `thinkingOptionId` | `string?` | Thinking/reasoning level |
| `featureValues` | `Record<string, unknown>?` | Feature preference overrides | | `featureValues` | `Record<string, unknown>?` | Feature preference overrides |
| `extra` | `Record<string, any>?` | Provider-specific config | | `extra` | `Record<string, any>?` | Provider-specific config |
| `systemPrompt` | `string?` | Custom system prompt | | `systemPrompt` | `string?` | Custom system prompt |
| `mcpServers` | `Record<string, any>?` | MCP server configurations | | `mcpServers` | `Record<string, any>?` | MCP server configurations |
### Nested: RuntimeInfo ### Nested: RuntimeInfo
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------------ | -------------------------- | ------------------------------ |
| `provider` | `string` | Active provider | | `provider` | `string` | Active provider |
| `sessionId` | `string?` | Active session ID | | `sessionId` | `string?` | Active session ID |
| `model` | `string?` | Active model | | `model` | `string?` | Active model |
| `thinkingOptionId` | `string?` | Active thinking option | | `thinkingOptionId` | `string?` | Active thinking option |
| `modeId` | `string?` | Active mode | | `modeId` | `string?` | Active mode |
| `extra` | `Record<string, unknown>?` | Provider-specific runtime data | | `extra` | `Record<string, unknown>?` | Provider-specific runtime data |
### Nested: PersistenceHandle ### Nested: PersistenceHandle
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | -------------- | ---------------------- | --------------------------------------------------------------------- |
| `provider` | `string` | Provider that owns the session | | `provider` | `string` | Provider that owns the session |
| `sessionId` | `string` | Session ID for resumption | | `sessionId` | `string` | Session ID for resumption |
| `nativeHandle` | `any?` | Provider-specific handle (Codex thread ID, Claude resume token, etc.) | | `nativeHandle` | `any?` | Provider-specific handle (Codex thread ID, Claude resume token, etc.) |
| `metadata` | `Record<string, any>?` | Extra metadata | | `metadata` | `Record<string, any>?` | Extra metadata |
### Nested: AgentFeature (discriminated union on `type`) ### Nested: AgentFeature (discriminated union on `type`)
**Toggle:** **Toggle:**
| Field | Type | | Field | Type |
|---|---| | ------------- | ---------- |
| `type` | `"toggle"` | | `type` | `"toggle"` |
| `id` | `string` | | `id` | `string` |
| `label` | `string` | | `label` | `string` |
| `description` | `string?` | | `description` | `string?` |
| `tooltip` | `string?` | | `tooltip` | `string?` |
| `icon` | `string?` | | `icon` | `string?` |
| `value` | `boolean` | | `value` | `boolean` |
**Select:** **Select:**
| Field | Type | | Field | Type |
|---|---| | ------------- | --------------------- |
| `type` | `"select"` | | `type` | `"select"` |
| `id` | `string` | | `id` | `string` |
| `label` | `string` | | `label` | `string` |
| `description` | `string?` | | `description` | `string?` |
| `tooltip` | `string?` | | `tooltip` | `string?` |
| `icon` | `string?` | | `icon` | `string?` |
| `value` | `string?` | | `value` | `string?` |
| `options` | `AgentSelectOption[]` | | `options` | `AgentSelectOption[]` |
--- ---
@@ -172,22 +172,22 @@ All fields are optional with sensible defaults.
One file per schedule. ID is 8 hex characters. One file per schedule. ID is 8 hex characters.
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ----------- | ------------------------------------- | -------------------------------- |
| `id` | `string` | 8-char hex ID | | `id` | `string` | 8-char hex ID |
| `name` | `string?` | Human-readable name | | `name` | `string?` | Human-readable name |
| `prompt` | `string` | The prompt to send | | `prompt` | `string` | The prompt to send |
| `cadence` | `ScheduleCadence` | Timing (see below) | | `cadence` | `ScheduleCadence` | Timing (see below) |
| `target` | `ScheduleTarget` | What to run (see below) | | `target` | `ScheduleTarget` | What to run (see below) |
| `status` | `"active" \| "paused" \| "completed"` | Current state | | `status` | `"active" \| "paused" \| "completed"` | Current state |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | | | `updatedAt` | `string` (ISO 8601) | |
| `nextRunAt` | `string?` (ISO 8601) | Next scheduled execution | | `nextRunAt` | `string?` (ISO 8601) | Next scheduled execution |
| `lastRunAt` | `string?` (ISO 8601) | Last execution time | | `lastRunAt` | `string?` (ISO 8601) | Last execution time |
| `pausedAt` | `string?` (ISO 8601) | When paused | | `pausedAt` | `string?` (ISO 8601) | When paused |
| `expiresAt` | `string?` (ISO 8601) | Auto-expire time | | `expiresAt` | `string?` (ISO 8601) | Auto-expire time |
| `maxRuns` | `number?` | Max executions before completing | | `maxRuns` | `number?` | Max executions before completing |
| `runs` | `ScheduleRun[]` | Execution history | | `runs` | `ScheduleRun[]` | Execution history |
### Nested: ScheduleCadence (discriminated union on `type`) ### Nested: ScheduleCadence (discriminated union on `type`)
@@ -201,16 +201,16 @@ One file per schedule. ID is 8 hex characters.
### Nested: ScheduleRun ### Nested: ScheduleRun
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | -------------- | -------------------------------------- | ----------------------- |
| `id` | `string` | Run ID | | `id` | `string` | Run ID |
| `scheduledFor` | `string` (ISO 8601) | Intended execution time | | `scheduledFor` | `string` (ISO 8601) | Intended execution time |
| `startedAt` | `string` (ISO 8601) | | | `startedAt` | `string` (ISO 8601) | |
| `endedAt` | `string?` (ISO 8601) | | | `endedAt` | `string?` (ISO 8601) | |
| `status` | `"running" \| "succeeded" \| "failed"` | | | `status` | `"running" \| "succeeded" \| "failed"` | |
| `agentId` | `string?` (UUID) | Agent used for this run | | `agentId` | `string?` (UUID) | Agent used for this run |
| `output` | `string?` | Agent output text | | `output` | `string?` | Agent output text |
| `error` | `string?` | Error message if failed | | `error` | `string?` | Error message if failed |
--- ---
@@ -229,25 +229,25 @@ Single file containing all rooms and messages.
### ChatRoom ### ChatRoom
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ----------- | ------------------- | ----------------------------------- |
| `id` | `string` (UUID) | | | `id` | `string` (UUID) | |
| `name` | `string` | Unique room name (case-insensitive) | | `name` | `string` | Unique room name (case-insensitive) |
| `purpose` | `string?` | Room description | | `purpose` | `string?` | Room description |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | Updated on each new message | | `updatedAt` | `string` (ISO 8601) | Updated on each new message |
### ChatMessage ### ChatMessage
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------------ | ------------------- | ----------------------------------- |
| `id` | `string` (UUID) | | | `id` | `string` (UUID) | |
| `roomId` | `string` | FK to ChatRoom.id | | `roomId` | `string` | FK to ChatRoom.id |
| `authorAgentId` | `string` | Agent ID of the author | | `authorAgentId` | `string` | Agent ID of the author |
| `body` | `string` | Message text (supports `@mentions`) | | `body` | `string` | Message text (supports `@mentions`) |
| `replyToMessageId` | `string?` | FK to another ChatMessage.id | | `replyToMessageId` | `string?` | FK to another ChatMessage.id |
| `mentionAgentIds` | `string[]` | Extracted `@mention` agent IDs | | `mentionAgentIds` | `string[]` | Extracted `@mention` agent IDs |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
--- ---
@@ -257,84 +257,84 @@ Single file containing all rooms and messages.
Single file containing an array of all loop records. Single file containing an array of all loop records.
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ----------------------- | --------------------------------------------------- | ------------------------------------------ |
| `id` | `string` | 8-char UUID prefix | | `id` | `string` | 8-char UUID prefix |
| `name` | `string?` | Human-readable name | | `name` | `string?` | Human-readable name |
| `prompt` | `string` | Worker prompt | | `prompt` | `string` | Worker prompt |
| `cwd` | `string` | Working directory | | `cwd` | `string` | Working directory |
| `provider` | `string` | Default provider | | `provider` | `string` | Default provider |
| `model` | `string?` | Default model | | `model` | `string?` | Default model |
| `workerProvider` | `string?` | Override provider for workers | | `workerProvider` | `string?` | Override provider for workers |
| `workerModel` | `string?` | Override model for workers | | `workerModel` | `string?` | Override model for workers |
| `verifierProvider` | `string?` | Override provider for verifiers | | `verifierProvider` | `string?` | Override provider for verifiers |
| `verifierModel` | `string?` | Override model for verifiers | | `verifierModel` | `string?` | Override model for verifiers |
| `verifyPrompt` | `string?` | LLM verification prompt | | `verifyPrompt` | `string?` | LLM verification prompt |
| `verifyChecks` | `string[]` | Shell commands to run as checks | | `verifyChecks` | `string[]` | Shell commands to run as checks |
| `archive` | `boolean` | Whether to archive worker agents after use | | `archive` | `boolean` | Whether to archive worker agents after use |
| `sleepMs` | `number` | Delay between iterations (ms) | | `sleepMs` | `number` | Delay between iterations (ms) |
| `maxIterations` | `number?` | Cap on iterations | | `maxIterations` | `number?` | Cap on iterations |
| `maxTimeMs` | `number?` | Total time budget (ms) | | `maxTimeMs` | `number?` | Total time budget (ms) |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | | | `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | | | `updatedAt` | `string` (ISO 8601) | |
| `startedAt` | `string` (ISO 8601) | | | `startedAt` | `string` (ISO 8601) | |
| `completedAt` | `string?` (ISO 8601) | | | `completedAt` | `string?` (ISO 8601) | |
| `stopRequestedAt` | `string?` (ISO 8601) | | | `stopRequestedAt` | `string?` (ISO 8601) | |
| `iterations` | `LoopIteration[]` | | | `iterations` | `LoopIteration[]` | |
| `logs` | `LoopLogEntry[]` | | | `logs` | `LoopLogEntry[]` | |
| `nextLogSeq` | `number` | Monotonic log sequence counter | | `nextLogSeq` | `number` | Monotonic log sequence counter |
| `activeIteration` | `number?` | Currently executing iteration index | | `activeIteration` | `number?` | Currently executing iteration index |
| `activeWorkerAgentId` | `string?` | Currently running worker agent | | `activeWorkerAgentId` | `string?` | Currently running worker agent |
| `activeVerifierAgentId` | `string?` | Currently running verifier agent | | `activeVerifierAgentId` | `string?` | Currently running verifier agent |
### Nested: LoopIteration ### Nested: LoopIteration
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------------- | --------------------------------------------------- | ------------------------ |
| `index` | `number` | 1-based iteration index | | `index` | `number` | 1-based iteration index |
| `workerAgentId` | `string?` | Agent ID of the worker | | `workerAgentId` | `string?` | Agent ID of the worker |
| `workerStartedAt` | `string` (ISO 8601) | | | `workerStartedAt` | `string` (ISO 8601) | |
| `workerCompletedAt` | `string?` (ISO 8601) | | | `workerCompletedAt` | `string?` (ISO 8601) | |
| `verifierAgentId` | `string?` | Agent ID of the verifier | | `verifierAgentId` | `string?` | Agent ID of the verifier |
| `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | | | `status` | `"running" \| "succeeded" \| "failed" \| "stopped"` | |
| `workerOutcome` | `"completed" \| "failed" \| "canceled"?` | | | `workerOutcome` | `"completed" \| "failed" \| "canceled"?` | |
| `failureReason` | `string?` | | | `failureReason` | `string?` | |
| `verifyChecks` | `LoopVerifyCheckResult[]` | Shell check results | | `verifyChecks` | `LoopVerifyCheckResult[]` | Shell check results |
| `verifyPrompt` | `LoopVerifyPromptResult?` | LLM verification result | | `verifyPrompt` | `LoopVerifyPromptResult?` | LLM verification result |
### Nested: LoopLogEntry ### Nested: LoopLogEntry
| Field | Type | | Field | Type |
|---|---| | ----------- | ---------------------------------------------------- |
| `seq` | `number` (monotonic) | | `seq` | `number` (monotonic) |
| `timestamp` | `string` (ISO 8601) | | `timestamp` | `string` (ISO 8601) |
| `iteration` | `number?` | | `iteration` | `number?` |
| `source` | `"loop" \| "worker" \| "verifier" \| "verify-check"` | | `source` | `"loop" \| "worker" \| "verifier" \| "verify-check"` |
| `level` | `"info" \| "error"` | | `level` | `"info" \| "error"` |
| `text` | `string` | | `text` | `string` |
### Nested: LoopVerifyCheckResult ### Nested: LoopVerifyCheckResult
| Field | Type | | Field | Type |
|---|---| | ------------- | ------------------- |
| `command` | `string` | | `command` | `string` |
| `exitCode` | `number` | | `exitCode` | `number` |
| `passed` | `boolean` | | `passed` | `boolean` |
| `stdout` | `string` | | `stdout` | `string` |
| `stderr` | `string` | | `stderr` | `string` |
| `startedAt` | `string` (ISO 8601) | | `startedAt` | `string` (ISO 8601) |
| `completedAt` | `string` (ISO 8601) | | `completedAt` | `string` (ISO 8601) |
### Nested: LoopVerifyPromptResult ### Nested: LoopVerifyPromptResult
| Field | Type | | Field | Type |
|---|---| | ----------------- | ------------------- |
| `passed` | `boolean` | | `passed` | `boolean` |
| `reason` | `string` | | `reason` | `string` |
| `verifierAgentId` | `string?` | | `verifierAgentId` | `string?` |
| `startedAt` | `string` (ISO 8601) | | `startedAt` | `string` (ISO 8601) |
| `completedAt` | `string` (ISO 8601) | | `completedAt` | `string` (ISO 8601) |
--- ---
@@ -344,15 +344,15 @@ Single file containing an array of all loop records.
Array of project records. Array of project records.
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------- | -------------------- | ------------------------------ |
| `projectId` | `string` | Primary key | | `projectId` | `string` | Primary key |
| `rootPath` | `string` | Filesystem root of the project | | `rootPath` | `string` | Filesystem root of the project |
| `kind` | `"git" \| "non_git"` | | | `kind` | `"git" \| "non_git"` | |
| `displayName` | `string` | | | `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | | | `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp | | `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. Array of workspace records. A workspace is a specific working directory within a project.
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------- | ----------------------------------------------- | ----------------------- |
| `workspaceId` | `string` | Primary key | | `workspaceId` | `string` | Primary key |
| `projectId` | `string` | FK to Project.projectId | | `projectId` | `string` | FK to Project.projectId |
| `cwd` | `string` | Filesystem path | | `cwd` | `string` | Filesystem path |
| `kind` | `"local_checkout" \| "worktree" \| "directory"` | | | `kind` | `"local_checkout" \| "worktree" \| "directory"` | |
| `displayName` | `string` | | | `displayName` | `string` | |
| `createdAt` | `string` (ISO 8601) | | | `createdAt` | `string` (ISO 8601) | |
| `updatedAt` | `string` (ISO 8601) | | | `updatedAt` | `string` (ISO 8601) | |
| `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp | | `archivedAt` | `string?` (ISO 8601) | Soft-delete timestamp |
--- ---
@@ -417,12 +417,12 @@ Stores binary attachment blobs keyed by attachment ID.
### AttachmentMetadata ### AttachmentMetadata
| Field | Type | Description | | Field | Type | Description |
|---|---|---| | ------------- | --------- | ------------------------------ |
| `id` | `string` | Unique attachment ID | | `id` | `string` | Unique attachment ID |
| `mimeType` | `string` | MIME type | | `mimeType` | `string` | MIME type |
| `storageType` | `string` | Storage backend identifier | | `storageType` | `string` | Storage backend identifier |
| `storageKey` | `string` | Key within the storage backend | | `storageKey` | `string` | Key within the storage backend |
| `createdAt` | `number` | Epoch ms | | `createdAt` | `number` | Epoch ms |
| `fileName` | `string?` | Original filename | | `fileName` | `string?` | Original filename |
| `byteSize` | `number?` | Size in bytes | | `byteSize` | `number?` | Size in bytes |
+11 -6
View File
@@ -31,6 +31,7 @@ PASEO_HOME=~/.paseo-blue npm run dev
### Default ports ### Default ports
In the main checkout: In the main checkout:
- Daemon: `localhost:6767` - Daemon: `localhost:6767`
- Expo app: `localhost:8081` - 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: Every `scripts` entry with `"type": "service"` receives these environment variables:
| Variable | Value | | 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>_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_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_URL` | Self alias for `PASEO_SERVICE_<SELF>_URL`. |
| `PASEO_PORT` | Self alias for `PASEO_SERVICE_<SELF>_PORT`. | | `PASEO_PORT` | Self alias for `PASEO_SERVICE_<SELF>_PORT`. |
| `HOST` | Bind host for the service process. | | `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. `<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: Find an agent by ID:
```bash ```bash
find $PASEO_HOME/agents -name "{agent-id}.json" find $PASEO_HOME/agents -name "{agent-id}.json"
``` ```
Find by content: Find by content:
```bash ```bash
rg -l "some title text" $PASEO_HOME/agents/ 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: Get the session ID from the agent JSON (`persistence.sessionId`), then:
**Claude:** **Claude:**
``` ```
~/.claude/projects/{cwd-with-dashes}/{session-id}.jsonl ~/.claude/projects/{cwd-with-dashes}/{session-id}.jsonl
``` ```
**Codex:** **Codex:**
``` ```
~/.codex/sessions/{YYYY}/{MM}/{DD}/rollout-{timestamp}-{session-id}.jsonl ~/.codex/sessions/{YYYY}/{MM}/{DD}/rollout-{timestamp}-{session-id}.jsonl
``` ```
+56 -56
View File
@@ -35,8 +35,8 @@ cat node_modules/material-icon-theme/icons/ICON_NAME.svg
``` ```
3. Add two things to `material-file-icons.ts`: 3. Add two things to `material-file-icons.ts`:
- The SVG string in `SVG_ICONS`: - The SVG string in `SVG_ICONS`:
```ts ```ts
"icon_name": `<svg ...>...</svg>`, "icon_name": `<svg ...>...</svg>`,
``` ```
@@ -52,58 +52,58 @@ cat node_modules/material-icon-theme/icons/ICON_NAME.svg
53 unique icons covering these extensions: 53 unique icons covering these extensions:
| Extension(s) | Icon | | Extension(s) | Icon |
|---|---| | ------------------------------------------ | ----------- |
| `ts` | typescript | | `ts` | typescript |
| `tsx` | react_ts | | `tsx` | react_ts |
| `js` | javascript | | `js` | javascript |
| `jsx` | react | | `jsx` | react |
| `py` | python | | `py` | python |
| `go` | go | | `go` | go |
| `rs` | rust | | `rs` | rust |
| `rb` | ruby | | `rb` | ruby |
| `java` | java | | `java` | java |
| `kt` | kotlin | | `kt` | kotlin |
| `c` | c | | `c` | c |
| `cpp` | cpp | | `cpp` | cpp |
| `h` | h | | `h` | h |
| `hpp` | hpp | | `hpp` | hpp |
| `cs` | csharp | | `cs` | csharp |
| `swift` | swift | | `swift` | swift |
| `dart` | dart | | `dart` | dart |
| `ex`, `exs` | elixir | | `ex`, `exs` | elixir |
| `erl` | erlang | | `erl` | erlang |
| `hs` | haskell | | `hs` | haskell |
| `clj` | clojure | | `clj` | clojure |
| `scala` | scala | | `scala` | scala |
| `ml` | ocaml | | `ml` | ocaml |
| `r` | r | | `r` | r |
| `lua` | lua | | `lua` | lua |
| `zig` | zig | | `zig` | zig |
| `nix` | nix | | `nix` | nix |
| `php` | php | | `php` | php |
| `html` | html | | `html` | html |
| `css` | css | | `css` | css |
| `scss` | sass | | `scss` | sass |
| `less` | less | | `less` | less |
| `json` | json | | `json` | json |
| `yml`, `yaml` | yaml | | `yml`, `yaml` | yaml |
| `xml` | xml | | `xml` | xml |
| `toml` | toml | | `toml` | toml |
| `md`, `markdown` | markdown | | `md`, `markdown` | markdown |
| `sql` | database | | `sql` | database |
| `graphql`, `gql` | graphql | | `graphql`, `gql` | graphql |
| `sh`, `bash` | console | | `sh`, `bash` | console |
| `tf` | terraform | | `tf` | terraform |
| `hcl` | hcl | | `hcl` | hcl |
| `vue` | vue | | `vue` | vue |
| `svelte` | svelte | | `svelte` | svelte |
| `astro` | astro | | `astro` | astro |
| `wasm` | webassembly | | `wasm` | webassembly |
| `svg` | svg | | `svg` | svg |
| `png`, `jpg`, `jpeg`, `gif`, `webp`, `ico` | image | | `png`, `jpg`, `jpeg`, `gif`, `webp`, `ico` | image |
| `txt` | document | | `txt` | document |
| `conf`, `cfg`, `ini` | settings | | `conf`, `cfg`, `ini` | settings |
| `lock` | lock | | `lock` | lock |
| `groovy` | groovy | | `groovy` | groovy |
| `gradle` | gradle | | `gradle` | gradle |
+2 -2
View File
@@ -223,7 +223,7 @@ const styles = StyleSheet.create((theme) => ({
}, },
})); }));
<Animated.View style={[styles.sidebar, animatedStyle]} /> <Animated.View style={[styles.sidebar, animatedStyle]} />;
``` ```
```tsx ```tsx
@@ -244,7 +244,7 @@ const { theme } = useUnistyles();
<Animated.View <Animated.View
style={[staticStyles.sidebar, animatedStyle, { backgroundColor: theme.colors.surfaceSidebar }]} style={[staticStyles.sidebar, animatedStyle, { backgroundColor: theme.colors.surfaceSidebar }]}
/> />;
``` ```
Regular `View` components can safely use Unistyles dynamic styles — the conflict is specific to `Animated.View`. Regular `View` components can safely use Unistyles dynamic styles — the conflict is specific to `Animated.View`.
+2
View File
@@ -30,6 +30,7 @@ Each project opens as a workspace. For git projects, the default workspace is th
### Inside a workspace ### Inside a workspace
A workspace is a flexible canvas: A workspace is a flexible canvas:
- Launch multiple agents side by side in split panes - Launch multiple agents side by side in split panes
- Open terminals alongside agents - Open terminals alongside agents
- Mix and match providers within the same workspace - 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. 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: This architecture means:
- The daemon can run on any machine: laptop, VM, remote server - The daemon can run on any machine: laptop, VM, remote server
- Multiple clients can connect simultaneously - Multiple clients can connect simultaneously
- Agents keep running when you close the app - Agents keep running when you close the app
+14 -7
View File
@@ -58,10 +58,10 @@ type MyProviderClientOptions = {
export class MyProviderACPAgentClient extends ACPAgentClient { export class MyProviderACPAgentClient extends ACPAgentClient {
constructor(options: MyProviderClientOptions) { constructor(options: MyProviderClientOptions) {
super({ super({
provider: "my-provider", // Must match the ID used everywhere else provider: "my-provider", // Must match the ID used everywhere else
logger: options.logger, logger: options.logger,
runtimeSettings: options.runtimeSettings, 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, defaultModes: MY_PROVIDER_MODES,
capabilities: MY_PROVIDER_CAPABILITIES, capabilities: MY_PROVIDER_CAPABILITIES,
}); });
@@ -70,7 +70,7 @@ export class MyProviderACPAgentClient extends ACPAgentClient {
// Override isAvailable() if the provider needs specific auth/env vars // Override isAvailable() if the provider needs specific auth/env vars
override async isAvailable(): Promise<boolean> { override async isAvailable(): Promise<boolean> {
if (!(await super.isAvailable())) { if (!(await super.isAvailable())) {
return false; // Binary not found return false; // Binary not found
} }
return Boolean(process.env["MY_PROVIDER_API_KEY"]); return Boolean(process.env["MY_PROVIDER_API_KEY"]);
} }
@@ -212,8 +212,8 @@ export const agentConfigs = {
provider: "my-provider", provider: "my-provider",
model: "default-model-id", model: "default-model-id",
modes: { modes: {
full: "autonomous", // Mode with no permission prompts full: "autonomous", // Mode with no permission prompts
ask: "default", // Mode that requires permission approval ask: "default", // Mode that requires permission approval
}, },
}, },
} as const satisfies Record<string, AgentTestConfig>; } 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 { interface AgentClient {
readonly provider: AgentProvider; readonly provider: AgentProvider;
readonly capabilities: AgentCapabilityFlags; readonly capabilities: AgentCapabilityFlags;
createSession(config: AgentSessionConfig, launchContext?: AgentLaunchContext): Promise<AgentSession>; createSession(
resumeSession(handle: AgentPersistenceHandle, overrides?: Partial<AgentSessionConfig>, launchContext?: AgentLaunchContext): Promise<AgentSession>; config: AgentSessionConfig,
launchContext?: AgentLaunchContext,
): Promise<AgentSession>;
resumeSession(
handle: AgentPersistenceHandle,
overrides?: Partial<AgentSessionConfig>,
launchContext?: AgentLaunchContext,
): Promise<AgentSession>;
listModels(options?: ListModelsOptions): Promise<AgentModelDefinition[]>; listModels(options?: ListModelsOptions): Promise<AgentModelDefinition[]>;
isAvailable(): Promise<boolean>; isAvailable(): Promise<boolean>;
// Optional: // Optional:
+4 -1
View File
@@ -79,7 +79,9 @@ const realSender: EmailSender = { send: sendgrid.send };
function createTestEmailSender() { function createTestEmailSender() {
const sent: Array<{ to: string; body: string }> = []; const sent: Array<{ to: string; body: string }> = [];
return { return {
send: async (to: string, body: string) => { sent.push({ to, body }); }, send: async (to: string, body: string) => {
sent.push({ to, body });
},
sent, sent,
}; };
} }
@@ -115,6 +117,7 @@ The test output is the source of truth, not your reading of the code.
## Design for testability ## Design for testability
If code isn't testable, refactor it. Signs: If code isn't testable, refactor it. Signs:
- You want to reach for a mock - You want to reach for a mock
- You can't inject a dependency - You can't inject a dependency
- You need to test private internals - You need to test private internals
+21 -23
View File
@@ -19,7 +19,7 @@ The important detail: the automatic native path tracks `props.style`. It does no
Avoid this pattern when the style depends on the theme: Avoid this pattern when the style depends on the theme:
```tsx ```tsx
<ScrollView contentContainerStyle={styles.container} /> <ScrollView contentContainerStyle={styles.container} />;
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
container: { container: {
@@ -39,10 +39,8 @@ Preferred pattern: put themed backgrounds on a normal wrapper view, and keep `co
```tsx ```tsx
<View style={styles.container}> <View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}> <ScrollView contentContainerStyle={styles.contentContainer}>{children}</ScrollView>
{children} </View>;
</ScrollView>
</View>
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
container: { container: {
@@ -66,10 +64,7 @@ import { StyleSheet, withUnistyles } from "react-native-unistyles";
const ThemedScrollView = withUnistyles(ScrollView); const ThemedScrollView = withUnistyles(ScrollView);
<ThemedScrollView <ThemedScrollView style={styles.scrollView} contentContainerStyle={styles.contentContainer} />;
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. `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(); const { theme } = useUnistyles();
<ScrollView <ScrollView
contentContainerStyle={[ contentContainerStyle={[styles.contentContainer, { backgroundColor: theme.colors.surface0 }]}
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. 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: 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 ```css
.unistyles_j2k2iilhfz { flex: 1 1 0%; background-color: var(--colors-surface0); } .unistyles_j2k2iilhfz {
.unistyles_j2k2iilhfz > * { flex: 1 1 0%; background-color: var(--colors-surface0); } 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. 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: Quick confirmation in DevTools console:
```js ```js
[...document.styleSheets].flatMap(s => [...(s.cssRules || [])]) [...document.styleSheets]
.map(r => r.cssText).filter(t => t.includes("unistyles") && t.includes("> *")); .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. 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 ```tsx
const { theme } = useUnistyles(); const { theme } = useUnistyles();
<Text style={[styles.title, { color: theme.colors.foreground }]}> <Text style={[styles.title, { color: theme.colors.foreground }]}>{title}</Text>;
{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. 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 ```tsx
const { theme } = useUnistyles(); 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. 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 #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 #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) - [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< async function loadDaemonClientConstructor(): Promise<
new ( new (config: DaemonClientConfig) => DaemonClientInstance
config: DaemonClientConfig,
) => DaemonClientInstance
> { > {
const repoRoot = path.resolve(__dirname, "../../../../"); const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL( const moduleUrl = pathToFileURL(
+1 -3
View File
@@ -81,9 +81,7 @@ type ArchiveTabDaemonClientConfig = {
}; };
async function loadDaemonClientConstructor(): Promise< async function loadDaemonClientConstructor(): Promise<
new ( new (config: ArchiveTabDaemonClientConfig) => ArchiveTabDaemonClient
config: ArchiveTabDaemonClientConfig,
) => ArchiveTabDaemonClient
> { > {
const repoRoot = path.resolve(__dirname, "../../../../"); const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL( const moduleUrl = pathToFileURL(
+1 -3
View File
@@ -49,9 +49,7 @@ function getDaemonWsUrl(): string {
} }
async function loadDaemonClientConstructor(): Promise< async function loadDaemonClientConstructor(): Promise<
new ( new (config: NewWorkspaceDaemonClientConfig) => NewWorkspaceDaemonClient
config: NewWorkspaceDaemonClientConfig,
) => NewWorkspaceDaemonClient
> { > {
const repoRoot = path.resolve(__dirname, "../../../../"); const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL( const moduleUrl = pathToFileURL(
+1 -3
View File
@@ -63,9 +63,7 @@ type TerminalPerfDaemonClientConfig = {
}; };
async function loadDaemonClientConstructor(): Promise< async function loadDaemonClientConstructor(): Promise<
new ( new (config: TerminalPerfDaemonClientConfig) => TerminalPerfDaemonClient
config: TerminalPerfDaemonClientConfig,
) => TerminalPerfDaemonClient
> { > {
const repoRoot = path.resolve(__dirname, "../../../../"); const repoRoot = path.resolve(__dirname, "../../../../");
const moduleUrl = pathToFileURL( const moduleUrl = pathToFileURL(
@@ -24,4 +24,3 @@ appId: sh.paseo
commands: commands:
- tapOn: - tapOn:
point: "50%,20%" point: "50%,20%"
-1
View File
@@ -3,4 +3,3 @@ appId: sh.paseo
- launchApp: - launchApp:
clearState: true clearState: true
- runFlow: flows/dev-client.yaml - runFlow: flows/dev-client.yaml
+1 -1
View File
@@ -1,8 +1,8 @@
{ {
"name": "@getpaseo/app", "name": "@getpaseo/app",
"main": "index.ts",
"version": "0.1.62", "version": "0.1.62",
"private": true, "private": true,
"main": "index.ts",
"scripts": { "scripts": {
"start": "APP_VARIANT=development expo start", "start": "APP_VARIANT=development expo start",
"reset-project": "node ./scripts/reset-project.js", "reset-project": "node ./scripts/reset-project.js",
+15 -14
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="%LANG_ISO_CODE%"> <html lang="%LANG_ISO_CODE%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -21,8 +21,9 @@
} }
/* Prevent white flash before React mounts */ /* Prevent white flash before React mounts */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html, body { html,
background-color: #181B1A; body {
background-color: #181b1a;
} }
} }
/* These styles make the root element full-height */ /* These styles make the root element full-height */
@@ -38,17 +39,17 @@
input, input,
textarea, textarea,
select, select,
[role='button'], [role="button"],
[role='link'], [role="link"],
[role='textbox'], [role="textbox"],
[role='combobox'], [role="combobox"],
[role='tab'], [role="tab"],
[role='switch'], [role="switch"],
[role='checkbox'], [role="checkbox"],
[role='slider'], [role="slider"],
[role='menuitem'], [role="menuitem"],
[tabindex], [tabindex],
[contenteditable='true'] { [contenteditable="true"] {
-webkit-app-region: no-drag !important; -webkit-app-region: no-drag !important;
} }
@@ -59,7 +60,7 @@
outline: none; outline: none;
} }
*:focus-visible { *:focus-visible {
outline: 2px solid #20744A; outline: 2px solid #20744a;
outline-offset: 2px; outline-offset: 2px;
} }
</style> </style>
+2 -3
View File
@@ -7,9 +7,8 @@ let attachmentStorePromise: Promise<AttachmentStore> | null = null;
async function createAttachmentStore(): Promise<AttachmentStore> { async function createAttachmentStore(): Promise<AttachmentStore> {
if (isWeb) { if (isWeb) {
if (isElectronRuntime()) { if (isElectronRuntime()) {
const { createDesktopAttachmentStore } = await import( const { createDesktopAttachmentStore } =
"../desktop/attachments/desktop-attachment-store" await import("../desktop/attachments/desktop-attachment-store");
);
return createDesktopAttachmentStore(); return createDesktopAttachmentStore();
} }
@@ -82,13 +82,7 @@ vi.mock("expo-image", () => ({
vi.mock("react-native", async () => { vi.mock("react-native", async () => {
const actual = await vi.importActual<Record<string, unknown>>("react-native"); const actual = await vi.importActual<Record<string, unknown>>("react-native");
const Modal = ({ const Modal = ({ visible = true, children }: { visible?: boolean; children?: React.ReactNode }) =>
visible = true,
children,
}: {
visible?: boolean;
children?: React.ReactNode;
}) =>
visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null; visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null;
return { ...actual, Modal }; return { ...actual, Modal };
}); });
@@ -285,13 +285,7 @@ vi.mock("expo-image", () => ({
vi.mock("react-native", async () => { vi.mock("react-native", async () => {
const actual = await vi.importActual<Record<string, unknown>>("react-native"); const actual = await vi.importActual<Record<string, unknown>>("react-native");
const Modal = ({ const Modal = ({ visible = true, children }: { visible?: boolean; children?: React.ReactNode }) =>
visible = true,
children,
}: {
visible?: boolean;
children?: React.ReactNode;
}) =>
visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null; visible ? React.createElement("div", { "data-testid": "lightbox-modal" }, children) : null;
return { ...actual, Modal }; return { ...actual, Modal };
}); });
@@ -1954,8 +1954,8 @@ export function SidebarWorkspaceList({
select: toProjectIconDataUri, select: toProjectIconDataUri,
enabled: Boolean( enabled: Boolean(
getHostRuntimeStore().getClient(request.serverId) && getHostRuntimeStore().getClient(request.serverId) &&
isHostRuntimeConnected(getHostRuntimeStore().getSnapshot(request.serverId)) && isHostRuntimeConnected(getHostRuntimeStore().getSnapshot(request.serverId)) &&
request.cwd, request.cwd,
), ),
staleTime: Infinity, staleTime: Infinity,
gcTime: 1000 * 60 * 60, gcTime: 1000 * 60 * 60,
+11 -12
View File
@@ -134,18 +134,17 @@ interface SplitNodeViewProps extends Omit<SplitContainerProps, "layout"> {
tabDropPreview: TabDropPreview | null; tabDropPreview: TabDropPreview | null;
} }
interface SplitPaneViewProps interface SplitPaneViewProps extends Omit<
extends Omit< SplitNodeViewProps,
SplitNodeViewProps, | "node"
| "node" | "workspaceKey"
| "workspaceKey" | "focusedPaneId"
| "focusedPaneId" | "activeDragTabId"
| "activeDragTabId" | "showDropZones"
| "showDropZones" | "dropPreview"
| "dropPreview" | "onMoveTabToPane"
| "onMoveTabToPane" | "onResizeSplit"
| "onResizeSplit" > {
> {
pane: SplitPane; pane: SplitPane;
uiTabs: WorkspaceTab[]; uiTabs: WorkspaceTab[];
isFocused: boolean; isFocused: boolean;
@@ -237,23 +237,24 @@ describe("providers snapshot hook cache scope", () => {
it.each([ it.each([
{ name: "missing", entries: [] }, { name: "missing", entries: [] },
{ name: "loading", entries: [codexEntry("loading")] }, { name: "loading", entries: [codexEntry("loading")] },
])("ensures a selected provider snapshot on selector open when it is $name", async ({ ])(
entries, "ensures a selected provider snapshot on selector open when it is $name",
}) => { async ({ entries }) => {
enableProvidersSnapshot(); enableProvidersSnapshot();
mockClient.getProvidersSnapshot mockClient.getProvidersSnapshot
.mockResolvedValueOnce(providersSnapshot(entries)) .mockResolvedValueOnce(providersSnapshot(entries))
.mockResolvedValueOnce(providersSnapshot([codexEntry("ready", [readyCodexModel])])); .mockResolvedValueOnce(providersSnapshot([codexEntry("ready", [readyCodexModel])]));
const { result } = renderProvidersSnapshotHook(); const { result } = renderProvidersSnapshotHook();
await waitForSnapshotEntries(result, entries); await waitForSnapshotEntries(result, entries);
await openSelectorForSelectedProvider(result); await openSelectorForSelectedProvider(result);
await waitForSnapshotReads(2); await waitForSnapshotReads(2);
expect(mockClient.getProvidersSnapshot).toHaveBeenLastCalledWith({}); expect(mockClient.getProvidersSnapshot).toHaveBeenLastCalledWith({});
expect(mockClient.refreshProvidersSnapshot).not.toHaveBeenCalled(); 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 () => { it("does not ensure a selected provider snapshot on selector open when the provider is ready with no models", async () => {
enableProvidersSnapshot(); enableProvidersSnapshot();
+1 -1
View File
@@ -51,7 +51,7 @@ export function resolveKeyboardFocusScope(input: {
candidates.some((element) => candidates.some((element) =>
Boolean( Boolean(
element.closest("[data-testid='command-center-panel']") || 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", ({ it.each(matchingCases)(
event, "$name",
context, ({ event, context, action, payload, preventDefault, stopPropagation }) => {
action, expectShortcutResolution({
payload, event,
preventDefault, context,
stopPropagation, action,
}) => { ...(payload !== undefined ? { payload } : {}),
expectShortcutResolution({ ...(preventDefault !== undefined ? { preventDefault } : {}),
event, ...(stopPropagation !== undefined ? { stopPropagation } : {}),
context, });
action, },
...(payload !== undefined ? { payload } : {}), );
...(preventDefault !== undefined ? { preventDefault } : {}),
...(stopPropagation !== undefined ? { stopPropagation } : {}),
});
});
const nonMatchingCases: NonMatchingShortcutCase[] = [ const nonMatchingCases: NonMatchingShortcutCase[] = [
{ {
+2 -7
View File
@@ -491,10 +491,7 @@ export class HostRuntimeController {
private probeRequestVersion = 0; private probeRequestVersion = 0;
private probeCycleInFlight: Promise<void> | null = null; private probeCycleInFlight: Promise<void> | null = null;
constructor(input: { constructor(input: { host: HostProfile; deps?: HostRuntimeControllerDeps }) {
host: HostProfile;
deps?: HostRuntimeControllerDeps;
}) {
this.host = input.host; this.host = input.host;
this.deps = input.deps ?? createDefaultDeps(); this.deps = input.deps ?? createDefaultDeps();
this.connectionMachineState = { this.connectionMachineState = {
@@ -1155,9 +1152,7 @@ export class HostRuntimeStore {
private storageLoaded = false; private storageLoaded = false;
private bootstrapAttempted = false; private bootstrapAttempted = false;
constructor(input?: { constructor(input?: { deps?: HostRuntimeControllerDeps }) {
deps?: HostRuntimeControllerDeps;
}) {
this.deps = input?.deps ?? createDefaultDeps(); this.deps = input?.deps ?? createDefaultDeps();
} }
@@ -230,11 +230,11 @@ export function WorkspaceDraftAgentTab({
const isReadyForPendingAutoSubmit = Boolean( const isReadyForPendingAutoSubmit = Boolean(
pendingAutoSubmit && pendingAutoSubmit &&
draftInput.isHydrated && draftInput.isHydrated &&
workspaceDirectory && workspaceDirectory &&
client && client &&
!isSubmitting && !isSubmitting &&
!composerState.isModelLoading, !composerState.isModelLoading,
); );
const autoSubmitKeyRef = useRef<string | null>(null); const autoSubmitKeyRef = useRef<string | null>(null);
useEffect(() => { useEffect(() => {
@@ -36,10 +36,10 @@ export function hasMeaningfulToolCallDetail(detail: ToolCallDetail | undefined):
case "search": case "search":
return Boolean( return Boolean(
detail.query.trim().length > 0 || detail.query.trim().length > 0 ||
detail.content || detail.content ||
(detail.filePaths && detail.filePaths.length > 0) || (detail.filePaths && detail.filePaths.length > 0) ||
(detail.webResults && detail.webResults.length > 0) || (detail.webResults && detail.webResults.length > 0) ||
(detail.annotations && detail.annotations.length > 0), (detail.annotations && detail.annotations.length > 0),
); );
case "fetch": case "fetch":
return Boolean(detail.url || detail.result || detail.codeText); return Boolean(detail.url || detail.result || detail.codeText);
+4 -4
View File
@@ -2,17 +2,17 @@
"name": "@getpaseo/cli", "name": "@getpaseo/cli",
"version": "0.1.62", "version": "0.1.62",
"description": "Paseo CLI - control your AI coding agents from the command line", "description": "Paseo CLI - control your AI coding agents from the command line",
"type": "module", "bin": {
"paseo": "bin/paseo"
},
"files": [ "files": [
"bin", "bin",
"dist" "dist"
], ],
"type": "module",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"bin": {
"paseo": "bin/paseo"
},
"scripts": { "scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false", "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build", "prepack": "npm run build",
+11 -11
View File
@@ -3,6 +3,16 @@
"version": "0.1.62", "version": "0.1.62",
"private": true, "private": true,
"description": "Paseo desktop app (Electron wrapper)", "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", "main": "dist/main.js",
"scripts": { "scripts": {
"build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml", "build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml",
@@ -27,15 +37,5 @@
"typescript": "5.9.3", "typescript": "5.9.3",
"unzip-crx-3": "^0.2.0", "unzip-crx-3": "^0.2.0",
"wait-on": "8.0.5" "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"
} }
+11 -11
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience 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 overall community
Examples of unacceptable behavior include: 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 advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission 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 professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **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. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within
@@ -18,18 +18,20 @@ This example showcases the basic functions of the expo-two-way-audio module:
In the `examples/basic-usage` directory: In the `examples/basic-usage` directory:
1. Install dependencies: 1. Install dependencies:
```sh
npm install ```sh
``` npm install
```
2. Run the app: 2. Run the app:
```sh
# Run iOS
npm run ios
# Run Android ```sh
npm run android # Run iOS
``` npm run ios
# Run Android
npm run android
```
## Notes ## Notes
@@ -1,6 +1,7 @@
{ {
"name": "expo-two-way-audio-example", "name": "expo-two-way-audio-example",
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "expo/AppEntry.js", "main": "expo/AppEntry.js",
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
@@ -11,17 +12,16 @@
"dependencies": { "dependencies": {
"buffer": "^6.0.3", "buffer": "^6.0.3",
"expo": "^52.0.0", "expo": "^52.0.0",
"react": "18.3.1", "expo-build-properties": "~0.13.1",
"react-native": "0.76.2",
"expo-dev-client": "~5.0.3", "expo-dev-client": "~5.0.3",
"expo-build-properties": "~0.13.1" "react": "18.3.1",
"react-native": "0.76.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@types/react": "~18.3.12", "@types/react": "~18.3.12",
"typescript": "^5.1.3" "typescript": "^5.1.3"
}, },
"private": true,
"expo": { "expo": {
"autolinking": { "autolinking": {
"nativeModulesDir": "../.." "nativeModulesDir": "../.."
@@ -1,6 +1,7 @@
{ {
"name": "expo-two-way-audio-flow-example", "name": "expo-two-way-audio-flow-example",
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "expo/AppEntry.js", "main": "expo/AppEntry.js",
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
@@ -25,7 +26,6 @@
"@types/react": "~18.3.12", "@types/react": "~18.3.12",
"typescript": "^5.1.3" "typescript": "^5.1.3"
}, },
"private": true,
"expo": { "expo": {
"autolinking": { "autolinking": {
"nativeModulesDir": "../.." "nativeModulesDir": "../.."
+6 -6
View File
@@ -1,10 +1,10 @@
{ {
"name": "@getpaseo/highlight", "name": "@getpaseo/highlight",
"version": "0.1.62", "version": "0.1.62",
"files": [
"dist"
],
"type": "module", "type": "module",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"exports": { "exports": {
@@ -14,9 +14,9 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"files": [ "publishConfig": {
"dist" "access": "public"
], },
"scripts": { "scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false", "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build", "prepack": "npm run build",
+5 -5
View File
@@ -2,14 +2,11 @@
"name": "@getpaseo/relay", "name": "@getpaseo/relay",
"version": "0.1.62", "version": "0.1.62",
"description": "Paseo relay for bridging daemon and client connections", "description": "Paseo relay for bridging daemon and client connections",
"type": "module",
"publishConfig": {
"access": "public"
},
"types": "./dist/index.d.ts",
"files": [ "files": [
"dist" "dist"
], ],
"type": "module",
"types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -30,6 +27,9 @@
"default": "./src/cloudflare-adapter.ts" "default": "./src/cloudflare-adapter.ts"
} }
}, },
"publishConfig": {
"access": "public"
},
"scripts": { "scripts": {
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false", "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
"prepack": "npm run build", "prepack": "npm run build",
+146 -139
View File
@@ -199,169 +199,176 @@ async function stopRelayProcess(relayProcess: ChildProcess): Promise<void> {
} }
}, SHUTDOWN_TIMEOUT_MS); }, SHUTDOWN_TIMEOUT_MS);
it("full flow: daemon and client exchange encrypted messages through relay", { it(
timeout: 90_000, "full flow: daemon and client exchange encrypted messages through relay",
}, async () => { {
const serverId = "test-session-" + Date.now(); timeout: 90_000,
const connectionId = "clt_test_" + Date.now() + "_" + Math.random().toString(36).slice(2); },
async () => {
const serverId = "test-session-" + Date.now();
const connectionId = "clt_test_" + Date.now() + "_" + Math.random().toString(36).slice(2);
// === DAEMON SIDE === // === DAEMON SIDE ===
// Generate keypair (public key goes in QR) // Generate keypair (public key goes in QR)
const daemonKeyPair = await generateKeyPair(); const daemonKeyPair = await generateKeyPair();
const daemonPubKeyB64 = await exportPublicKey(daemonKeyPair.publicKey); 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 // Daemon connects to relay as "server" control role
const daemonControlWs = new WebSocket( const daemonControlWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&v=2`, `ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&v=2`,
); );
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
daemonControlWs.on("open", resolve); daemonControlWs.on("open", resolve);
daemonControlWs.on("error", reject); daemonControlWs.on("error", reject);
}); });
// === CLIENT SIDE === // === CLIENT SIDE ===
// Client scans QR, gets daemon's public key and session ID // Client scans QR, gets daemon's public key and session ID
// Client generates own keypair // Client generates own keypair
const clientKeyPair = await generateKeyPair(); const clientKeyPair = await generateKeyPair();
const clientPubKeyB64 = await exportPublicKey(clientKeyPair.publicKey); const clientPubKeyB64 = await exportPublicKey(clientKeyPair.publicKey);
// Client imports daemon's public key and derives shared secret // Client imports daemon's public key and derives shared secret
const daemonPubKeyOnClient = await importPublicKey(daemonPubKeyB64); const daemonPubKeyOnClient = await importPublicKey(daemonPubKeyB64);
const clientSharedKey = await deriveSharedKey(clientKeyPair.secretKey, daemonPubKeyOnClient); const clientSharedKey = await deriveSharedKey(clientKeyPair.secretKey, daemonPubKeyOnClient);
const waitForClientSeen = new Promise<void>((resolve, reject) => { const waitForClientSeen = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("timed out waiting for connected")), 5000); const timeout = setTimeout(
const onMessage = (raw: unknown) => { () => reject(new Error("timed out waiting for connected")),
try { 5000,
const text = );
typeof raw === "string" const onMessage = (raw: unknown) => {
? raw try {
: raw && typeof (raw as any).toString === "function" const text =
? (raw as any).toString() typeof raw === "string"
: ""; ? raw
const msg = JSON.parse(text); : raw && typeof (raw as any).toString === "function"
if (msg?.type === "connected" && msg.connectionId === connectionId) { ? (raw as any).toString()
clearTimeout(timeout); : "";
daemonControlWs.off("message", onMessage); const msg = JSON.parse(text);
resolve(); if (msg?.type === "connected" && msg.connectionId === connectionId) {
return; 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" && daemonControlWs.on("message", onMessage);
Array.isArray(msg.connectionIds) && });
msg.connectionIds.includes(connectionId)
) {
clearTimeout(timeout);
daemonControlWs.off("message", onMessage);
resolve();
}
} catch {
// ignore
}
};
daemonControlWs.on("message", onMessage);
});
// Client connects to relay as "client" role (must include connectionId) // Client connects to relay as "client" role (must include connectionId)
const clientWs = new WebSocket( const clientWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=client&connectionId=${connectionId}&v=2`, `ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=client&connectionId=${connectionId}&v=2`,
); );
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
clientWs.on("open", resolve); clientWs.on("open", resolve);
clientWs.on("error", reject); clientWs.on("error", reject);
}); });
await waitForClientSeen; await waitForClientSeen;
const daemonWs = new WebSocket( const daemonWs = new WebSocket(
`ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&connectionId=${connectionId}&v=2`, `ws://127.0.0.1:${relayPort}/ws?serverId=${serverId}&role=server&connectionId=${connectionId}&v=2`,
); );
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
daemonWs.on("open", resolve); daemonWs.on("open", resolve);
daemonWs.on("error", reject); daemonWs.on("error", reject);
}); });
// Client sends hello with its public key (this message is NOT encrypted - it's the handshake) // Client sends hello with its public key (this message is NOT encrypted - it's the handshake)
const helloMsg = JSON.stringify({ type: "hello", key: clientPubKeyB64 }); const helloMsg = JSON.stringify({ type: "hello", key: clientPubKeyB64 });
clientWs.send(helloMsg); clientWs.send(helloMsg);
// === DAEMON RECEIVES HELLO === // === DAEMON RECEIVES HELLO ===
const daemonReceivedHello = await new Promise<string>((resolve) => { const daemonReceivedHello = await new Promise<string>((resolve) => {
daemonWs.once("message", (data) => resolve(data.toString())); daemonWs.once("message", (data) => resolve(data.toString()));
}); });
const hello = JSON.parse(daemonReceivedHello); const hello = JSON.parse(daemonReceivedHello);
expect(hello.type).toBe("hello"); expect(hello.type).toBe("hello");
expect(hello.key).toBe(clientPubKeyB64); expect(hello.key).toBe(clientPubKeyB64);
// Daemon imports client's public key and derives shared secret // Daemon imports client's public key and derives shared secret
const clientPubKeyOnDaemon = await importPublicKey(hello.key); const clientPubKeyOnDaemon = await importPublicKey(hello.key);
const daemonSharedKey = await deriveSharedKey(daemonKeyPair.secretKey, clientPubKeyOnDaemon); 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 // Daemon sends encrypted "ready" message
const readyPlaintext = JSON.stringify({ type: "ready" }); const readyPlaintext = JSON.stringify({ type: "ready" });
const readyCiphertext = await encrypt(daemonSharedKey, readyPlaintext); const readyCiphertext = await encrypt(daemonSharedKey, readyPlaintext);
daemonWs.send(Buffer.from(readyCiphertext)); daemonWs.send(Buffer.from(readyCiphertext));
// Client receives and decrypts // Client receives and decrypts
const clientReceivedReady = await new Promise<Buffer>((resolve) => { const clientReceivedReady = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer)); clientWs.once("message", (data) => resolve(data as Buffer));
}); });
const decryptedReady = await decrypt( const decryptedReady = await decrypt(
clientSharedKey, clientSharedKey,
clientReceivedReady.buffer.slice( clientReceivedReady.buffer.slice(
clientReceivedReady.byteOffset, clientReceivedReady.byteOffset,
clientReceivedReady.byteOffset + clientReceivedReady.byteLength, clientReceivedReady.byteOffset + clientReceivedReady.byteLength,
), ),
); );
expect(JSON.parse(decryptedReady as string)).toEqual({ type: "ready" }); expect(JSON.parse(decryptedReady as string)).toEqual({ type: "ready" });
// Client sends encrypted message // Client sends encrypted message
const clientMessage = "Hello from client!"; const clientMessage = "Hello from client!";
const clientCiphertext = await encrypt(clientSharedKey, clientMessage); const clientCiphertext = await encrypt(clientSharedKey, clientMessage);
clientWs.send(Buffer.from(clientCiphertext)); clientWs.send(Buffer.from(clientCiphertext));
// Daemon receives and decrypts // Daemon receives and decrypts
const daemonReceivedMsg = await new Promise<Buffer>((resolve) => { const daemonReceivedMsg = await new Promise<Buffer>((resolve) => {
daemonWs.once("message", (data) => resolve(data as Buffer)); daemonWs.once("message", (data) => resolve(data as Buffer));
}); });
const decryptedClientMsg = await decrypt( const decryptedClientMsg = await decrypt(
daemonSharedKey, daemonSharedKey,
daemonReceivedMsg.buffer.slice( daemonReceivedMsg.buffer.slice(
daemonReceivedMsg.byteOffset, daemonReceivedMsg.byteOffset,
daemonReceivedMsg.byteOffset + daemonReceivedMsg.byteLength, daemonReceivedMsg.byteOffset + daemonReceivedMsg.byteLength,
), ),
); );
expect(decryptedClientMsg).toBe(clientMessage); expect(decryptedClientMsg).toBe(clientMessage);
// Daemon sends encrypted response // Daemon sends encrypted response
const daemonMessage = "Hello from daemon!"; const daemonMessage = "Hello from daemon!";
const daemonCiphertext = await encrypt(daemonSharedKey, daemonMessage); const daemonCiphertext = await encrypt(daemonSharedKey, daemonMessage);
daemonWs.send(Buffer.from(daemonCiphertext)); daemonWs.send(Buffer.from(daemonCiphertext));
// Client receives and decrypts // Client receives and decrypts
const clientReceivedMsg = await new Promise<Buffer>((resolve) => { const clientReceivedMsg = await new Promise<Buffer>((resolve) => {
clientWs.once("message", (data) => resolve(data as Buffer)); clientWs.once("message", (data) => resolve(data as Buffer));
}); });
const decryptedDaemonMsg = await decrypt( const decryptedDaemonMsg = await decrypt(
clientSharedKey, clientSharedKey,
clientReceivedMsg.buffer.slice( clientReceivedMsg.buffer.slice(
clientReceivedMsg.byteOffset, clientReceivedMsg.byteOffset,
clientReceivedMsg.byteOffset + clientReceivedMsg.byteLength, clientReceivedMsg.byteOffset + clientReceivedMsg.byteLength,
), ),
); );
expect(decryptedDaemonMsg).toBe(daemonMessage); expect(decryptedDaemonMsg).toBe(daemonMessage);
// Cleanup // Cleanup
daemonWs.close(); daemonWs.close();
clientWs.close(); clientWs.close();
}); },
);
it("relay only sees opaque bytes after handshake", { timeout: 90_000 }, async () => { it("relay only sees opaque bytes after handshake", { timeout: 90_000 }, async () => {
const serverId = "opaque-test-" + Date.now(); const serverId = "opaque-test-" + Date.now();
+1 -5
View File
@@ -1,8 +1,4 @@
export { export { createClientChannel, createDaemonChannel, EncryptedChannel } from "./encrypted-channel.js";
createClientChannel,
createDaemonChannel,
EncryptedChannel,
} from "./encrypted-channel.js";
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js"; export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
export { export {
+2 -9
View File
@@ -1,7 +1,4 @@
export type { export type { ConnectionRole, RelaySessionAttachment } from "./types.js";
ConnectionRole,
RelaySessionAttachment,
} from "./types.js";
export { export {
generateKeyPair, generateKeyPair,
@@ -12,9 +9,5 @@ export {
decrypt, decrypt,
} from "./crypto.js"; } from "./crypto.js";
export { export { createClientChannel, createDaemonChannel, EncryptedChannel } from "./encrypted-channel.js";
createClientChannel,
createDaemonChannel,
EncryptedChannel,
} from "./encrypted-channel.js";
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js"; export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
+1 -3
View File
@@ -3,9 +3,7 @@ account_id = "10ed39a1dbf316e30abd0c409bed40d6"
main = "src/cloudflare-adapter.ts" main = "src/cloudflare-adapter.ts"
compatibility_date = "2024-12-01" compatibility_date = "2024-12-01"
routes = [ routes = [{ pattern = "relay.paseo.sh", custom_domain = true }]
{ pattern = "relay.paseo.sh", custom_domain = true }
]
[observability] [observability]
enabled = true enabled = true
+31 -10
View File
@@ -11,6 +11,7 @@ Paseo is a mobile + CLI app for monitoring and controlling local AI coding agent
## Build / Lint / Test Commands ## Build / Lint / Test Commands
### Root (monorepo) ### Root (monorepo)
```bash ```bash
npm run dev # Start daemon + Expo in Tmux npm run dev # Start daemon + Expo in Tmux
npm run build:daemon # Build: highlight + relay + server + cli 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`) ### Server package (`packages/server`)
```bash ```bash
npm run dev # Start dev daemon (tsx watch) npm run dev # Start dev daemon (tsx watch)
npm run build # Build lib + scripts to dist/ npm run build # Build lib + scripts to dist/
@@ -45,6 +47,7 @@ npm run test:ui # Vitest UI at localhost:51204
``` ```
### Other useful commands ### Other useful commands
```bash ```bash
npm run build --workspace=@getpaseo/relay # Rebuild relay before daemon npm run build --workspace=@getpaseo/relay # Rebuild relay before daemon
npm run build --workspace=@getpaseo/server # Rebuild server npm run build --workspace=@getpaseo/server # Rebuild server
@@ -58,13 +61,22 @@ npm run cli -- daemon status # Check daemon status
## Code Style ## Code Style
### Biome (formatting only, no linting) ### Biome (formatting only, no linting)
```json ```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 ### TypeScript
- **Fully strict** — no `any`, no implicit `any` - **Fully strict** — no `any`, no implicit `any`
- **`interface`** over `type`** when possible - **`interface`** over `type`\*\* when possible
- **`function` declarations** over arrow function assignments - **`function` declarations** over arrow function assignments
- **Named types** — no complex inline types in public signatures - **Named types** — no complex inline types in public signatures
- **Object parameters** — use single object param when >1 argument - **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` - `noUnusedLocals: true`, `noUnusedParameters: true`, `noFallthroughCasesInSwitch: true`
### Imports ### Imports
- Use path alias `@server/*` in server package (maps to `./src/`) - Use path alias `@server/*` in server package (maps to `./src/`)
- No barrel `index.ts` re-exports — they create unnecessary indirection - No barrel `index.ts` re-exports — they create unnecessary indirection
### Naming ### Naming
- Files: `kebab-case.ts` named after the main export (`create-tool-call.ts`) - Files: `kebab-case.ts` named after the main export (`create-tool-call.ts`)
- Tests: collocated with implementation (`thing.test.ts`) - Tests: collocated with implementation (`thing.test.ts`)
- No prefixes like `RpcX`, `DbX`, `UiX` — keep one canonical type per concept - No prefixes like `RpcX`, `DbX`, `UiX` — keep one canonical type per concept
### Error Handling ### Error Handling
- **Fail explicitly** — throw instead of silently returning defaults - **Fail explicitly** — throw instead of silently returning defaults
- **Typed domain errors** — extend `Error` with structured metadata - **Typed domain errors** — extend `Error` with structured metadata
@@ -97,10 +112,16 @@ class TimeoutError extends Error {
``` ```
### State Design ### State Design
Discriminated unions over bags of booleans/optionals: Discriminated unions over bags of booleans/optionals:
```typescript ```typescript
// Bad // Bad
interface FetchState { isLoading: boolean; error?: Error; data?: Data } interface FetchState {
isLoading: boolean;
error?: Error;
data?: Data;
}
// Good // Good
type FetchState = type FetchState =
@@ -168,10 +189,10 @@ npm run db:query -- "SELECT * FROM agent_timeline_rows..."
## Relevant Docs ## Relevant Docs
| File | What it covers | | File | What it covers |
|---|---| | ---------------------------------------------------------- | ------------------------------------------------ |
| [../CLAUDE.md](../CLAUDE.md) | Repository overview, critical rules, quick start | | [../CLAUDE.md](../CLAUDE.md) | Repository overview, critical rules, quick start |
| [../docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) | System design, WebSocket protocol, data flow | | [../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/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 | | [../docs/TESTING.md](../docs/TESTING.md) | TDD workflow, determinism, real deps over mocks |
| [../SECURITY.md](../SECURITY.md) | Relay threat model, E2E encryption | | [../SECURITY.md](../SECURITY.md) | Relay threat model, E2E encryption |
+4
View File
@@ -49,15 +49,18 @@ npm start
## Project Status ## Project Status
**✅ Completed** (Phases 1-2): **✅ Completed** (Phases 1-2):
- Package setup and configuration - Package setup and configuration
- Express server with WebSocket - Express server with WebSocket
- React UI with Vite - React UI with Vite
- WebSocket client with ping/pong testing - WebSocket client with ping/pong testing
**⏳ In Progress** (Phase 3): **⏳ In Progress** (Phase 3):
- Terminal control (tmux integration) - Terminal control (tmux integration)
**📋 Planned** (Phases 4-9): **📋 Planned** (Phases 4-9):
- LLM integration (OpenAI GPT-4) - LLM integration (OpenAI GPT-4)
- Agent orchestrator - Agent orchestrator
- Speech-to-Text (Deepgram) - Speech-to-Text (Deepgram)
@@ -95,6 +98,7 @@ PASEO_HOME=~/.paseo-blue PASEO_LISTEN=127.0.0.1:7777 npm run dev
## Testing ## Testing
Currently manual testing via: Currently manual testing via:
1. Start servers: `npm run dev` 1. Start servers: `npm run dev`
2. Open http://localhost:5173 2. Open http://localhost:5173
3. Test WebSocket connection (green status indicator) 3. Test WebSocket connection (green status indicator)
+5 -5
View File
@@ -2,11 +2,6 @@
"name": "@getpaseo/server", "name": "@getpaseo/server",
"version": "0.1.62", "version": "0.1.62",
"description": "Paseo backend server", "description": "Paseo backend server",
"type": "module",
"publishConfig": {
"access": "public"
},
"types": "./dist/server/server/exports.d.ts",
"files": [ "files": [
"dist/server", "dist/server",
"dist/src", "dist/src",
@@ -15,6 +10,8 @@
"README.md", "README.md",
".env.example" ".env.example"
], ],
"type": "module",
"types": "./dist/server/server/exports.d.ts",
"exports": { "exports": {
".": { ".": {
"types": "./dist/server/server/exports.d.ts", "types": "./dist/server/server/exports.d.ts",
@@ -27,6 +24,9 @@
"default": "./dist/server/utils/tool-call-parsers.js" "default": "./dist/server/utils/tool-call-parsers.js"
} }
}, },
"publishConfig": {
"access": "public"
},
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development node --import tsx scripts/dev-runner.ts", "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", "dev:tsx": "cross-env NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' src/server/index.ts",
+4 -5
View File
@@ -232,11 +232,10 @@ export interface CreateAgentRequestOptions extends AgentConfigOverrides {
labels?: Record<string, string>; labels?: Record<string, string>;
} }
export interface CreatePaseoWorktreeInput export interface CreatePaseoWorktreeInput extends Pick<
extends Pick< CreatePaseoWorktreeRequest,
CreatePaseoWorktreeRequest, "cwd" | "worktreeSlug" | "attachments" | "refName" | "action" | "githubPrNumber"
"cwd" | "worktreeSlug" | "attachments" | "refName" | "action" | "githubPrNumber" > {}
> {}
type CheckoutStatusPayload = CheckoutStatusResponse["payload"]; type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
type SubscribeCheckoutDiffPayload = Extract< type SubscribeCheckoutDiffPayload = Extract<
@@ -130,9 +130,7 @@ function buildZodValidator<T>(schema: z.ZodTypeAny, schemaName: string): SchemaV
function buildJsonSchemaValidator<T>(schema: JsonSchema): SchemaValidator<T> { function buildJsonSchemaValidator<T>(schema: JsonSchema): SchemaValidator<T> {
const AjvConstructor = Ajv as unknown as { const AjvConstructor = Ajv as unknown as {
new ( new (options?: AjvOptions): {
options?: AjvOptions,
): {
compile: (input: JsonSchema) => ((value: unknown) => boolean) & { compile: (input: JsonSchema) => ((value: unknown) => boolean) & {
errors?: ErrorObject[] | null; errors?: ErrorObject[] | null;
}; };
@@ -1203,9 +1203,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
? ( ? (
await Promise.all( await Promise.all(
terminalManager.listDirectories().map(async (directory) => terminalManager.listDirectories().map(async (directory) =>
( (await terminalManager.getTerminals(directory)).map((terminal) => ({
await terminalManager.getTerminals(directory)
).map((terminal) => ({
id: terminal.id, id: terminal.id,
name: terminal.name, name: terminal.name,
cwd: terminal.cwd, cwd: terminal.cwd,
@@ -8,4 +8,3 @@
* This file is kept as a stub to document the migration. * This file is kept as a stub to document the migration.
* It can be deleted once the refactoring is confirmed working. * 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 activeSidechains = new Map<string, SubAgentActivityState>();
private readonly getToolInput: (toolUseId: string) => AgentMetadata | null | undefined; private readonly getToolInput: (toolUseId: string) => AgentMetadata | null | undefined;
constructor(input: { constructor(input: { getToolInput: (toolUseId: string) => AgentMetadata | null | undefined }) {
getToolInput: (toolUseId: string) => AgentMetadata | null | undefined;
}) {
this.getToolInput = input.getToolInput; this.getToolInput = input.getToolInput;
} }
@@ -291,11 +291,7 @@ export class MockLoadTestAgentSession implements AgentSession {
private modeId: string | null; private modeId: string | null;
private modelId: string | null; private modelId: string | null;
constructor(options: { constructor(options: { config: AgentSessionConfig; sessionId: string; logger?: Logger }) {
config: AgentSessionConfig;
sessionId: string;
logger?: Logger;
}) {
this.id = options.sessionId; this.id = options.sessionId;
this.logger = options.logger; this.logger = options.logger;
this.modeId = options.config.modeId ?? MOCK_LOAD_TEST_MODE_ID; 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( test.each(providerLaunchCases)(
providerLaunchCases, "$provider launches a cmd shim from a path with spaces through spawnProcess",
)("$provider launches a cmd shim from a path with spaces through spawnProcess", async ({ async ({ binaryName, args, shell }) => {
binaryName, const fixture = makeFixture(binaryName, args);
args,
shell,
}) => {
const fixture = makeFixture(binaryName, args);
const result = await runProviderFixture({ const result = await runProviderFixture({
command: fixture.shim, command: fixture.shim,
args: fixture.expectedArgs, args: fixture.expectedArgs,
expectedArgs: fixture.expectedArgs, expectedArgs: fixture.expectedArgs,
shell, shell,
}); });
expect(result.error).toBeNull(); expect(result.error).toBeNull();
expect(result.code).toBe(0); expect(result.code).toBe(0);
expect(result.signal).toBeNull(); expect(result.signal).toBeNull();
expect(result.stderr).toBe(""); expect(result.stderr).toBe("");
expect(result.stdout.trim()).toBe("ARGV_OK"); expect(result.stdout.trim()).toBe("ARGV_OK");
}); },
);
}); });
@@ -78,52 +78,51 @@ describe("daemon E2E", () => {
await ctx.cleanup(); await ctx.cleanup();
}, 60000); }, 60000);
describe.each([ describe.each(["claude", "codex", "opencode"] as const)(
"claude", "live model switching (%s)",
"codex", (provider) => {
"opencode", const shouldRun =
] as const)("live model switching (%s)", (provider) => { provider === "claude" ||
const shouldRun = (provider === "codex" && hasCodex) ||
provider === "claude" || (provider === "opencode" && hasOpenCode);
(provider === "codex" && hasCodex) ||
(provider === "opencode" && hasOpenCode);
test.runIf(shouldRun)( test.runIf(shouldRun)(
"updates agent model without restarting", "updates agent model without restarting",
async () => { async () => {
const cwd = tmpCwd(); const cwd = tmpCwd();
try { try {
const modelList = await ctx.client.listProviderModels(provider); const modelList = await ctx.client.listProviderModels(provider);
if (!modelList.models || modelList.models.length === 0) { if (!modelList.models || modelList.models.length === 0) {
throw new Error(`No models returned for provider ${provider}`); 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); },
180000,
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,
);
});
test("live thinking switching works for Claude (off -> on)", async () => { test("live thinking switching works for Claude (off -> on)", async () => {
const cwd = tmpCwd(); const cwd = tmpCwd();
+1 -5
View File
@@ -35,11 +35,7 @@ export {
type ProviderOverride, type ProviderOverride,
type ProviderProfileModel, type ProviderProfileModel,
} from "./agent/provider-launch-config.js"; } from "./agent/provider-launch-config.js";
export { export { findExecutable, quoteWindowsArgument, quoteWindowsCommand } from "../utils/executable.js";
findExecutable,
quoteWindowsArgument,
quoteWindowsCommand,
} from "../utils/executable.js";
export { execCommand, spawnProcess } from "../utils/spawn.js"; export { execCommand, spawnProcess } from "../utils/spawn.js";
// Provider manifest (source of truth for provider definitions) // Provider manifest (source of truth for provider definitions)
@@ -1776,9 +1776,9 @@ describe("workspace aggregation", () => {
rmSync(tempDir, { recursive: true, force: true }); rmSync(tempDir, { recursive: true, force: true });
} }
const response = emitted.find((message) => message.type === "create_paseo_worktree_response") as const response = emitted.find(
| { type: "create_paseo_worktree_response"; payload: any } (message) => message.type === "create_paseo_worktree_response",
| undefined; ) as { type: "create_paseo_worktree_response"; payload: any } | undefined;
expect(response?.payload.error).toBeNull(); expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace).toMatchObject({ expect(response?.payload.workspace).toMatchObject({
@@ -7,8 +7,5 @@ export {
type DaemonEvent, type DaemonEvent,
type DaemonEventHandler, type DaemonEventHandler,
} from "./daemon-client.js"; } from "./daemon-client.js";
export { export { createDaemonTestContext, type DaemonTestContext } from "./daemon-test-context.js";
createDaemonTestContext,
type DaemonTestContext,
} from "./daemon-test-context.js";
export { useTempClaudeConfigDir } from "./claude-config.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" }); expect(parsed.payload.error).toEqual({ kind: "unknown", message: "x" });
}); });
test.each([ test.each(["rate_limited", "", undefined])(
"rate_limited", "normalizes malformed error kind %j to unknown",
"", (kind) => {
undefined, const parsed = PullRequestTimelineResponseSchema.parse({
])("normalizes malformed error kind %j to unknown", (kind) => { type: "pull_request_timeline_response",
const parsed = PullRequestTimelineResponseSchema.parse({ payload: {
type: "pull_request_timeline_response", error: { kind },
payload: { },
error: { kind }, });
},
});
expect(parsed.payload.error).toEqual({ kind: "unknown", message: "" }); expect(parsed.payload.error).toEqual({ kind: "unknown", message: "" });
}); },
);
}); });
describe("checkout PR status compatibility", () => { describe("checkout PR status compatibility", () => {
+1 -1
View File
@@ -6,7 +6,7 @@ main = "@tanstack/react-start/server-entry"
routes = [ routes = [
{ pattern = "paseo.sh", custom_domain = true }, { pattern = "paseo.sh", custom_domain = true },
{ pattern = "www.paseo.sh", custom_domain = true } { pattern = "www.paseo.sh", custom_domain = true },
] ]
[observability] [observability]
+5
View File
@@ -19,6 +19,7 @@ Load the **Paseo skill** first if you need CLI guidance for launching or messagi
## Rules ## Rules
When using chat: When using chat:
- create a room with `paseo chat create` if you need a new room - create a room with `paseo chat create` if you need a new room
- inspect available rooms with `paseo chat ls` and `paseo chat inspect` - inspect available rooms with `paseo chat ls` and `paseo chat inspect`
- post with `paseo chat post` - post with `paseo chat post`
@@ -27,6 +28,7 @@ When using chat:
- check chat often while working - check chat often while working
Mentions are active: Mentions are active:
- write mentions inline in the message body as `@<agent-id>` to notify a specific agent immediately - 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 - use `@everyone` to notify all non-archived, non-internal agents
- notifications are sent to the target agent without blocking the chat post - notifications are sent to the target agent without blocking the chat post
@@ -99,10 +101,12 @@ paseo chat wait issue-456 --timeout 60s
## Defaults ## Defaults
When creating a room: When creating a room:
- choose a short slug: `issue-456`, `pr-143-review`, `relay-cleanup` - choose a short slug: `issue-456`, `pr-143-review`, `relay-cleanup`
- give it a clear purpose - give it a clear purpose
When using a room: When using a room:
- read only a bounded window before acting - read only a bounded window before acting
- post updates when they would help another agent or your future self - post updates when they would help another agent or your future self
- use `--reply-to` when responding to a specific message - 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` - your own agent ID is available via `$PASEO_AGENT_ID`
Typical things to post: Typical things to post:
- status updates - status updates
- blockers - blockers
- handoffs - handoffs
+7 -7
View File
@@ -30,13 +30,13 @@ Parse `$ARGUMENTS` to determine:
### Provider Resolution ### Provider Resolution
| User says | --provider | Mode | | User says | --provider | Mode |
|---|---|---| | ----------- | --------------- | ------------- |
| *(nothing)* | `codex/gpt-5.4` | `full-access` | | _(nothing)_ | `codex/gpt-5.4` | `full-access` |
| `codex` | `codex/gpt-5.4` | `full-access` | | `codex` | `codex/gpt-5.4` | `full-access` |
| `claude` | `claude/opus` | `bypass` | | `claude` | `claude/opus` | `bypass` |
| `opus` | `claude/opus` | `bypass` | | `opus` | `claude/opus` | `bypass` |
| `sonnet` | `claude/sonnet` | `bypass` | | `sonnet` | `claude/sonnet` | `bypass` |
Default is **Codex** with `gpt-5.4`. Default is **Codex** with `gpt-5.4`.
+2
View File
@@ -108,6 +108,7 @@ paseo loop stop <id> # Stop a running loop
### Worker prompt ### Worker prompt
The worker prompt must be: The worker prompt must be:
- self-contained - self-contained
- concrete about commands, files, branches, tests, PRs, or systems to inspect - concrete about commands, files, branches, tests, PRs, or systems to inspect
- explicit about what counts as progress this iteration - explicit about what counts as progress this iteration
@@ -115,6 +116,7 @@ The worker prompt must be:
### Verifier prompt ### Verifier prompt
The verifier prompt should: The verifier prompt should:
- check facts, not offer fixes - check facts, not offer fixes
- cite commands, outputs, or file evidence - cite commands, outputs, or file evidence
- be specific about what "done" means - be specific about what "done" means
+59 -50
View File
@@ -17,6 +17,7 @@ You are an end-to-end implementation orchestrator. You take a task from understa
## Prerequisites ## Prerequisites
Load these skills before proceeding: Load these skills before proceeding:
1. **e2e-playwright** — if the task involves frontend/UI work 1. **e2e-playwright** — if the task involves frontend/UI work
## Guard ## 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 before `/` is the `agentType` (e.g., `codex`, `claude`, `opencode`)
- The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`) - The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`)
| Category | Roles covered | Default | | Category | Roles covered | Default |
|----------|--------------|---------| | ---------- | --------------------------------- | --------------- |
| `impl` | impl, tester, refactorer | `codex/gpt-5.4` | | `impl` | impl, tester, refactorer | `codex/gpt-5.4` |
| `ui` | impl agents doing UI/styling work | `claude/opus` | | `ui` | impl agents doing UI/styling work | `claude/opus` |
| `research` | researcher | `codex/gpt-5.4` | | `research` | researcher | `codex/gpt-5.4` |
| `planning` | planner, plan-reviewer | `codex/gpt-5.4` | | `planning` | planner, plan-reviewer | `codex/gpt-5.4` |
| `audit` | auditor, qa | `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. 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 ### How to Write Agent Prompts
**Describe the problem, not the solution.** Your prompt should tell the agent: **Describe the problem, not the solution.** Your prompt should tell the agent:
- What's wrong or what needs to be built (the goal) - What's wrong or what needs to be built (the goal)
- How it currently fails (error output, test output, user-visible behavior) - How it currently fails (error output, test output, user-visible behavior)
- The acceptance criteria (what "done" looks like) - The acceptance criteria (what "done" looks like)
**Do NOT tell the agent:** **Do NOT tell the agent:**
- Which specific lines to change - Which specific lines to change
- What code to write - What code to write
- Which functions to call or which patterns to use - 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. **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 | | Phase | Agents |
|-------|--------| | --------- | ----------------------------- |
| Research | 1 researcher | | Research | 1 researcher |
| Planning | 0 — orchestrator plans inline | | Planning | 0 — orchestrator plans inline |
| Implement | 1 impl | | Implement | 1 impl |
| Verify | 1-2 auditors | | Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer | | Cleanup | 0-1 refactorer |
**Order 2 — Single module, few files.** A feature or fix within one package that touches 3-8 files. **Order 2 — Single module, few files.** A feature or fix within one package that touches 3-8 files.
| Phase | Agents | | Phase | Agents |
|-------|--------| | --------- | ---------------- |
| Research | 2 researchers | | Research | 2 researchers |
| Planning | 1 planner | | Planning | 1 planner |
| Implement | 1 impl per phase | | Implement | 1 impl per phase |
| Verify | 2-3 auditors | | Verify | 2-3 auditors |
| Cleanup | 1 refactorer | | Cleanup | 1 refactorer |
**Order 3 — Cross-module, multiple packages.** A feature that spans packages. **Order 3 — Cross-module, multiple packages.** A feature that spans packages.
| Phase | Agents | | Phase | Agents |
|-------|--------| | --------- | ---------------------------- |
| Research | 3-4 researchers | | Research | 3-4 researchers |
| Planning | 2 planners + 1 plan-reviewer | | Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase | | Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors | | Verify | 3-4 auditors |
| Cleanup | 1-2 refactorers | | Cleanup | 1-2 refactorers |
**Order 4 — Architectural, system-wide.** A new subsystem, major refactor, or system-wide change. **Order 4 — Architectural, system-wide.** A new subsystem, major refactor, or system-wide change.
| Phase | Agents | | Phase | Agents |
|-------|--------| | --------- | ------------------------------ |
| Research | 5+ researchers | | Research | 5+ researchers |
| Planning | 2+ planners + 2 plan-reviewers | | Planning | 2+ planners + 2 plan-reviewers |
| Implement | 2+ impl agents per phase | | Implement | 2+ impl agents per phase |
| Verify | Full auditor suite per phase | | Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers | | 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. 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: The right approach:
1. Study the existing code — understand why it's shaped the way it is 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? 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? 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: Persist the plan to disk and set up the heartbeat:
Use the Paseo **create schedule** tool with: Use the Paseo **create schedule** tool with:
- `name`: `"heartbeat-<task-slug>"` - `name`: `"heartbeat-<task-slug>"`
- `target`: `"self"` - `target`: `"self"`
- `every`: `"5m"` - `every`: `"5m"`
@@ -394,6 +399,7 @@ Do the following steps in order:
## Phase 7: Implement ## Phase 7: Implement
Execute phases from the plan sequentially. For each phase: Execute phases from the plan sequentially. For each phase:
1. Launch impl agent(s) with `background: true, notifyOnFinish: true` 1. Launch impl agent(s) with `background: true, notifyOnFinish: true`
2. Wait for notification 2. Wait for notification
3. Verify (Phase 8) 3. Verify (Phase 8)
@@ -406,6 +412,7 @@ UI passes use `providers.ui` from preferences. All other impl work uses `provide
### TDD — Not Optional ### TDD — Not Optional
Every impl agent works TDD: Every impl agent works TDD:
1. Write a failing test that defines the expected behavior 1. Write a failing test that defines the expected behavior
2. Make it pass 2. Make it pass
3. Refactor if needed 3. Refactor if needed
@@ -462,6 +469,7 @@ Run typecheck when done. Do NOT commit."
### Handling Blockers ### Handling Blockers
If an impl agent reports a blocker: If an impl agent reports a blocker:
- Do NOT ask the user (in either mode) - Do NOT ask the user (in either mode)
- Spin up a researcher to investigate - Spin up a researcher to investigate
- Spin up an impl agent to fix it - 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 ### Which Auditors to Deploy
| Phase type | Auditors | | Phase type | Auditors |
|-----------|----------| | ------------------ | ------------------------------------------------------ |
| Refactor | `parity`, `regression`, `types` | | Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` | | Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) | | Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) |
| UI pass | `overeng`, `browser` (if applicable) | | UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` | | Test-only | `regression` |
Deploy all relevant auditors in parallel — they're read-only so they don't conflict. 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 ### Interpreting Findings
If any auditor reports issues: If any auditor reports issues:
1. Check the auditor's activity with Paseo **get agent activity** for details 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 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 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. **Naming:** `<role>-<scope>[-<specialization>]` in kebab-case.
| Role | Job | Edits? | Prompt emphasis | | 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." | | `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." | | `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." | | `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." | | `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." | | `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." | | `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." | | `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." | | `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` Auditor specializations: `overeng`, `dry`, `tests`, `regression`, `types`, `browser`, `parity`
Refactorer specializations: `dry`, `dead-code`, `naming` Refactorer specializations: `dry`, `dead-code`, `naming`
@@ -81,6 +81,7 @@ Do NOT edit files."
## If Issues Are Found ## If Issues Are Found
If any final QA agent reports issues: If any final QA agent reports issues:
1. Launch an impl or refactorer agent to fix them 1. Launch an impl or refactorer agent to fix them
2. Re-run the specific QA check that failed 2. Re-run the specific QA check that failed
3. Repeat until all checks pass 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 ## TDD — Not Optional
Every impl agent works TDD: Every impl agent works TDD:
1. Write a failing test that defines the expected behavior 1. Write a failing test that defines the expected behavior
2. Make it pass 2. Make it pass
3. Refactor if needed 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. Execute phases sequentially from the plan. Refactoring phases first, then feature phases, then UI passes.
After each phase: After each phase:
1. Verify (see verification.md) 1. Verify (see verification.md)
2. Fix any issues found 2. Fix any issues found
3. Re-verify 3. Re-verify
@@ -29,11 +31,13 @@ Use the Paseo **create agent** tool with `background: true` and `notifyOnFinish:
### How to describe the work ### How to describe the work
**Describe the problem, not the solution.** Your prompt should tell the agent: **Describe the problem, not the solution.** Your prompt should tell the agent:
- What's wrong or what needs to be built (the goal) - What's wrong or what needs to be built (the goal)
- How it currently fails (error output, test output, user-visible behavior) - How it currently fails (error output, test output, user-visible behavior)
- The acceptance criteria (what "done" looks like) - The acceptance criteria (what "done" looks like)
**Do NOT tell the agent:** **Do NOT tell the agent:**
- Which specific lines to change - Which specific lines to change
- What code to write - What code to write
- Which functions to call or which patterns to use - Which functions to call or which patterns to use
@@ -95,6 +99,7 @@ Run typecheck when done. Do NOT commit."
## Handling Blockers ## Handling Blockers
If an impl agent reports a blocker: If an impl agent reports a blocker:
- Do NOT ask the user (in either mode) - Do NOT ask the user (in either mode)
- Spin up a researcher to investigate - Spin up a researcher to investigate
- Spin up an impl agent to fix it - 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. 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: The right approach:
1. Study the existing code — understand why it's shaped the way it is 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? 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? 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+) ## Multiple Planners (Order 3+)
For cross-module tasks, deploy planners focusing on different slices: For cross-module tasks, deploy planners focusing on different slices:
- One for backend phases - One for backend phases
- One for frontend phases - One for frontend phases
- One for test strategy - One for test strategy
@@ -74,6 +76,7 @@ Then deploy a plan-reviewer to challenge the combined plan.
## Iteration ## Iteration
If the plan-reviewer finds significant issues, either: If the plan-reviewer finds significant issues, either:
1. Send follow-up instructions via the Paseo **send agent prompt** tool to the planner 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 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 before `/` is the `agentType` (e.g., `codex`, `claude`, `opencode`)
- The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`) - The part after `/` is the `model` (e.g., `gpt-5.4`, `opus`)
| Category | Roles covered | | Category | Roles covered |
|----------|--------------| | ---------- | --------------------------------- |
| `impl` | impl, tester, refactorer | | `impl` | impl, tester, refactorer |
| `ui` | impl agents doing UI/styling work | | `ui` | impl agents doing UI/styling work |
| `research` | researcher | | `research` | researcher |
| `planning` | planner, plan-reviewer | | `planning` | planner, plan-reviewer |
| `audit` | auditor, qa | | `audit` | auditor, qa |
If a category is missing, use these defaults: If a category is missing, use these defaults:
- `impl` -> `codex/gpt-5.4` - `impl` -> `codex/gpt-5.4`
- `ui` -> `claude/opus` - `ui` -> `claude/opus`
- `research` -> `codex/gpt-5.4` - `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. 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: Examples:
- "Prefer small, focused PRs over large bundled ones" - "Prefer small, focused PRs over large bundled ones"
- "Run E2E tests with Maestro, not Playwright" - "Run E2E tests with Maestro, not Playwright"
- "Always check mobile responsiveness" - "Always check mobile responsiveness"
+27 -27
View File
@@ -18,46 +18,46 @@ State the order and briefly why: "Order 3 — touches server session management
A contained change: fix a bug in one function, add a field to one type, update one component. A contained change: fix a bug in one function, add a field to one type, update one component.
| Phase | Agents | | Phase | Agents |
|-------|--------| | --------- | ----------------------------- |
| Research | 1 researcher | | Research | 1 researcher |
| Planning | 0 — orchestrator plans inline | | Planning | 0 — orchestrator plans inline |
| Implement | 1 impl | | Implement | 1 impl |
| Verify | 1-2 auditors | | Verify | 1-2 auditors |
| Cleanup | 0-1 refactorer | | Cleanup | 0-1 refactorer |
### Order 2 — Single module, few files ### 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. A feature or fix within one package that touches 3-8 files. Might involve new types, new tests, a few component changes.
| Phase | Agents | | Phase | Agents |
|-------|--------| | --------- | ---------------- |
| Research | 2 researchers | | Research | 2 researchers |
| Planning | 1 planner | | Planning | 1 planner |
| Implement | 1 impl per phase | | Implement | 1 impl per phase |
| Verify | 2-3 auditors | | Verify | 2-3 auditors |
| Cleanup | 1 refactorer | | Cleanup | 1 refactorer |
### Order 3 — Cross-module, multiple packages ### 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. 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 | | Phase | Agents |
|-------|--------| | --------- | ----------------------------------------------------------------------- |
| Research | 3-4 researchers (one per area: backend, frontend, tests, external docs) | | Research | 3-4 researchers (one per area: backend, frontend, tests, external docs) |
| Planning | 2 planners + 1 plan-reviewer | | Planning | 2 planners + 1 plan-reviewer |
| Implement | 1-2 impl agents per phase | | Implement | 1-2 impl agents per phase |
| Verify | 3-4 auditors (overeng, tests, regression, types) | | Verify | 3-4 auditors (overeng, tests, regression, types) |
| Cleanup | 1-2 refactorers | | Cleanup | 1-2 refactorers |
### Order 4 — Architectural, system-wide ### 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. 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 | | Phase | Agents |
|-------|--------| | --------- | --------------------------------------------------- |
| Research | 5+ researchers across all relevant areas | | Research | 5+ researchers across all relevant areas |
| Planning | 2+ planners (one per major area) + 2 plan-reviewers | | Planning | 2+ planners (one per major area) + 2 plan-reviewers |
| Implement | 2+ impl agents per phase, sequenced carefully | | Implement | 2+ impl agents per phase, sequenced carefully |
| Verify | Full auditor suite per phase | | Verify | Full auditor suite per phase |
| Cleanup | 2+ refactorers with different specializations | | 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: Not every phase needs every auditor. Match auditors to the work:
| Phase type | Auditors | | Phase type | Auditors |
|-----------|----------| | ------------------ | ------------------------------------------------------ |
| Refactor | `parity`, `regression`, `types` | | Refactor | `parity`, `regression`, `types` |
| Feature (backend) | `overeng`, `tests`, `regression`, `types` | | Feature (backend) | `overeng`, `tests`, `regression`, `types` |
| Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) | | Feature (frontend) | `overeng`, `tests`, `types`, `browser` (if applicable) |
| UI pass | `overeng`, `browser` (if applicable) | | UI pass | `overeng`, `browser` (if applicable) |
| Test-only | `regression` | | Test-only | `regression` |
Deploy all relevant auditors in parallel — they're read-only so they don't conflict. 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 ## Interpreting Findings
If any auditor reports issues: If any auditor reports issues:
1. Check the auditor's activity with the Paseo **get agent activity** tool for details 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 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 3. Re-deploy the same auditor after fixes
+4
View File
@@ -193,6 +193,7 @@ paseo terminal send-keys <terminal-id> --literal "raw text" # No special token
`Enter`, `Tab`, `Escape`, `Space`, `BSpace`, `C-c`, `C-d`, `C-z`, `C-l`, `C-a`, `C-e` `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:** **Common pattern — launch a process and interact with it:**
```bash ```bash
id=$(paseo terminal create --name "my-shell" -q) id=$(paseo terminal create --name "my-shell" -q)
paseo terminal send-keys "$id" "claude" Enter paseo terminal send-keys "$id" "claude" Enter
@@ -208,11 +209,13 @@ paseo terminal kill "$id"
## Available Models ## Available Models
**Claude (default provider):** **Claude (default provider):**
- `--provider claude/haiku` — Fast/cheap, ONLY for tests (not for real work) - `--provider claude/haiku` — Fast/cheap, ONLY for tests (not for real work)
- `--provider claude/sonnet` — Good for most tasks - `--provider claude/sonnet` — Good for most tasks
- `--provider claude/opus` — For harder reasoning, complex debugging - `--provider claude/opus` — For harder reasoning, complex debugging
**Codex:** **Codex:**
- `--provider codex/gpt-5.4` — Latest frontier agentic coding model (preferred for all engineering tasks) - `--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 - `--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. `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:** **Detach + wait pattern for parallel work:**
```bash ```bash
api_id=$(paseo run -d --name "impl-api" "implement the API" -q) api_id=$(paseo run -d --name "impl-api" "implement the API" -q)
ui_id=$(paseo run -d --name "impl-ui" "implement the UI" -q) ui_id=$(paseo run -d --name "impl-ui" "implement the UI" -q)