paseo-plugin について
paseo-plugin は、Paseo のアプリとデーモンを拡張するプラグインを作成・管理するスキルです。UI、RPC ハンドラー、ライフサイクルフック、互換性要件について開発者を案内します。
主な機能
- 01画面、パネル、コマンドの追加
- 02タイムラインと添付機能の登録
- 03デーモン側プラグイン RPC の実装
- 04エージェントとワークスペースのライフサイクル連携
- 05プラグイン構成のひな形作成と検証
活用シーン
- 01ワークスペースパネルや独自画面の作成
- 02スラッシュコマンドやタイムラインレンダラーの追加
- 03アプリ UI とプラグイン固有のデーモン処理の連携
- 04ライフサイクルフックによるエージェント起動設定
MCP 対応
このスキルでは、作成・起動フックを通じて MCP サーバー設定を扱う方法を説明します。paseo-plugin 自体を MCP サーバーとは説明していません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ getpaseo/ paseo --skill paseo-pluginインストール後、Claude Code で/paseo-pluginを入力して読み込まれたか確認します。
詳細
Paseo プラグイン
依頼されたプラグインを直接作成または管理します。現在の公開ドキュメントを参照して仕様の変更を確認しますが、ネットワークが利用できない場合はこのスキルを使って作業を続けてください。
ユーザーの依頼: $ARGUMENTS
現在のドキュメントを確認する
まず https://paseo.sh/llms.txt を取得します。プラグインを変更する前に、そのインデックスから現在のプラグイン用 Markdown ページを選んで取得してください。
- プラグインのクイックスタート (ブラウザページ)
- 公開:npm パッケージの内容、依存関係、Git の準備、プライベートレジストリ。
- プラグインリファレンス (ブラウザページ)
このスキルと内容が異なる場合は、公開されているドキュメントに従ってください。作業を完了せず、ユーザーにドキュメントを読むよう促してはいけません。
Paseo リポジトリ内では、チェックアウトに含まれる API(未公開の変更を含む)について public-docs/plugins/reference.md を使用してください。メンテナー向けのガイダンスには docs/plugins.md を使用してください。完全な仕様は公開ドキュメントに記載されています。このスキルでは、参照先と例をまとめています。
プラグインが提供できるもの
依頼に合った機能を選んでください。各行には登録方法、適した用途、完全な仕様の場所が記載されています。多くのプラグインは複数の機能を組み合わせます。たとえば、RPC を呼び出すスラッシュコマンドがタイムラインに行を追加し、それをレンダラーが描画します。
| 貢献内容 | 登録方法 | 使用する場面 | 参照 |
|---|---|---|---|
| 画面とサイドバー項目 | addScreen + addSidebarHeaderItem/addSidebarFooterItem | プラグイン UI の全画面、または画面やポップオーバーを開くサイドバーのヘッダーまたはフッターの行 | reference.md → 画面とサイドバー項目 |
| ワークスペースパネル | addWorkspacePanel | エージェント、ターミナル、ファイル、差分の横にタブとして表示される UI。Explorer には locations: ["explorer"] を指定 | reference.md → ワークスペースパネル |
| ファイルパネル | addFilePanel | ファイルハンドルを介して、ファイルタブ内で一致するファイルを表示または編集 | reference.md → ファイルパネル;plugin-examples/pdf-panel、plugin-examples/markdown-panel |
| コマンドセンター項目 | addCommandCenterItem | ⌘K から実行できる、グローバル、ワークスペース、またはエージェントのアクション | reference.md → コマンドセンター項目 |
| クライアントのスラッシュコマンド | addSlashCommand | エージェントにプロンプトを送る代わりにプラグインコードを実行する、コンポーザー内の /command args | reference.md → スラッシュコマンド |
| コンポーザーピル | addComposerPill | Tasks と Subagents の横にある、コンポーザートラックバー内のエージェントごとのボタン | reference.md → コンポーザーピル |
| タイムライン変換機能 | addTimelineTransformer + addTimelineRenderer | ストリーミング中も含め、組み込みのタイムライン項目を置き換え、展開、または非表示にする | reference.md → タイムライン項目;plugin-examples/timeline-items、plugin-examples/inline-thinking |
| タイムライン行 | paseo.agents.ref(id).timeline.append(...) | サーバーハンドラーからエージェントのタイムラインにプラグイン所有の行を追加し、後から更新する | reference.md → デーモンからタイムライン行を追加する |
| 添付ファイルソース | client.addAttachmentSource + server.handle | イシューなど、検索可能な外部リソースをユーザーがプロンプトに添付できるようにする | reference.md → コンポーザー添付ファイルソースを追加する;plugin-examples/linear |
| テーマ | addTheme | Settings → Appearance の下にあるライトまたはダークの配色 | reference.md → テーマを提供する;plugin-examples/catppuccin |
| プラグイン RPC | defineRpc + server.handle + useRpc | 通常の Paseo 操作ではないデーモン側の処理:ベンダー API、認証情報、ローカルファイル | reference.md → プラグイン固有のバックエンド動作を追加する |
| ライフサイクルイベント | server.on | エージェントやワークスペースのライフサイクルを監視し、終了したターンを調べ、権限リクエストに応答する | ライフサイクルフック |
| 作成と起動のフック | server.before | 操作の前にエージェント設定、プロバイダーオプション、MCP サーバー、環境、またはワークスペースの分離を変更する | 実行前フック |
| Paseo SDK | usePaseo() / handler { paseo } | 通常の Paseo 操作:ワークスペース、エージェント、プロバイダー、設定 | reference.md → Paseo SDK を使用する |
| ライフサイクルタスク | 例 | ||
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ||
| 11 個すべてのフックをログに記録 | lifecycle-logger | ||
| フォローアップ、権限、環境、プロバイダーの切り替え、ワークツリー | lifecycle-actions | ||
| MCP サーバーの注入と Codex サンドボックス/承認オプションの変更 | agent-configuration |
プロジェクトを作成する
デーモンマシン上の絶対パスを使用してください。init はファイルを書き込みますが、パッケージはインストールしません。
paseo plugin init /absolute/path/to/my-plugin
cd /absolute/path/to/my-plugin
npm install
生成されたプロジェクトには、次のものが含まれます。
my-plugin/
paseo-plugin.json
OVERVIEW.md
package.json
tsconfig.json
index.client.tsx
index.server.ts
client/greeting.tsx
server/greeting.ts
shared/greeting.ts
マニフェストには、デフォルトのインストール ID と、サポート対象の Paseo バージョンが記載されています。
{ "id": "my-plugin", "requirements": { "paseo": ">=0.8.0" } }
プラグインを作成または編集する際は、requirements.paseo を正しく保ってください。init は CLI バージョンに続けて >= を使用します。新しい API を採用する場合は最小バージョンを引き上げ、後続の Paseo リリースとの互換性がない場合は上限を追加してください。npm の semver 範囲を使用し、ベータ版を対象にする場合はベータ版を明示的に含めてください。要件がない場合は <0.8.0 と見なされます。>=0.8.0 を追加する前に、0.8 エントリへの移行を完了してください。デーモンと、クライアント機能を実行するアプリの両方で互換性を確認してください。要件を参照してください。
各ランタイムには、それぞれ任意のエントリを指定できます。プラグインには少なくとも 1 つのエントリが必要です。どちらのエントリも .ts または .tsx を使用できます。コンポーネントをインポートするエントリには .tsx を使用してください。
| パス | ランタイム |
|---|---|
index.client.tsx と client/ | アプリ |
index.server.ts と server/ | デーモンのサブプロセス |
shared/ | 両方 |
プラグインのルートには、ほかのコードモジュールを置かないでください。
クライアントからの server/ のインポート、サーバーからの client/ のインポート、クライアントコードから到達可能なすべての node: インポートは、コンパイルエラーになります。プラグインルート内の別のコードファイルへの相対インポートもコンパイルエラーになります。その場合は、client/、server/、または shared/ に移動してください。共有モジュールには Zod のコントラクトとプレーンな値を含めます。Node や React Native のランタイム API はインポートしません。
各エントリから 1 つのコントリビューション関数をデフォルトエクスポートし、クリーンアップ関数を返してください。
// index.client.tsx
import type { PluginClientContext } from "@getpaseo/plugin/client";
export default function contribute(client: PluginClientContext) {
// Register components and client callbacks here.
return () => {};
}
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
export default function contribute(server: PluginServerContext) {
// Register daemon-side RPC handlers here.
return () => {};
}
クリーンアップは非同期にもできます。プラグインコードで作成したタイマー、ウォッチャー、ソケット、サブスクリプション、その他のリソースには、これを使用してください。クライアントの各 add* メソッドは、冪等な削除関数を返します。プラグインの停止時、Paseo はまずエントリのクリーンアップを呼び出し、残っている登録を削除し、保留中の RPC を拒否して、プラグインセッションを閉じ、サブプロセスを停止します。
ワークスペースパネルを追加する
ワークスペースパネルは、エージェント、ターミナル、ファイル、差分の隣に表示されます。プラグインはデスクトップとモバイルで実行され、Paseo には複数のテーマがあります。すべての Text で、色を theme.colors から指定する必要があります。余白や配置には layout.compact を使用してください。スタイルのないテキストは黒く表示されるため、ダークテーマでは正しく表示されません。
import {
type PluginClientContext,
type PluginWorkspacePanelProps,
useWorkspace,
} from "@getpaseo/plugin/client";
import { useMemo } from "react";
import { Text, View } from "react-native";
function Overview({ theme, layout, workspaceId }: PluginWorkspacePanelProps) {
const name = useWorkspace(workspaceId, (workspace) => workspace.name);
const styles = useMemo(
() => ({
screen: {
flex: 1,
padding: layout.compact ? 16 : 24,
gap: layout.compact ? 8 : 12,
backgroundColor: theme.colors.surface0,
},
title: { color: theme.colors.foreground, fontSize: layout.compact ? 20 : 24 },
}),
[theme, layout.compact],
);
return (
<View style={styles.screen}>
<Text style={styles.title}>{name}</Text>
</View>
);
}
export default function contribute(client: PluginClientContext) {
client.addWorkspacePanel({
id: "overview",
title: "Workspace overview",
icon: "PanelsTopLeft",
context: "workspace",
Component: Overview,
});
client.addCommandCenterItem({
id: "open-overview",
title: "Open workspace overview",
icon: "PanelsTopLeft",
context: "workspace",
onSelect({ openPanel }) {
openPanel("overview");
},
});
return () => {};
}
useWorkspace(id, selector) と useAgent(id, selector) を使用してください。セレクターは必須で、結果には浅い等価性が使用されます。スナップショット全体を選択したり、アクティブなワークスペースやエージェントを取得するための RPC を追加したりしないでください。コマンドのコールバックには、選択されたホストの paseo、型付きの rpc(contract, input)、openScreen({ screenId, params? })、およびコンテキストに応じた openPanel(id) の各機能が渡されます。
画面とサイドバー項目を追加する
プラグインの画面では React Native のプリミティブを使用し、デスクトップ、ブラウザー、iOS、Android で動作します。ヘッダーまたはフッターに置くサイドバー項目はコンポーネントです。@getpaseo/plugin/client/ui から SidebarRow を 1 つ以上レンダリングし、グループ間に SidebarSeparator を置いて、そこから openScreen または openPopover を呼び出します。画面の title は文字列、または params を受け取る関数です。画面のコンポーネントには、開くときに渡された params が渡されます。テキストの色には theme.colors を、余白には layout.compact を使用します。
import type {
PluginClientContext,
PluginScreenProps,
PluginSidebarItemProps,
} from "@getpaseo/plugin/client";
import { SidebarRow } from "@getpaseo/plugin/client/ui";
import { useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native";
function Counter({ theme, layout }: PluginScreenProps) {
const [count, setCount] = useState(0);
const styles = useMemo(
() => ({
screen: {
flex: 1,
padding: layout.compact ? 16 : 24,
gap: 16,
backgroundColor: theme.colors.surface0,
},
count: { color: theme.colors.foreground, fontSize: layout.compact ? 36 : 48 },
button: { padding: 14, borderRadius: 10, backgroundColor: theme.colors.accent },
buttonText: { color: theme.colors.accentForeground, textAlign: "center" as const },
}),
[theme, layout.compact],
);
return (
<View style={styles.screen}>
<Text style={styles.count}>{count}</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Increment counter, currently ${count}`}
onPress={() => setCount((value) => value + 1)}
style={styles.button}
>
<Text style={styles.buttonText}>Count me in</Text>
</Pressable>
</View>
);
}
function CounterItem({ currentScreen, openScreen }: PluginSidebarItemProps) {
return (
<SidebarRow
icon="ListPlus"
active={currentScreen?.screenId === "main"}
onPress={() => openScreen({ screenId: "main" })}
/>
);
}
export default function contribute(client: PluginClientContext) {
client.addScreen({ id: "main", title: "Counter", Component: Counter });
client.addSidebarHeaderItem({ id: "main", title: "Counter", Component: CounterItem });
return () => {};
}
アイコンには Lucide のアイコン名、インライン SVG({ svg })、またはコンポーネントを使用できます。アイコンを参照してください。theme はすべての画面とパネルで使用できる、型付きの PluginTheme です。主要なテキストには theme.colors.foreground、ラベルには theme.colors.foregroundMuted、ルートビューには theme.colors.surface0 を使用します。モバイルと幅の狭いウィンドウでは layout.compact が true になります。ルート、ヘッダー、ホストピッカー、閉じるアクション、エラーバウンダリー、インストールごとのクエリクライアントは Paseo が管理します。
インポートを記述する前に、各モジュールを shared、client、server のいずれかに分類してください。推移的な依存関係と型の依存関係も含め、SDK のインポート境界に従ってください。ルートで使用できるのは shared のみです。フックとクライアントコンテキストは @getpaseo/plugin/client、サーバーコンテキストは /server、ホスト UI は /client/ui に配置します。型チェックのため、依存関係をローカルにインストールしてください。ホストのランタイムモジュールは Paseo が提供します。JSX では自動ランタイムを使用します。プラグインコードから /client/host をインポートしないでください。
モバイルで動作させる
プラグインの作業完了を報告する前に、次の項目を確認してください。
- React Native のプリミティブのみを使用してください:
View、Text、Pressable、ScrollView、TextInput。 - HTML 要素、
className、CSS 文字列、onClickは使用しないでください。 tsconfig.jsonに"DOM"を指定したり、/// <reference lib="dom" />を使用したりしないでください。DOM グローバルはclient/web.tsにのみ置き、そのモジュールで使うものだけを宣言してください。すべてのエクスポートでPlatform.OS === "web"を条件にし、ネイティブ環境での代替手段または no-op を用意してください。- 色には
theme.colorsを使用してください。 - コンパクトレイアウトを確認してください。
client/ に対して次の監査を実行してください。
rg -n "document\.|window\.|localStorage|navigator\.|<[a-z]+[ >]|className=|onClick=" client/
client/web.ts 以外で該当があれば、バグです。
正しい API を選ぶ
通常の Paseo 操作には既存の Paseo SDK を使用してください。プラグイン固有のバックエンド動作に限り、プラグイン RPC を使用してください。
サーフェスから Paseo を呼び出す
usePaseo() は、選択中のホストの現在の接続を借用します。サーフェス内で別のクライアントを作成しないでください。これはすべてのサーフェスで使うプラグイン唯一のクライアントです。クリーンアップ時にサブスクリプションを解除してください。残りの処理はプラグインのティアダウンで終了します。
import { usePaseo } from "@getpaseo/plugin/client";
function PullRequestAction() {
const paseo = usePaseo();
async function createReviewWorkspace() {
const workspace = await paseo.workspaces.create({
title: "Review PR 42",
source: {
kind: "worktree",
cwd: "/absolute/path/to/repository",
action: "checkout",
checkoutSource: { kind: "change_request", forge: "github", number: 42 },
},
});
await workspace.agents.create({
config: { provider: "codex/gpt-5.5" },
prompt: "Review PR #42.",
});
}
// Wire createReviewWorkspace to a Pressable.
return null;
}
API ではワークスペース、エージェント、プロバイダー、デーモン設定を扱えます。接続は Paseo が管理するため、接続のライフサイクルは含まれません。メソッドの詳細は、最新の SDK リファレンスを参照してください。
デーモン側の動作を追加する
shared/ に Zod コントラクトを 1 つ定義し、index.server.ts にサブプロセスハンドラーを登録して、クライアントコードから useRpc() で呼び出します。
// shared/greeting.ts
import { defineRpc } from "@getpaseo/plugin";
import { z } from "zod";
const greeting = defineRpc({
name: "greeting.create",
input: z.object({ name: z.string() }),
output: z.object({ message: z.string() }),
});
// server/greeting.ts
import type { RpcInput } from "@getpaseo/plugin";
import { greeting } from "../shared/greeting";
export async function createGreeting({ name }: RpcInput<typeof greeting>) {
return { message: `Hello, ${name}!` };
}
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { createGreeting } from "./server/greeting";
import { greeting } from "./shared/greeting";
export default function contribute(server: PluginServerContext) {
server.handle(greeting, createGreeting);
return () => {};
}
// client/greeting.tsx
import { useRpc } from "@getpaseo/plugin/client";
import { greeting } from "../shared/greeting";
function Greeting() {
const createGreeting = useRpc(greeting);
// Use createGreeting({ name: "Ada" }) in a query, mutation, or event.
return null;
}
入力と出力は両側で検証されます。バックエンドハンドラーには、同じ PaseoApi が { paseo } として渡されます。IPC を介するデーモンセッションは、サブプロセスとまったく同じ期間だけ存続します。バックエンドコードでは Node API とインストール済みの依存関係を使用できます。認証情報、ファイルシステムへのアクセス、シェルコマンド、ベンダー API の呼び出しは、クライアントサーフェスではなくハンドラーに置いてください。
非同期リクエストの状態管理、キャッシュ、ミューテーションには TanStack Query を使用してください。
デーモン側の動作をデバッグする
バックエンドへのコントリビューションでは、通常の Node ロギングを使用できます。console.log() はプラグインの標準出力に書き込み、console.error() は標準エラー出力に書き込みます。Paseo はプラグインの IPC に干渉することなく、両方のストリームをキャプチャします。
インストール後、再読み込み後、RPC の失敗後、またはサブプロセスのクラッシュ後に、最近の出力を確認します。
paseo plugin logs my-plugin
paseo plugin logs my-plugin --json
paseo plugin logs my-plugin --host <url>
同じログの末尾は、設定 → プラグイン → ログからも確認できます。初期化、ハンドラー、クリーンアップ、クラッシュ時の最終出力が含まれます。再読み込み、無効化、プロセスの失敗後もログの末尾は保持されます。プラグインを削除するとログは消去され、デーモンを再起動するとメモリ内のログは消去されます。構造化されたログのコピーもデーモンのログに出力されます。認証情報やその他の秘密情報は決してログに記録しないでください。
コンポーザーの添付ファイルソースを追加する
shared/ に検索 RPC と宣言的なソースを定義し、サーバー側で処理して、クライアントに登録します。
// shared/issues.ts
import { defineAttachmentSource, defineRpc } from "@getpaseo/plugin";
import { z } from "zod";
const searchIssues = defineRpc({
name: "issues.search",
input: z.object({ query: z.string() }),
output: z.object({
items: z.array(
z.object({
id: z.string(),
identifier: z.string(),
title: z.string(),
subtitle: z.string().optional(),
url: z.string().url(),
text: z.string(),
resourceType: z.string(),
}),
),
}),
});
const issues = defineAttachmentSource({
id: "issues",
title: "Acme issue",
icon: "CircleDot",
pickerTitle: "Attach Acme issue",
searchPlaceholder: "Search by identifier or title",
search: searchIssues,
});
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { searchIssues } from "./shared/issues";
export default function contribute(server: PluginServerContext) {
server.handle(searchIssues, ({ query }) => searchAcmeIssues(query));
return () => {};
}
// index.client.tsx
import type { PluginClientContext } from "@getpaseo/plugin/client";
import { issues } from "./shared/issues";
export default function contribute(client: PluginClientContext) {
client.addAttachmentSource(issues);
return () => {};
}
完全なテキストのスナップショットを返します。コンポーザーのメニュー、ピッカー、ピル、下書き、送信は Paseo が管理します。認証情報とベンダーへの呼び出しはデーモンハンドラーに置いてください。
クライアントのスラッシュコマンドを追加する
スラッシュコマンドは、ユーザーが /name args を送信すると、アプリ内でプラグインコードを実行します。エージェントには何も送信されません。args はコマンド名の後に続く未加工のテキストで、前後の空白が取り除かれます。プラグイン内で解析してください。
client.addSlashCommand({
name: "review",
description: "Run the review bot",
argumentHint: "[scope]",
context: "agent", // or "workspace" so drafts get it too
async onSubmit({ args, agent, rpc, openPanel }) {
await rpc(startReview, { agentId: agent.id, scope: args });
openPanel("review");
},
});
コールバックには、一致する Command Center 項目と同じコンテキストに加えて、args が渡されます。オートコンプリートの行、入力のクリア、エラートーストは Paseo が管理します。処理中の UI はピルまたはパネルに表示してください。優先順位は、組み込みのクライアントコマンド、プラグインコマンド、プロバイダーコマンドの順です。優先順位の低いコマンドとの競合は除外されます。コンポーザーに添付ファイルがある間は、コマンドは実行されません。サーバー側のスラッシュコマンドはありません。
コンポーザーピルを追加する
ピルは、タスクやサブエージェントの隣にあるコンポーザーのトラックバーに表示される、エージェントごとのボタンです。クライアントエントリーのライフサイクルでピルを追加・削除します。addComposerPill は PluginClientContext で使用できます。
import type { PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";
export function contributeClient(client: PluginClientContext) {
const pills = new Map<string, PluginButtonRegistration>();
const lifetime = new AbortController();
const register = (agent: { id: string; workspaceId?: string | null }) => {
if (lifetime.signal.aborted || !agent.workspaceId) return;
const agentId = agent.id;
const workspaceId = agent.workspaceId;
pills.get(agentId)?.remove();
pills.set(
agentId,
client.addComposerPill({
id: "review",
workspaceId,
agentId,
button: {
title: "Open review",
icon: "Scan",
label: "Review",
behavior: {
kind: "action",
onPress() {
client.openPanel("review", { workspaceId, agentId });
},
},
},
}),
);
};
const removeAll = () => {
for (const pill of pills.values()) pill.remove();
pills.clear();
};
// An owned list subscription delivers existing agents, then updates, and a fresh snapshot after a reconnect.
void client.paseo.agents
.list({ subscribe: {}, signal: lifetime.signal })
.then(({ subscription }) => {
subscription.subscribe({
snapshot: ({ entries }) => {
removeAll();
for (const { agent } of entries) register(agent);
},
update: (message) => {
if (message.type !== "agent_update") return;
const update = message.payload;
if (update.kind === "upsert") return register(update.agent);
pills.get(update.agentId)?.remove();
pills.delete(update.agentId);
},
});
return undefined;
})
.catch((error) => {
if (!lifetime.signal.aborted) console.error("Agent observation failed", error);
});
return () => {
lifetime.abort();
removeAll();
};
}
index.client.tsx から contributeClient(client) を呼び出すか、その本体をエントリーポイントに移してください。button 記述子でアイコン、ラベル、動作を設定します。押下可能な要素、クローム、処理中の状態、エラー報告、配置は Paseo が管理します。addComposerPill は update() と remove() を備えた登録を返します。remove() は複数回呼び出しても安全です。プラグイン、クライアントのエントリーポイント、またはホスト接続が終了すると、Paseo はすべてのピルを削除します。
タイムライン項目を変換して表示する
タイムラインのトランスフォーマーとレンダラーは、クライアント側のコントリビューションです。トランスフォーマーは、組み込みの AgentTimelineItem.type を 1 つ選択して項目を調べ、バージョン付きのプラグイン項目を 0 個以上返します。undefined の場合は元の項目が維持され、items の場合は置き換えられ、[] の場合は削除されます。レンダラーは kind と version を 1 つずつ描画し、Zod スキーマで data を検証します。
client.addTimelineTransformer({
id: "inline-thinking",
query: { itemType: "reasoning" },
transform: ({ item, phase }) => ({
items: [
{ type: "plugin", kind: "inline-thinking", version: 1, data: { text: item.text, phase } },
],
}),
});
client.addTimelineRenderer({
kind: "inline-thinking",
version: 1,
schema: z.object({ text: z.string(), phase: z.enum(["streaming", "complete"]) }),
Component: InlineThinking,
});
トランスフォーマーは、取得した履歴とライブ更新のたびに、レンダーモデルの構築時に実行されます。そのため、読み込み中の思考や実行中のツール呼び出しでは、phase は "streaming" になります。ID は元の項目から引き継がれるため、ストリーミング中の項目ではマウント済みコンポーネントが維持されます。1 つのソース項目から複数の項目が生成される場合は、出力の id を設定してください。トランスフォーマーは同期的かつ決定的である必要があり、data は JSON でなければなりません。例外をスローしたトランスフォーマーはログに記録され、スキップされます。テンポを付けたアシスタントのテキストを表示するには、@getpaseo/plugin/client/ui の <StreamedText text={text} phase={phase} /> をレンダリングしてください。テキストの見た目をカスタマイズするには、style プロパティを使用します。plugin-examples/inline-thinking は思考の行をインラインテキストに置き換え、plugin-examples/timeline-items は todo ツール呼び出しをタスクカードに置き換えます。
デーモンからタイムライン行を追加する
サーバーハンドラーは、任意のエージェントのタイムラインにプラグイン所有の行を追加できます。同じレンダラー登録によって描画されます。
server.handle(publishReview, async ({ agentId, verdict }, { paseo }) => {
await paseo.agents.ref(agentId).timeline.append({
type: "plugin",
id: "review",
kind: "review-result",
version: 1,
data: { verdict },
});
return {};
});
デーモンはプラグインセッションから pluginId を設定するため、これを呼び出せるのはプラグインコードだけです。同じ id で再度追加すると、先の行が即時表示と再取得時の両方で置き換えられます。これがプラグインによる行の更新方法です。data はシリアライズ後に 64 KiB までに制限され、それを超えると拒否されます。行はデーモンのメモリ内タイムラインに保存され、スクロール、再取得、再接続後も保持されますが、デーモンの再起動後は保持されません。プラグインが見つからない行は、利用不可を示すプレースホルダーとして描画されます。ホストは server_info.features.pluginTimelineItems を通じて対応を通知します。
テーマを追加する
addTheme は、ライトまたはダークの小さなパレットを受け取り、Paseo がそれを完全なトークンセットに拡張します。すべての色は 16 進数文字列です。
client.addTheme({
id: "mocha",
name: "Catppuccin Mocha",
appearance: "dark",
colors: {
background: "#1e1e2e",
foreground: "#cdd6f4",
raised: "#313244",
control: "#45475a",
border: "#45475a",
accent: "#cba6f7",
mutedForeground: "#a6adc8",
ring: "#6c7086",
},
});
「設定」→「外観」に表示されます。addTheme より前のクライアントではエントリーを評価できず、client.addTheme is not a function と表示されるため、クライアントを更新してください。plugin-examples/catppuccin を参照してください。
ホストと信頼
プラグインはデーモンごとにインストールされ、信頼されたサンドボックス化されていないコードです。バックエンドコードは、デーモンマシン上のファイル、プロセス、認証情報、ネットワークサービスにアクセスできます。クライアント側の拡張機能は Paseo アプリ内で実行されます。ユーザーが許可していないプラグインや、確認していないソースコードはインストールしないでください。
インストール前にグローバルスイッチを確認する
対象のデーモンを特定し、config.json のルートにある pluginsEnabled の値を確認します。ローカルデーモンの場合、paseo daemon status --json にその home が表示されます。ファイルは <home>/config.json です。フィールドがない場合は false として扱います。個々のプラグインも無効化できるため、プラグインの disabled ステータスからグローバル値を推測しないでください。
pluginsEnabled がすでに true の場合は、ユーザーに有効化を求めずに続行してください。
false または未設定の場合は、編集や有効化を行う前に停止し、ユーザーに明示的な許可を求めてください。依頼には次の警告を含めてください。
プラグインは信頼されたサンドボックス化されていないコードです。バックエンドのプラグインコードは、ファイル、プロセス、認証情報、ネットワークサービスなど、デーモンマシン上のリソースにアクセスできます。クライアントのプラグインコードは Paseo アプリ内で実行されます。このデーモンでプラグインを有効にしてもよろしいですか?
ユーザーが同意するまで続行しないでください。許可を得た後は、次の手順を実行してください。
config.jsonの他の内容を保持し、ルートのpluginsEnabledフィールドをtrueに設定します。- そのデーモンに対して
paseo reload --jsonを実行します。 appliedPathsにpluginsEnabledが含まれていることを確認します。または、ファイルを再読み込みし、稼働中のプラグインカタログが有効であると確認できた場合に限り、空のappliedPathsを受け入れます。paseo plugin lsを実行し、インストール後に目的のプラグインがrunningになることを確認します。
ユーザーからグローバルスイッチを無効にするよう求められた場合は、pluginsEnabled を false に設定し、paseo reload --json を実行して、設定済みのプラグインが disabled と報告されることを確認してください。
対象がリモートデーモンの場合、ローカルの設定を編集しないでください。デーモンマシン上で編集するか、「設定」→「プラグイン」→「プラグインを有効にする」 を使用するようユーザーに依頼してください。paseo reload --host <url> はリモートデーモン自身のファイルを再読み込みしますが、ファイルの編集は行いません。
同じ画面またはサイドバー項目が複数の接続済みホストに存在する場合、Paseo はそれを 1 つだけ表示します。画面のヘッダーにはホスト選択メニューがあります。サイドバー項目では、表示中の画面のホストが使用されます。それがない場合は、そのプラグインの画面のいずれかで最後に選択したホストが使われます。選択されたホストがバンドル、SDK 呼び出し、RPC、クエリーキャッシュを所有します。選択されたホストがオフラインでも、別のホストには切り替わりません。添付ファイルのソースは、引き続きコンポーザーのホストに限定されます。
リスティングの公開
Paseo 内のプラグインページ用に、paseo-plugin.json と同じ場所に OVERVIEW.md を作成します。
このファイルは、プラグインをインストールするかどうかの判断に役立ちます。インストールコマンドはすでにページの先頭にあります。README は GitHub の読者を想定しており、インストール手順、技術的な詳細、バッジを掲載します。AI が生成した長い README では、プラグインの内容を把握するために、こうした情報を読み飛ばす必要が生じます。
レジストリへのプラグイン登録には OVERVIEW.md が必要です。ソースリポジトリの固定コミットで、paseo-plugin.json と同じ場所にコミットしてください。モノレポの場合、レジストリはマニフェストを基準に、pluginPath の下からこのファイルを解決します。公開する npm パッケージにも含めてください。スキャフォールドの files リストには含まれています。公開前に、スキャフォールドの説明コメントを役に立つ情報に置き換えてください。
リポジトリの概要は、レジストリへのインポート時の暫定的な代替手段より優先されます。固定コミットに OVERVIEW.md がない場合、オンライン検証は失敗します。ただし、レジストリリポジトリにすでに plugins/<owner>/<slug>.md があり、変更されていないインポート済みレコードは例外です。バージョンを更新するたびにリポジトリの概要が必要になり、同じ PR で暫定的な代替手段を削除します。リポジトリの概要を含まない更新は検証に失敗します。README は概要の代わりにはなりません。
作成者が書く概要と、レジストリの暫定的な代替文書は、次の順序で同じ内容要件に従います。役立つ場合にのみ見出しを選び、長さは内容の複雑さに合わせてください。テーマの場合は、段落を 1 つにします。
- まず、プラグインが何であり、何をするのかを平易な言葉で説明します。
- 仕組みが明らかでない場合に限り、動作の仕組みを説明します。
- 必要に応じて、設定、アカウント、トークン、プロバイダー、外部ツール、その他のプラグインなど、セットアップ方法を説明します。該当する場合は、デーモンのバージョンと OS の要件も含めます。セットアップの説明は可能ですが、インストール手順は含めないでください。
- 理解しておくべき機能や設定、それぞれのオプションの役割、プラグインが何を読み取り、どこに送信するか、権限、既知の制限を説明します。
文頭を大文字にする英語の見出し形式を避け、平易で事実に基づいた表現を使い、em ダッシュは使用しないでください。インストールコマンド、バッジ、変更履歴、コントリビューションやライセンスのセクション、宣伝文句、裏付けのない主張は省きます。空のクリーンアップ関数、テーマトークンのフィールド一覧、存在しない機能の一覧など、実装に関する埋め草は避けてください。プラグインを選ぶ際に役立つ情報だけを残します。
変更されていないインポート済みレコードの場合は、レジストリの暫定的な代替文書を置き換えるために、作成者が概要を提案できます。自分の OVERVIEW.md にインポート時のクレジットを追加しないでください。
リスティングページについてを参照してください。
型チェックと管理
プラグインを編集したら、インストールまたは再読み込みの前にソースを型チェックします。
npm run typecheck
paseo plugin install /absolute/path/to/plugin
paseo plugin install /absolute/path/to/plugin --id another-runtime-id
paseo plugin install npm:@acme/paseo-review
paseo plugin install npm:@acme/[email protected]
paseo plugin install github:owner/repository
paseo plugin install github:owner/repository --ref main
paseo plugin update my-plugin
paseo plugin ls
paseo plugin reload my-plugin
paseo plugin logs my-plugin
paseo plugin disable my-plugin
paseo plugin enable my-plugin
paseo plugin remove my-plugin
npm を使う場合は、デーモンの PATH に npm が含まれていることを確認してください。そのホストのレジストリ設定と認証情報を使用します。インストールセレクターがコンテンツを選択するのは一度だけであり、更新は固定されません。install と add はエイリアスです。Paseo のパッケージ内容と準備要件については、公開ガイドに従ってください。公開には標準の npm コマンドを使用します。
CLI のデフォルト以外のデーモンを管理する場合は、--host <url> を使用します。インストールや生成が必要な Git ソースは、paseo-plugin.json で build を argv 配列のリストとして宣言します。Paseo はインストール時と更新時にシェルを介さずにコマンドを実行し、いずれかが失敗した場合は以前のバージョンを維持します。プラグインのソースを編集した場合は paseo plugin reload が必要です。グローバルスイッチの設定を変更した場合は paseo reload が必要です。プラグインの再読み込みに失敗した場合、状態は失敗のままです。paseo plugin ls で読み込みエラーを、paseo plugin logs <id> でサブプロセスの出力を確認し、ソースを修正して型チェックを行い、もう一度再読み込みしてください。remove はローカルのソースディレクトリを保持し、管理対象の Git/npm インストールを削除します。
ソースの変更を読み込むためにデーモンを再起動しないでください。再起動すると、作業を実行しているエージェントが停止することがあります。
古い形式の混在エントリについては、スタンドアロンのruntime-entry 移行ガイドに手順どおり従ってください。
結果を確認する
変更後は、次の手順を実行します。
npm run typecheckを実行します。- 正確なランタイム ID をインストールまたは再読み込みします。
paseo plugin lsを実行し、エラーがなくrunningであることを確認します。- 対象のホストでコントリビューションを確認します。⌘K(macOS)または Ctrl+K(Windows/Linux)でコマンドセンターを開きます。スラッシュコマンドを使うには、コンポーザーに
/を入力します。タイムラインの作業では、ソースアイテムを生成するエージェントのターンを実行し、完了後だけでなく、ストリーミング中も確認します。UI の作業では、幅の広いデスクトップウィンドウとコンパクトなモバイルクライアントを確認し、テーマを切り替えて、テキストが引き続きforeground/foregroundMutedを使用していることを確認します。 - 変更したアクションまたは RPC を、エラー状態も含めてテストします。
よくある失敗:
- サイドバー項目が表示されない:ホストが間違っている、プラグインが
runningではない、項目が Settings > Sidebar で非表示になっている、またはコンポーネントが例外をスローしている(未知の Lucide 名や安全でない SVG を含むSidebarRowは例外をスローします)。 - クライアントモジュールを利用できない:クライアントバンドルでは、上記のホスト提供モジュールのみ使用できます。
- RPC が拒否される:入力または出力が Zod スキーマに適合しなかったか、ハンドラーが例外をスローしました。ハンドラーの出力は
paseo plugin logs <id>で確認します。 - プラグインが終了する、または再読み込みに失敗する:ステータスは
paseo plugin lsで、初期化、クリーンアップ、クラッシュの出力はpaseo plugin logs <id>で確認します。 - UI が古いままになる:
paseo plugin reload <id>を実行せずにソースを編集しました。 - タイムライン項目に「Plugin timeline item unavailable」と表示される:その
kindとversionに対応するレンダラーが登録されていない、レンダラースキーマがdataを拒否した、またはそのホストでプラグインが実行されていません。 - トランスフォーマーが効果を発揮しない:
query.itemTypeがソースの型と一致しない、変換処理がundefinedを返した、または例外がスローされてスキップされました。アプリのコンソールで[Plugins] Timeline transformer failedを確認します。 - スラッシュコマンドが候補に表示されない:名前が組み込み機能または別のプラグインと競合している、下書きのコンテキストが
agentである、またはコンポーザーに添付ファイルがあります。 - 追加が拒否される:呼び出し元がプラグインセッションではない、
dataが 64 KiB を超えている、またはホストがfeatures.pluginTimelineItemsの導入前のバージョンです。