Accept prerelease Paseo versions in plugin requirements and prepare plugin docs for the 0.8 beta (#4452)

Accept plugin requirements against the runtime version or its stable core while preserving the legacy version boundary. Validate all example manifests, correct the ACP example metadata, and prepare plugin author documentation for the beta.
This commit is contained in:
Mohamed Boudra
2026-09-08 08:30:52 +02:00
committed by GitHub
parent 1f5b6143d6
commit 229f3cd5a8
13 changed files with 88 additions and 65 deletions
+9
View File
@@ -47,6 +47,15 @@ Run agents in parallel on your own machines. Ship from your phone or your desk.
- **Cross-device:** iOS, Android, desktop, web, and CLI. Start work at your desk, check in from your phone, script it from the terminal.
- **Privacy-first:** Paseo doesn't have any telemetry, tracking, or forced log-ins.
## Plugins
Add themes, workspace panels, commands, settings screens, and coding-agent providers with trusted
TypeScript plugins. Install from a local directory or Git repository with `paseo plugin add <source>`.
See the [plugin docs](https://paseo.sh/docs/plugins) for your Paseo version, or start with the
[0.8 beta quickstart](https://paseo.sh/docs/plugins/v0.8). Plugins run with access to your daemon
machine and inside connected clients; install only code you trust.
## Getting Started
Paseo runs a local server called the daemon that manages your coding agents. Clients like the desktop app, mobile app, web app, and CLI connect to it.
@@ -1,24 +1,26 @@
import { describe, expect, it } from "vitest";
import { assertPluginCompatibility, validatePluginRequirements } from "./plugin-requirements.js";
describe("plugin requirements", () => {
describe.each(["daemon", "app"] as const)("plugin requirements on %s", (runtime) => {
it("rejects legacy manifests on the first breaking release with migration instructions", () => {
expect(() =>
assertPluginCompatibility({ id: "legacy", version: "0.8.0", runtime: "daemon" }),
).toThrow(/legacy.*<0\.8\.0.*0\.8\.0.*https:\/\/paseo.sh\/docs\/plugins\/v0.8\/migration/);
expect(() => assertPluginCompatibility({ id: "legacy", version: "0.8.0", runtime })).toThrow(
/legacy.*<0\.8\.0.*0\.8\.0.*https:\/\/paseo.sh\/docs\/plugins\/v0.8\/migration/,
);
});
it.each([
[undefined, "0.7.2"],
[">=0.8.0", "0.8.0"],
[">=0.8.0", "0.8.0-beta.1"],
[">=0.8.0", "1.0.0"],
["^0.8.0", "0.8.4"],
[">=0.8.0-beta.1", "0.8.0-beta.2"],
[">=0.8.0-beta.1", "0.8.0-beta.1"],
[">=0.8.0-beta.1", "0.8.0"],
["^0.8.0 || ^0.9.0", "0.9.2+build.42"],
])("accepts %s on %s", (paseo, version) => {
expect(() =>
assertPluginCompatibility({ id: "test", requirements: { paseo }, version, runtime: "app" }),
assertPluginCompatibility({ id: "test", requirements: { paseo }, version, runtime }),
).not.toThrow();
});
@@ -27,11 +29,11 @@ describe("plugin requirements", () => {
[undefined, "0.9.0"],
[">=0.8.0", "0.7.2"],
["^0.8.0", "0.9.0"],
[">=0.8.0", "0.8.0-beta.1"],
["<0.8.0", "0.8.0-beta.1"],
])("rejects %s on %s", (paseo, version) => {
expect(() =>
assertPluginCompatibility({ id: "test", requirements: { paseo }, version, runtime: "app" }),
).toThrow(`Your app is ${version}`);
assertPluginCompatibility({ id: "test", requirements: { paseo }, version, runtime }),
).toThrow(`Your ${runtime} is ${version}`);
});
it.each(["", " ", "latest", ">=potato", "0.8.0 nonsense"])(
@@ -47,8 +49,8 @@ describe("plugin requirements", () => {
id: "test",
requirements: { paseo: "*" },
version,
runtime: "app",
runtime,
}),
).toThrow("Paseo app version is unknown");
).toThrow(`Paseo ${runtime} version is unknown`);
});
});
+6 -4
View File
@@ -1,5 +1,5 @@
import type { PluginRequirements } from "./messages.js";
import valid from "semver/functions/valid.js";
import parse from "semver/functions/parse.js";
import validRange from "semver/ranges/valid.js";
import satisfies from "semver/functions/satisfies.js";
@@ -21,14 +21,16 @@ interface PluginCompatibilityInput {
export function assertPluginCompatibility(input: PluginCompatibilityInput): void {
validatePluginRequirements(input.requirements);
// COMPAT(plugin-requirements): added in v0.8.0; remove after 2027-03-07 once pre-0.8 plugins and catalogs are unsupported.
// COMPAT(plugin-requirements): added in v0.8.0-beta.1; remove after 2027-03-07 once pre-0.8 plugins and catalogs are unsupported. The legacy range excludes 0.8 prereleases and their stable core.
const range = input.requirements?.paseo ?? "<0.8.0";
if (!input.version || !valid(input.version)) {
const version = input.version ? parse(input.version) : null;
if (!version) {
throw new Error(
`Cannot check plugin "${input.id}" requirements: Paseo ${input.runtime} version is unknown. Update the ${input.runtime}.`,
);
}
if (satisfies(input.version, range)) return;
const stableCore = `${version.major}.${version.minor}.${version.patch}`;
if (satisfies(version, range) || satisfies(stableCore, range)) return;
const action =
input.requirements?.paseo === undefined
? "This plugin has no requirements.paseo and targets Paseo before 0.8. Ask its author to migrate it: https://paseo.sh/docs/plugins/v0.8/migration"
@@ -1,16 +1,29 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { readPluginManifest } from "./manifest.js";
const directories: string[] = [];
const examplesDirectory = fileURLToPath(
new URL("../../../../../plugin-examples/", import.meta.url),
);
const examples = (await readdir(examplesDirectory, { withFileTypes: true }))
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name);
afterEach(async () => {
await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true })));
});
describe("plugin manifest", () => {
it.each(examples)("validates the %s example manifest", async (name) => {
await expect(readPluginManifest(path.join(examplesDirectory, name))).resolves.toMatchObject({
id: expect.any(String),
});
});
it("reads and validates requirements before any plugin code runs", async () => {
const directory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-manifest-"));
directories.push(directory);
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { buildDocsNavTree, getDocs, getLegacyDocsRedirect } from "./docs";
describe("versioned plugin documentation", () => {
it("keeps current and preview releases together in the Plugins navigation", () => {
it("keeps stable and beta releases together in the Plugins navigation", () => {
const plugins = buildDocsNavTree(getDocs()).find(
(node) => node.type === "category" && node.label === "Plugins",
);
@@ -19,11 +19,12 @@ describe("versioned plugin documentation", () => {
},
{
type: "group",
label: "Paseo v0.8 — Preview",
label: "Paseo v0.8 — Beta",
href: "/docs/plugins/v0.8",
children: [
{ type: "page", label: "Reference", href: "/docs/plugins/v0.8/reference" },
{ type: "page", label: "Provider plugins", href: "/docs/plugins/v0.8/providers" },
{ type: "page", label: "Migration", href: "/docs/plugins/v0.8/migration" },
{ type: "page", label: "Reference", href: "/docs/plugins/v0.8/reference" },
],
},
],
@@ -17,6 +17,10 @@ paseo plugin install /absolute/path/to/plugin-examples/lifecycle-logger
paseo plugin logs lifecycle-logger
```
Outside the Paseo repository, `server/inspect.ts` needs `@getpaseo/protocol` installed for its
type imports. Add it to your plugin development dependencies at the same version as
`@getpaseo/plugin` before installing this example.
Copy the callbacks you need into your own plugin. The follow-up example sends a new message; it does
not replay attachments or undo previous tool effects. If the provider keeps returning the matching
phrase, it keeps sending follow-ups. Add limits or delay in your plugin when your workflow needs them.
+7 -12
View File
@@ -9,21 +9,16 @@ Set a personal API key in the daemon environment:
export LINEAR_API_KEY="lin_api_..."
```
Register the extension in `$PASEO_HOME/config.json`:
Start your daemon with that environment variable set. Then turn on **Enable plugins** in
**Settings → Plugins** and install the example:
```json
{
"pluginsEnabled": true,
"plugins": {
"linear": {
"source": "directory",
"path": "/absolute/path/to/paseo/plugin-examples/linear"
}
}
}
```bash
paseo plugin add /absolute/path/to/paseo/plugin-examples/linear
paseo plugin ls linear
```
Restart the development daemon after changing its environment or plugin configuration.
Use `paseo plugin reload linear` after source edits. Changing the API key requires restarting the
daemon with the new environment; installing or reloading plugin source does not.
The split entry point demonstrates the complete attachment-source pattern:
@@ -1,8 +1,5 @@
{
"id": "provider-acp-transformer-example",
"name": "ACP provider transformer example",
"version": "1.0.0",
"description": "Wraps an ACP command and normalizes one vendor file-edit shape.",
"requirements": {
"paseo": ">=0.8.0"
}
+2 -2
View File
@@ -15,7 +15,7 @@ change between releases.
Build and maintain plugins for the latest stable Paseo release.
## [Paseo v0.8 — Preview](/docs/plugins/v0.8)
## [Paseo v0.8 — Beta](/docs/plugins/v0.8)
Prepare plugins for the upcoming client and server entry layout. Existing plugin authors should
Build plugins for Paseo 0.8 beta, with separate client and server entries. Existing plugin authors should
follow the [v0.8 migration guide](/docs/plugins/v0.8/migration).
+3 -3
View File
@@ -1,15 +1,15 @@
---
title: Plugin quickstart
description: Build, install, share, and update a trusted Paseo plugin.
nav: Paseo v0.8 — Preview
nav: Paseo v0.8 — Beta
order: 46
category: Plugins
---
# Plugin quickstart
> **For the upcoming Paseo v0.8 release.** Use the [current v0.7 docs](/docs/plugins/v0.7)
> unless you are preparing a plugin for v0.8.
> **For Paseo v0.8 beta.** Use the [v0.7 docs](/docs/plugins/v0.7)
> if you run the stable release.
> **Experimental:** The plugin API is still evolving, so expect breaking changes and updates to
> your plugins as Paseo evolves. See the [plugin roadmap](https://github.com/getpaseo/paseo/labels/plugins)
+3 -3
View File
@@ -8,7 +8,7 @@ category: Plugins
# Migrate a plugin to runtime entries
> **For the upcoming Paseo v0.8 release.** This migration is not required for Paseo v0.7.
> **For Paseo v0.8 beta.** This migration is not required for Paseo v0.7.
Give this page to a coding agent with the plugin directory as its working directory. Execute the
steps in order. Do not keep a compatibility entry.
@@ -255,8 +255,8 @@ rejects the plugin even if its files have been moved. Adding the field alone doe
code. Update the local `@getpaseo/plugin` development dependency to the version you target and
reinstall dependencies before typechecking.
For a 0.8 beta, use its explicit version in both the SDK dependency and range, for example
`>=0.8.0-beta.1`. See [requirements](reference#requirements) for range and prerelease semantics.
For a 0.8 beta, use its explicit version in the SDK dependency and `>=0.8.0` in the manifest.
See [requirements](reference#requirements) for range and prerelease semantics.
## 8. Verify the migration
+3 -3
View File
@@ -8,7 +8,7 @@ category: Plugins
# Build a provider plugin
> **For the upcoming Paseo v0.8 release.** Start with the
> **For Paseo v0.8 beta.** Start with the
> [plugin quickstart](/docs/plugins/v0.8) if you have not built a Paseo plugin before.
A provider plugin connects a coding agent to Paseo without adding it to Paseo core. Publish the
@@ -22,10 +22,10 @@ Choose one implementation path:
| It already implements ACP | Register it with `runAcpProvider()` and add only narrow vendor transformers. |
| It has a TypeScript SDK, JSON-RPC API, or custom process protocol | Implement `ProviderRegistration` directly. |
The complete examples are:
The examples are:
- [`provider-direct`](https://github.com/getpaseo/paseo/tree/main/plugin-examples/provider-direct): sessions, settings, prompts, persistence, child sessions, and a provider-owned timeline renderer;
- [`provider-acp-transformer`](https://github.com/getpaseo/paseo/tree/main/plugin-examples/provider-acp-transformer): an ACP command with a Zod-validated vendor edit transformer;
- [`provider-acp-transformer`](https://github.com/getpaseo/paseo/tree/main/plugin-examples/provider-acp-transformer): an ACP command template with a Zod-validated vendor edit transformer. Replace `example-acp --stdio` with an installed ACP agent before loading it;
- [`inline-thinking`](https://github.com/getpaseo/paseo/tree/main/plugin-examples/inline-thinking): a renderer-only plugin that does not implement a provider.
## Register a direct provider
+21 -21
View File
@@ -8,7 +8,7 @@ category: Plugins
# Plugin reference
> **For the upcoming Paseo v0.8 release.** Return to the [v0.8 quickstart](/docs/plugins/v0.8).
> **For Paseo v0.8 beta.** Return to the [v0.8 quickstart](/docs/plugins/v0.8).
Migrating an existing plugin? Follow the standalone [runtime-entry migration guide](/docs/plugins/v0.8/migration).
@@ -55,15 +55,13 @@ range. An omitted `requirements.paseo` means `<0.8.0`: the plugin predates the f
plugin release. Paseo 0.8 and later reject it with a link to the [migration guide](migration).
Empty strings, invalid ranges, and unknown manifest requirement keys are rejected.
| Range | Compatible releases |
| ---------------- | ------------------------------------------------------------------- |
| `>=0.8.0` | 0.8.0 and later stable releases, including future breaking releases |
| `^0.8.0` | 0.8.x stable releases only |
| `>=0.8.3 <0.9.0` | 0.8.3 through the last 0.8 patch |
| `>=0.8.0-beta.1` | Betas from 0.8.0-beta.1, then 0.8.0 and later stable releases |
| Range | Compatible releases |
| ---------------- | ---------------------------------------------------------------------------- |
| `>=0.8.0` | 0.8.0 and later releases, including prereleases and future breaking releases |
| `^0.8.0` | 0.8.x releases, including prereleases |
| `>=0.8.3 <0.9.0` | 0.8.3 through the last 0.8 patch, including prereleases |
Standard npm prerelease rules apply: `>=0.8.0` excludes `0.8.0-beta.1`. A prerelease must be
explicitly included for its major/minor/patch tuple; no version tags are stripped.
Prerelease Paseo versions also satisfy a range their stable core (`major.minor.patch`) satisfies, so `0.8.0-beta.1` satisfies `>=0.8.0` but not `<0.8.0`.
`paseo plugin init` writes `>=` followed by the current CLI version and pins the matching SDK
for typechecking. Raise the minimum when adopting a newer API. Add an upper bound when a later
@@ -81,10 +79,10 @@ and cannot show this new diagnostic.
### Runtime entries
| Entry | Runtime | Receives | Required |
| ------------------ | --------------------- | --------------------- | ----------------------------------------------------------------- |
| `index.client.tsx` | Paseo app, per client | `PluginClientContext` | When the plugin has any UI, callback, theme, or attachment source |
| `index.server.ts` | Daemon subprocess | `PluginServerContext` | When the plugin handles RPCs |
| Entry | Runtime | Receives | Required |
| ------------------ | --------------------- | --------------------- | ------------------------------------------------------------------------------- |
| `index.client.tsx` | Paseo app, per client | `PluginClientContext` | When the plugin has any UI, callback, theme, or attachment source |
| `index.server.ts` | Daemon subprocess | `PluginServerContext` | When the plugin contributes handlers, hooks, settings persistence, or providers |
At least one entry is required; both accept `.ts` or `.tsx`. A directory that still has only the
old `index.ts` fails to load and points at the [migration guide](/docs/plugins/v0.8/migration).
@@ -346,7 +344,9 @@ and directory lookup/import operations are unaffected.
### Send a follow-up when a turn ends
Copy [server/inspect.ts](https://github.com/getpaseo/paseo/blob/main/plugin-examples/lifecycle-actions/server/inspect.ts)
into your plugin. `latestOutputText` joins text chunks after the latest user message.
into your plugin. The helper imports types from `@getpaseo/protocol/agent-types`; add
`@getpaseo/protocol` at the same version as your plugin SDK to your development dependencies
and install them before loading the plugin. `latestOutputText` joins text chunks after the latest user message.
```ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
@@ -1215,7 +1215,7 @@ import { z } from "zod";
const refreshReview = defineRpc({
name: "review.refresh",
input: z.object({ agentId: z.string() }),
input: z.object({ agentId: z.string(), scope: z.string().optional() }),
output: z.object({ refreshed: z.boolean() }),
});
@@ -1376,10 +1376,10 @@ registered by the same plugin.
Use `usePaseo()` for ordinary Paseo operations from a surface. It borrows the selected host's existing connection; do not create another client.
```tsx
import { usePaseo } from "@getpaseo/plugin/client";
import { type PluginSurfaceProps, usePaseo } from "@getpaseo/plugin/client";
import { Pressable, Text } from "react-native";
function PullRequestAction() {
function PullRequestAction({ theme }: PluginSurfaceProps) {
const paseo = usePaseo();
async function createReviewWorkspace() {
@@ -1400,7 +1400,7 @@ function PullRequestAction() {
return (
<Pressable accessibilityRole="button" onPress={() => void createReviewWorkspace()}>
<Text>Create review workspace</Text>
<Text style={{ color: theme.colors.foreground }}>Create review workspace</Text>
</Pressable>
);
}
@@ -1659,7 +1659,7 @@ compilation, activation, or replacement. A failing command reports its output, d
candidate, and leaves the installed/running version intact. The daemon log records each command and
output; with the global `--host` option, execution is on that daemon host.
Run `npm run typecheck` before install or reload. Never edit the daemon config directly.
Run `npm run typecheck` before install or reload. Manage plugin source entries with the CLI or Settings.
The daemon-wide **Enable plugins** switch lives under **Settings → Plugins**. A configured plugin remains `disabled` until that switch and the plugin's own enabled state are both on.
@@ -1673,8 +1673,8 @@ Use `paseo plugin ls` to read the current status and error.
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `This plugin was made for an older version of Paseo` | The directory has only an `index.ts` entry. Follow the [migration guide](/docs/plugins/v0.8/migration). |
| `Plugin entry points are missing` | Neither `index.client.tsx` nor `index.server.ts` exists with that exact name. |
| `server-only module cannot be imported into the plugin client bundle` | Client code imports `server/` or a `*.server.*` file. Move the work behind an RPC and import its contract from `shared/`. |
| `client-only module cannot be imported into the plugin server bundle` | Server code imports `client/` or a `*.client.*` file. Register that contribution from `index.client.tsx` instead. |
| `server-only module cannot be imported into the plugin client bundle` | Client code imports `server/`. Move the work behind an RPC and import its contract from `shared/`. |
| `client-only module cannot be imported into the plugin server bundle` | Server code imports `client/`. Register that contribution from `index.client.tsx` instead. |
| `Node module cannot be imported into the plugin client bundle` | Client code imports `node:*`. Move the operation to `server/` and call it through an RPC. |
| Sidebar item is missing | The plugin is `running`, the item references an existing surface, the icon name is valid, and the client is on the installation's host. |
| Client module is unavailable | Import only the host-provided client modules listed above. |