@jmfederico/pi-web 1.202610.3 → 1.202610.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/client/assets/classic-CyM97Akm.css +54 -0
- package/dist/client/assets/graphite-DquaqFXU.css +54 -0
- package/dist/client/assets/index-DPS1kTH7.js +4389 -0
- package/dist/client/assets/paper-CB1oLuRr.css +54 -0
- package/dist/client/assets/pi-web-dark-D_KtbM95.css +54 -0
- package/dist/client/assets/pi-web-light-DSNLMIBI.css +54 -0
- package/dist/client/index.html +2 -2
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -1
- package/dist/pi-packages/captains-log/dist/browser/index.js +13 -17
- package/dist/pi-packages/captains-log/dist/browser/panel.js +7 -11
- package/dist/pi-packages/relays/extensions/index.js +1 -0
- package/dist/pi-packages/relays/pi-web-plugin.js +21 -1
- package/dist/pi-packages/relays/relaysPanelElement.js +36 -32
- package/dist/pi-packages/workflows/browser/pi-web-plugin.js +35 -27
- package/dist/pi-packages/workflows/browser/worktreeDialog.js +156 -0
- package/dist/pi-packages/workflows/package.json +1 -0
- package/dist/pi-packages/workflows/prompts/worktree.md +11 -2
- package/dist/pi-packages/workflows/server-plugin.js +579 -0
- package/dist/pi-packages/workflows/vite.config.mjs +17 -0
- package/dist/pi-web-plugins/code-highlight/browser/pi-web-plugin.js +22 -0
- package/dist/pi-web-plugins/code-highlight/package.json +13 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +119 -162
- package/dist/pi-web-plugins/git/browser/git-panel.js +61 -46
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +25 -3
- package/dist/pi-web-plugins/git/git-backend.js +13 -1
- package/dist/pi-web-plugins/git/server-plugin.js +8 -1
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +8 -3
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +50 -29
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +11 -11
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +21 -1
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +35 -19
- package/dist/piWebUpdate.js +32 -9
- package/dist/piWebUpdate.js.map +1 -1
- package/dist/plugin-api.d.ts +174 -45
- package/dist/server/browserMessageProjection.js +1 -1
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +7 -1
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +3 -2
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/piWebPluginService.js +7 -1
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +31 -38
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendProxyRoutes.js +38 -45
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +58 -34
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -1
- package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +21 -8
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +24 -14
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginBackendRoutes.js +51 -47
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginCatalogRoutes.js +37 -0
- package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +17 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond.js +24 -4
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +1 -0
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +13 -0
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/builtinExtensionFactories.js +15 -1
- package/dist/server/sessions/builtinExtensionFactories.js.map +1 -1
- package/dist/server/sessions/companionBackend.js +286 -0
- package/dist/server/sessions/companionBackend.js.map +1 -0
- package/dist/server/sessions/mcpAuthFlowService.js +237 -0
- package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
- package/dist/server/sessions/mcpAuthRoutes.js +76 -0
- package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
- package/dist/server/sessions/messageHistoryTarget.js +18 -1
- package/dist/server/sessions/messageHistoryTarget.js.map +1 -1
- package/dist/server/sessions/piSessionEventConnections.js +5 -2
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +279 -22
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +3 -17
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +4 -4
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +1 -0
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server-plugin-api.d.ts +65 -15
- package/dist/server-plugin-api.js +25 -0
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +17 -5
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +53 -0
- package/dist/shared/pluginBackendProtocol.js +56 -1
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginCatalogTypes.d.ts +49 -0
- package/dist/shared/pluginCatalogTypes.js +156 -0
- package/dist/shared/pluginCatalogTypes.js.map +1 -0
- package/docs/config.md +68 -2
- package/docs/plugins.md +382 -20
- package/examples/session-bridge-plugin/src/browser/index.ts +1 -1
- package/package.json +7 -12
- package/dist/client/assets/index-BAl5IbYx.js +0 -4429
package/docs/plugins.md
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PI WEB plugin implementation reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is the documentation entry point for agents and developers building PI WEB plugins. For a short overview of possibilities and installation, use the [plugin guide](https://pi-web.dev/plugins).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Start here
|
|
6
|
+
|
|
7
|
+
1. Read [public contracts and examples](#where-to-implement). Use declarations matching the installed PI WEB version; source links track development on `main`.
|
|
8
|
+
2. Choose the smallest example that fits the requested workflow. Use supported plugin APIs rather than modifying PI WEB or calling private routes.
|
|
9
|
+
3. Follow the relevant sections below for behavior, compatibility, and lifecycle requirements.
|
|
10
|
+
4. Test the requested behavior, failure paths, and cleanup. Explain installation, reload/restart steps, and any permissions or model use.
|
|
11
|
+
|
|
12
|
+
### Find the relevant guidance
|
|
13
|
+
|
|
14
|
+
- [Capabilities and possible contributions](#what-can-be-built)
|
|
15
|
+
- [Browser/server roles and feature behavior](#how-plugins-work)
|
|
16
|
+
- [Installation and local development](#install-and-manage)
|
|
17
|
+
- [Remote machines](#remote-machines)
|
|
18
|
+
- [Contracts, examples, and design boundaries](#agent-development)
|
|
19
|
+
- [Shared UI styles](#host-provided-ui-styles), [CSS integration](#css-files-and-shadow-roots), and [themes](#css-first-themes)
|
|
20
|
+
- [Read-only catalogs and scoped backends](#read-only-catalogs-and-scoped-backends-v5)
|
|
21
|
+
- [Trust and recovery](#trust-and-recovery)
|
|
6
22
|
|
|
7
23
|
## What can be built
|
|
8
24
|
|
|
@@ -16,6 +32,7 @@ This guide explains what is possible and what to expect. For implementation, use
|
|
|
16
32
|
| Run builds, tests, or development servers | Workspace terminal commands |
|
|
17
33
|
| Customize the appearance | Themes and light/dark theme pairs |
|
|
18
34
|
| Preview diagrams or other text formats in chat and Files | Browser content renderers |
|
|
35
|
+
| Highlight additional programming languages in Files and chat | Syntax highlighters |
|
|
19
36
|
| Read or change workspace files | File listing, reading, writing, moving, deleting, and uploads |
|
|
20
37
|
| Show live backend results | Requests or streaming channels between a plugin's browser and server entries |
|
|
21
38
|
| Save plugin-owned results or preferences | A persistent server-side plugin directory |
|
|
@@ -115,12 +132,28 @@ Older hosts may ignore message actions or lack newer display-action features; up
|
|
|
115
132
|
|
|
116
133
|
### Content previews in chat and Files
|
|
117
134
|
|
|
118
|
-
Plugins can
|
|
135
|
+
Plugins can render source as highlighted code, diagrams, or other presentations in chat code blocks, Markdown files, and standalone Files previews. Manual rendering is the default for contributions; plugins may opt into automatic rendering. Every matching renderer has a named header button alongside **Raw**. Clicking a view button selects and activates it, including manual renderers. The buttons stay on one horizontally scrollable row when space is limited.
|
|
119
136
|
|
|
120
|
-
|
|
137
|
+
The last matching automatic renderer in plugin discovery order is the default. Bundled plugins are discovered before installed/project plugins; asynchronous import completion does not change precedence. Later manual renderers do not take over. An explicit renderer choice or **Raw** takes precedence over automatic defaults. **Raw** is always available as unhighlighted source, including when rendering fails.
|
|
138
|
+
|
|
139
|
+
**Raw** shows source; **Copy** copies source even while a rendered view is visible. Files puts the view buttons beside **Download** and remembers your Raw/rendered preference; rendered Markdown also permits manual renderers in its code blocks. Chat choices are temporary and clear on reload.
|
|
121
140
|
|
|
122
141
|
Incomplete, truncated, or oversized input is not rendered. Files also limits the size of inline previews. Failed previews fall back to source. Renderers must treat text as untrusted, sanitize output, and avoid executing source. For implementation, see the [Files consumer](https://github.com/jmfederico/pi-web/blob/main/pi-web-plugins/files/pi-web-plugin.ts) and [Mermaid contribution](https://github.com/jmfederico/pi-web/tree/main/pi-web-plugins/mermaid).
|
|
123
142
|
|
|
143
|
+
### Highlight additional languages
|
|
144
|
+
|
|
145
|
+
The bundled **Code highlighting** plugin supplies one **Highlighted** renderer, offered only for languages it supports. Browser v5 plugins can extend it with `syntaxHighlighters` for any language, including languages that PI WEB does not recognize on the server. Match chat fences with `languages` (including aliases) and files with `fileExtensions` (without a leading dot). Adding a grammar expands this renderer's coverage; it does not add a separate renderer choice. Highlighting is available in completed chat fences, Markdown files, and standalone Files previews. **Raw** stays unhighlighted.
|
|
146
|
+
|
|
147
|
+
Use any tokenizer or grammar bundled with your plugin. The `highlight({ text, signal })` callback returns tokens synchronously or asynchronously: `{ from, to, kind }`, with UTF-16 offsets into the unchanged source. Ranges must be ordered, non-overlapping, non-empty, and within the source. Unmarked text remains plain. Kinds are `keyword`, `string`, `number`, `comment`, `type`, `function`, `variable`, `operator`, `punctuation`, `property`, `literal`, or `meta`; PI WEB supplies theme-aware colours. Return tokens, not HTML or CodeMirror extensions, so independently bundled parsers do not need to share an editor-library instance with the host.
|
|
148
|
+
|
|
149
|
+
Plugins should stop asynchronous work when `signal` is aborted. Oversized input is not sent to highlighters. A failed highlighter leaves source readable and reports the failure. If several grammar contributions match, the last discovered one wins. Highlighting adds no editing, completion, language-server support, or plugin-defined styling.
|
|
150
|
+
|
|
151
|
+
A plugin can instead provide an independent highlighting system as a `contentRenderers` contribution. Both highlighting renderers remain separate choices when they support the source; the same last-automatic-renderer rule chooses the default. A v5 renderer's `supports(request)` callback can determine eligibility dynamically, and `title` names its view button—for example, **Highlighted** or **Diagram**. Without a title, the host uses the plugin and contribution IDs. V5 rendering receives the source language, file path, and machine as well as the text; v4 renderer inputs retain their original text, signal, and failure callback. The bundled highlighting plugin demonstrates this pattern.
|
|
152
|
+
|
|
153
|
+
The bundled Mermaid plugin contributes both a **Diagram** renderer and a source grammar for `mermaid` fences and `.mmd`/`.mermaid` files. Its grammar adds **Highlighted** to those sources without loading the diagram engine. Diagram rendering stays manual; **Raw**, **Highlighted**, and **Diagram** retain the same source text. The lightweight grammar colours common Mermaid syntax; it does not validate diagrams.
|
|
154
|
+
|
|
155
|
+
Older hosts ignore syntax-highlighter contributions; require the host capability `{ pluginId: "pi-web", id: "syntax-highlighting", version: 1 }` when your plugin depends on this feature. This capability exposes `supports()` and `highlight()` for renderers that consume semantic token contributions. See `SyntaxHighlighterContribution`, `SyntaxHighlightingCapability`, and `ContentRendererContributionV5` in the [public browser contract](https://github.com/jmfederico/pi-web/blob/main/src/plugin-api.ts).
|
|
156
|
+
|
|
124
157
|
### Conversations and companion extensions
|
|
125
158
|
|
|
126
159
|
A server plugin can create a normal, visible Pi conversation, with or without an initial prompt. A companion Pi extension can exchange messages with the backend and use Pi's own APIs to do agent work.
|
|
@@ -129,6 +162,38 @@ A server plugin can create a normal, visible Pi conversation, with or without an
|
|
|
129
162
|
|
|
130
163
|
A messaging connection targets a session already hosted on that machine; it does not open saved sessions automatically. Sending a message is not proof that a companion is installed or that its work succeeded. Integrations must report their own progress and results. Connections have no startup-message replay, and closing a connection does not stop agent work.
|
|
131
164
|
|
|
165
|
+
### Companion tools calling a v5 backend
|
|
166
|
+
|
|
167
|
+
Companion Pi extensions can call the same v5 `peer` handlers as a plugin's browser entry. Import `createCompanionBackend` from `@jmfederico/pi-web/server-plugin-api`; no additional server handler, HTTP listener, or browser panel is needed.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
171
|
+
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
172
|
+
import { createCompanionBackend } from "@jmfederico/pi-web/server-plugin-api";
|
|
173
|
+
|
|
174
|
+
export default function (pi: ExtensionAPI) {
|
|
175
|
+
pi.registerTool(defineTool({
|
|
176
|
+
name: "save_bookmark",
|
|
177
|
+
label: "Save bookmark",
|
|
178
|
+
description: "Save a bookmark through the bookmarks plugin",
|
|
179
|
+
parameters: Type.Object({ url: Type.String(), title: Type.String() }),
|
|
180
|
+
async execute(_id, input, signal) {
|
|
181
|
+
const backend = createCompanionBackend(pi.events, "bookmarks");
|
|
182
|
+
const machine = backend.forScope({ kind: "machine" });
|
|
183
|
+
if (machine.request === undefined) throw new Error("Bookmarks backend does not support requests");
|
|
184
|
+
const result = await machine.request("save", input, signal === undefined ? {} : { signal });
|
|
185
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
186
|
+
},
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use the package manifest's stable PI WEB plugin ID and obtain the backend during tool execution or `session_start`, not while the extension factory loads. The helper returns the existing v5 backend interface, with `machineId: "local"` meaning the hosted session's machine, regardless of browser selection. `forScope` accepts machine scope, `{ kind: "project", projectId }`, or `{ kind: "workspace", projectId, workspaceId }`; the host resolves IDs rather than trusting extension-supplied paths. Available request/channel methods follow the active v5 peer's capabilities. Normal JSON bounds, deadlines, cancellation, and revision checks apply; this helper does not add remote transport or retry failed operations.
|
|
192
|
+
|
|
193
|
+
The matching v5 server entry must be enabled and healthy enough to serve requests. Discovery before daemon backend initialization finishes rejects explicitly. Retained facades stay bound to that backend revision and concrete hosted session; closing or replacing it revokes access and cancels pending work. Native extension reload needs no host reconnect; extensions should still close their own channels during `session_shutdown`.
|
|
194
|
+
|
|
195
|
+
Outside a supporting PI WEB host, discovery throws rather than falling back to HTTP. Plugin authors decide whether to offer CLI behavior, handle that error, or expose the tool only in PI WEB. Extensions are trusted in-process code; the helper is not a new permission boundary. Update PI WEB and manually restart its session daemon before using the helper in existing deployments.
|
|
196
|
+
|
|
132
197
|
### Storage and background work
|
|
133
198
|
|
|
134
199
|
Server plugins receive a persistent `dataDirectory`, separate from installed package code. The directory is shared across that plugin's projects on the machine. Plugins own their data format, migrations, and cleanup; there is no host storage API to learn.
|
|
@@ -149,6 +214,10 @@ pi-web workspaces notify --project ID --plugin git
|
|
|
149
214
|
|
|
150
215
|
`ID` is the registered project ID, not a filesystem path. `--plugin` identifies the provider (`git` here), which must own the project and accept notifications. Connected browsers re-read the workspace list; the command does not register projects or workspaces.
|
|
151
216
|
|
|
217
|
+
## Find community plugins
|
|
218
|
+
|
|
219
|
+
[Find community plugins on the website](https://pi-web.dev/community-plugins). Browse public npm packages tagged with `pi-web.dev-plugin` or `pi-web-plugin`, then install packages you trust using the steps below. Listings are community-contributed, not reviewed or endorsed. Plugin authors can follow the website's publishing instructions to make their packages discoverable.
|
|
220
|
+
|
|
152
221
|
## Install and manage
|
|
153
222
|
|
|
154
223
|
Use **Settings → Pi packages** to install, update, or remove a package. Enter its npm, git/URL, or local package source. Use **Settings → PI WEB plugins** to enable or disable its web integration and see whether it is active or needs a restart.
|
|
@@ -195,6 +264,7 @@ Keep gateways and targets compatible. During this plugin API transition, upgrade
|
|
|
195
264
|
- **Files** supplies file browsing, previews, and uploads. Disabling its panel does not remove other plugins' file helpers.
|
|
196
265
|
- **Git** discovers Git workspaces and provides status/diff, including [automatic discovery of new worktrees](#automatic-git-worktree-discovery) on supported Git builds. Disabling it leaves the project-folder workspace available unless another provider takes over.
|
|
197
266
|
- **Mermaid** uses the default manual mode: choose **Render** to preview `mermaid` fences and `.mmd`/`.mermaid` text files. Its bundled engine runs locally in an opaque-origin sandbox with network access blocked; no diagram service receives your source. Interactive links and external resources are intentionally unavailable. Disable Mermaid in plugin Settings to keep plain code rendering. Only a browser reload is needed after changing this browser-only plugin.
|
|
267
|
+
- **Code highlighting** colours completed chat code fences in common languages automatically, including JavaScript/TypeScript, Python, shell, HTML/CSS, JSON, Go, Rust, SQL, and diff. Choose **Raw** to view plain source or **Preview** to restore colours; Copy always copies the source. Unsupported, unlabelled, incomplete, and oversized blocks stay plain. Highlighting runs locally without executing code. Disable Code highlighting in plugin Settings to keep plain code rendering; reload the browser after changing this browser-only plugin.
|
|
198
268
|
- **Info** displays machine and PI WEB status without requiring a workspace, adds workspace details when selected, and provides copyable diagnostics.
|
|
199
269
|
- **Updates** shows update/restart guidance when relevant and offers a manual update check. Guidance and Copy work without a workspace; Run requires a selected workspace with an available Terminal provider on that machine.
|
|
200
270
|
- **Workspace Tasks** turns project commands into runnable buttons.
|
|
@@ -227,7 +297,11 @@ Open the **Tasks** tab to run a command in a workspace terminal. Tasks can also
|
|
|
227
297
|
|
|
228
298
|
### Workflows
|
|
229
299
|
|
|
230
|
-
Workflows is a shipped Pi package for PI WEB's opinionated prompts and shortcuts.
|
|
300
|
+
Workflows is a shipped Pi package for PI WEB's opinionated prompts and shortcuts. **Create Worktree** opens a dialog for the base branch and purpose or description. The base defaults to the main workspace's current branch, or `HEAD` when detached; local and remote branches are suggested. Submitting starts a fresh conversation in the selected project's main workspace and submits `/worktree` with the details. The action is a shortcut for the same prompt template used by the command, not a separate agent workflow. You can invoke it from another worktree or without a selected conversation; your current conversation and draft remain untouched.
|
|
301
|
+
|
|
302
|
+
The action requires the Workflows server plugin and API v5. It uses public project/workspace discovery and session capabilities, including on remote machines. If dispatch cannot be confirmed, inspect Sessions before trying again; the dialog does not resend automatically. The agent creates the worktree, so opening its conversation is not confirmation that creation has finished.
|
|
303
|
+
|
|
304
|
+
You can also use `/worktree` directly in a conversation with the free-form hint **Purpose and/or base branch**:
|
|
231
305
|
|
|
232
306
|
```text
|
|
233
307
|
/worktree fix login redirects
|
|
@@ -236,7 +310,7 @@ Workflows is a shipped Pi package for PI WEB's opinionated prompts and shortcuts
|
|
|
236
310
|
|
|
237
311
|
The prompt asks the agent to create a worktree named for your purpose, using the requested base or the current worktree's HEAD. If you give no purpose or description, the agent asks for one.
|
|
238
312
|
|
|
239
|
-
PI WEB installs Workflows automatically for the active agent profile at session-daemon startup. Its web plugin is enabled by default. Disable **Workflows** in **Settings → PI WEB plugins
|
|
313
|
+
PI WEB installs Workflows automatically for the active agent profile at session-daemon startup. Its web plugin is enabled by default. After installing or updating its server-backed web plugin, restart the target session daemon when safe and reload the browser. Disable **Workflows** in **Settings → PI WEB plugins** and restart the target daemon when safe to hide the shortcut while keeping `/worktree`. To remove its prompts as well, remove the package in **Settings → Pi packages** and run `/reload` in each idle conversation. Removal is remembered across daemon restarts; reinstall from **Available packages** if you change your mind, then reload idle conversations and the browser.
|
|
240
314
|
|
|
241
315
|
### Relays
|
|
242
316
|
|
|
@@ -271,7 +345,7 @@ These three dialog methods are supported; other Pi extension UI surfaces, such a
|
|
|
271
345
|
|
|
272
346
|
Pi extension commands can rewind with `ctx.navigateTree()` and fork with `ctx.fork()`, including follow-up work using the fresh `withSession` context. These operations run independently of browser connections. Rewinds refresh the viewed conversation; forks appear in Sessions for you to open. Other viewers keep their own selection and unsent drafts.
|
|
273
347
|
|
|
274
|
-
|
|
348
|
+
Extension tree commands preserve this browser's unsent draft; prepared text from these commands is not shown in the web UI. The web UI's **Go back** action restores a selected user message directly into the editor and asks for confirmation before replacing a nonempty draft. Going back to other messages preserves the draft without a replacement warning.
|
|
275
349
|
|
|
276
350
|
Extension idle/reload actions are supported. `ctx.newSession()` and `ctx.switchSession()` report that they are unsupported; use the web UI for those actions. Other terminal editor and widget APIs remain unsupported.
|
|
277
351
|
|
|
@@ -283,17 +357,7 @@ Live partial responses remain untransformed until completion, including after re
|
|
|
283
357
|
|
|
284
358
|
## Agent development
|
|
285
359
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
```text
|
|
289
|
-
Build a PI WEB plugin for this project.
|
|
290
|
-
Goal: <describe the workflow and expected UI>.
|
|
291
|
-
Read https://pi-web.dev/plugins.md, then follow its public-contract
|
|
292
|
-
and example links for the PI WEB version installed here.
|
|
293
|
-
Use supported plugin APIs; do not modify PI WEB or call private routes.
|
|
294
|
-
Explain installation, any permissions or model use, and how to reload it.
|
|
295
|
-
Test the behavior, including failures and cleanup.
|
|
296
|
-
```
|
|
360
|
+
Start with the public contracts and a working example below. Keep API and lifecycle details here; the [human-facing guide](https://pi-web.dev/plugins#agent-development) provides the prompt for requesting a plugin.
|
|
297
361
|
|
|
298
362
|
### Where to implement
|
|
299
363
|
|
|
@@ -310,7 +374,305 @@ Use the smallest example that fits. Source links below track development on `mai
|
|
|
310
374
|
| A simpler initial-prompt workflow | [Workspace Reviews example](https://github.com/jmfederico/pi-web/tree/main/examples/session-bridge-plugin) |
|
|
311
375
|
| Package discovery rules and diagnostics | [Plugin catalog](https://github.com/jmfederico/pi-web/blob/main/src/server/piWebPluginCatalog.ts) |
|
|
312
376
|
|
|
313
|
-
The current browser
|
|
377
|
+
The current browser and server contracts are both **API v5**. Browser v4 and server v3 remain supported with a once-per-plugin deprecation diagnostic; no removal date is set. Other versions are unsupported. Legacy entrypoint contracts stay frozen; new contribution fields and callback inputs belong to v5. Capabilities have independent versions: a compatible v4 browser or v3 server plugin can declare and resolve a new capability without changing its entrypoint version. The public source contracts and their tests are the reference for signatures, validation, limits, and lifecycle details.
|
|
378
|
+
|
|
379
|
+
### Host-provided UI styles
|
|
380
|
+
|
|
381
|
+
**Styling is experimental.** Shared classes, theme properties, visual design, and layout may change between releases. There is no stable styling contract yet; test custom styling when upgrading PI WEB.
|
|
382
|
+
|
|
383
|
+
Plugins can reuse the host's button, select, toolbar, tab, and file/tree-row styles without receiving CSS text or creating stylesheet elements. Request `{ pluginId: "pi-web", id: "stylesheet-integration", version: 1 }`, resolve it in `start`, and opt each participating shadow root into the single shared UI bundle. See [integrating plugin components](#integrating-plugin-components) for the complete capability setup.
|
|
384
|
+
|
|
385
|
+
For Lit, call once in the component constructor:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
styles.createController(this, { sharedUi: true });
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
For plain DOM, register on connection and release on disconnection:
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
const release = styles.registerRoot(shadowRoot, { sharedUi: true });
|
|
395
|
+
// On disconnect:
|
|
396
|
+
release();
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The host compiles one shared sheet on demand, adopts it before contributed theme/plugin sheets, and removes it when the last shared-UI registration for that root is released. The same sheet serves existing and later roots; theme changes do not remove these defaults. A registration without `sharedUi: true` receives contributed stylesheets only. The bundle contains shared primitives, not shell or chat layouts. No private client imports or additional plugin manifest fields are needed.
|
|
400
|
+
|
|
401
|
+
The current bundle provides these native HTML patterns:
|
|
402
|
+
|
|
403
|
+
| Pattern | Classes and states |
|
|
404
|
+
| --- | --- |
|
|
405
|
+
| Button | `button.ui-button`; optional `ui-button--compact`, `ui-button--icon`, `ui-button--primary`, or `danger`; native `disabled` |
|
|
406
|
+
| Select | `select.ui-select`; preserve its native label and change handling |
|
|
407
|
+
| Toolbar | `.ui-toolbar`, optionally `ui-toolbar--primary`; group controls with `.ui-toolbar-group` and label areas with `.ui-toolbar-title` |
|
|
408
|
+
| Trailing group boundary | Add `ui-toolbar-group--bounded` when a group meets empty bar space, not the bar's edge |
|
|
409
|
+
| Tabs | `.ui-tabs` containing `button.ui-button.ui-tab`; optionally `ui-tabs--primary` for main navigation; nested groups can use `.ui-toolbar-group` |
|
|
410
|
+
| Active tab | `.selected`, `aria-current="page"`, or `aria-pressed="true"` on the tab button |
|
|
411
|
+
|
|
412
|
+
Toolbars and tabs use compact 32px desktop controls by default. Primary variants use 44px controls; coarse-pointer targets grow to at least 44px. Icon-only buttons need an accessible name. Keep grouped controls gapless and let the shared styles provide separators and hover/focus states. Use existing `--pi-*` colour tokens in plugin-specific content styles so theme changes continue to propagate through shadow roots.
|
|
413
|
+
|
|
414
|
+
The shared bundle also provides compact file/tree lists:
|
|
415
|
+
|
|
416
|
+
```html
|
|
417
|
+
<div class="ui-file-list">
|
|
418
|
+
<button type="button" class="ui-button ui-file-row selected" style="--depth: 1" aria-current="page" title="src/main.ts">
|
|
419
|
+
<span class="ui-file-leading" aria-hidden="true"><!-- file icon or disclosure chevron --></span>
|
|
420
|
+
<span class="ui-file-label">main.ts</span>
|
|
421
|
+
<span class="ui-file-meta">M</span>
|
|
422
|
+
</button>
|
|
423
|
+
</div>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
File rows are gapless and **32px high for every pointer type**, intentionally remaining compact on touch devices too. The leading slot is 16px wide; integer `--depth` adds 16px indentation per level. `.ui-file-label` truncates long names; provide the full path through `title`. `.ui-file-meta` is optional trailing metadata. Use `.selected` or `aria-current="page"` for the active file, and `aria-expanded` on folder buttons. The Files and Git plugins use this pattern without sharing their controllers or viewer layout.
|
|
427
|
+
|
|
428
|
+
These are appearance patterns: the plugin still owns events, selection, keyboard navigation, and ARIA semantics. Styling a row as tabs does not implement an ARIA tab widget. Do not deep-import client source files. Menu, form, and dialog styles are not currently included in the shared bundle. You can target internal selectors through [contributed CSS files](#css-files-and-shadow-roots), but those selectors and markup may change between releases.
|
|
429
|
+
|
|
430
|
+
Stylesheet integration works with supported browser APIs v4 and v5. Its capability version describes the integration interface, not a stable set of CSS classes or a promise of unchanged appearance. Shared styling follows the running PI WEB frontend after reload. The earlier unreleased `ui-styles` CSS-text capability has been replaced by this root opt-in.
|
|
431
|
+
|
|
432
|
+
### CSS files and shadow roots
|
|
433
|
+
|
|
434
|
+
Contribute CSS files to customize the document and participating host or plugin shadow roots. This builds on the shared UI patterns above and inherited `--pi-*` custom properties; it does not require a separate option for each styling choice. Internal host classes, component names, and markup are **not stable selectors**: you may target them, but test your CSS when upgrading PI WEB. CSS changes presentation, not behavior or component structure.
|
|
435
|
+
|
|
436
|
+
With browser API v5, add `stylesheets` to the object returned by `activate().contributions`:
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
import type { PiWebPlugin } from "@jmfederico/pi-web/plugin-api";
|
|
440
|
+
|
|
441
|
+
export default {
|
|
442
|
+
apiVersion: 5,
|
|
443
|
+
name: "Custom appearance",
|
|
444
|
+
activate: () => ({
|
|
445
|
+
contributions: {
|
|
446
|
+
stylesheets: [{
|
|
447
|
+
id: "controls",
|
|
448
|
+
url: new URL("./controls.css", import.meta.url).href,
|
|
449
|
+
order: 1100,
|
|
450
|
+
}],
|
|
451
|
+
},
|
|
452
|
+
}),
|
|
453
|
+
} satisfies PiWebPlugin;
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
For example, `controls.css` can style native controls inside integrated roots:
|
|
457
|
+
|
|
458
|
+
```css
|
|
459
|
+
.ui-button {
|
|
460
|
+
border-radius: 6px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Relative to controls.css, not the page or JavaScript module. */
|
|
464
|
+
.my-plugin-logo {
|
|
465
|
+
background-image: url("./images/logo.svg");
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Each file has an `id` unique among its plugin's contribution IDs (or within its theme's stylesheet list), an absolute HTTP(S) `url`, and optional finite numeric `order` (default `1000`). Lower orders are adopted first; ties sort lexically by qualified ID (`plugin:file` or `plugin:theme:file`), independently of network completion. Normal CSS cascade, specificity, layers, and importance still apply. Contributed sheets follow a root's own adopted sheets; this is not a promise that every selector wins.
|
|
470
|
+
|
|
471
|
+
Ship the CSS and referenced assets under the plugin's browser-public root. Use module-relative URLs rather than hard-coded PI WEB routes so nested deployments work. Configure your bundler to emit a CSS **file**, not a JavaScript CSS module or data URL; with Vite, `new URL("./controls.css?no-inline", import.meta.url).href` prevents small files from becoming data URLs. Inline CSS contributions and runtime `@import` are unsupported: bundle imports into each contributed file before shipping. Relative resource URLs resolve against the final CSS response URL, including redirects. Fragment-only references such as `url(#filter)` remain local SVG references. Cross-origin files need the usual browser CORS permissions.
|
|
472
|
+
|
|
473
|
+
Plugin-level files apply while the plugin is active and eligible on the selected machine, after successful `start`. Files inside a theme apply only to the resolved selected theme. Theme changes, automatic light/dark switching, and plugin unload remove inactive sheets from every registered root; pending obsolete loads are cancelled. Each active URL is fetched and compiled once, shared across roots, and released when no active contribution references it. Existing and later-registered roots receive the same sheets.
|
|
474
|
+
|
|
475
|
+
Loading is asynchronous. Requests require a successful HTTP response with `Content-Type: text/css`, are limited to **15 seconds and 2 MiB per file**, and reject runtime imports or invalid resource URL syntax. Failures appear in the browser console as **Failed to apply plugin stylesheets**, with the contribution ID and URL in the error chain. Failed files do not prevent successful files, including the selected theme, from applying. The next publication retries failed files without reloading successful active files. Standard browser CSS parsing still governs individual invalid declarations.
|
|
476
|
+
|
|
477
|
+
#### Integrating plugin components
|
|
478
|
+
|
|
479
|
+
The document, host shadow roots, and bundled plugin-owned shadow roots participate already. External plugins choose their depth: register every root for direct rules, register just a top-level root and map inherited variables into internal or third-party components, or do not participate. A rule cannot cross another shadow boundary without participation at that root. Unregistered descendants can still inherit custom properties. There is no automatic root discovery or `attachShadow` patching.
|
|
480
|
+
|
|
481
|
+
Request the `stylesheet-integration` v1 capability, then pass its value to your components. It handles both contributed files and optional shared UI injection. The public import supplies types only; no private client import is needed.
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
import type {
|
|
485
|
+
PiWebPlugin, PluginCapability, StylesheetIntegrationCapabilityV1,
|
|
486
|
+
} from "@jmfederico/pi-web/plugin-api";
|
|
487
|
+
|
|
488
|
+
const stylesheetIntegration: PluginCapability<StylesheetIntegrationCapabilityV1> = {
|
|
489
|
+
pluginId: "pi-web", id: "stylesheet-integration", version: 1,
|
|
490
|
+
parse(value) {
|
|
491
|
+
if (typeof value !== "object" || value === null
|
|
492
|
+
|| !("registerRoot" in value) || typeof value.registerRoot !== "function"
|
|
493
|
+
|| !("createController" in value) || typeof value.createController !== "function"
|
|
494
|
+
|| !("subscribe" in value) || typeof value.subscribe !== "function") {
|
|
495
|
+
throw new Error("Stylesheet integration v1 unavailable");
|
|
496
|
+
}
|
|
497
|
+
return value as StylesheetIntegrationCapabilityV1;
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export default {
|
|
502
|
+
apiVersion: 5,
|
|
503
|
+
name: "Participating panel",
|
|
504
|
+
requires: [stylesheetIntegration],
|
|
505
|
+
activate({ html }) {
|
|
506
|
+
return {
|
|
507
|
+
start({ capabilities }) {
|
|
508
|
+
definePanel(capabilities.resolve(stylesheetIntegration));
|
|
509
|
+
},
|
|
510
|
+
contributions: {
|
|
511
|
+
workspacePanels: [{
|
|
512
|
+
id: "example", title: "Example",
|
|
513
|
+
render: () => html`<my-styled-panel></my-styled-panel>`,
|
|
514
|
+
}],
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
},
|
|
518
|
+
} satisfies PiWebPlugin;
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
For Lit, implement `definePanel` alongside that plugin and call `createController(this, { sharedUi: true })` once in the constructor. The controller registers after Lit creates the shadow root, releases it on disconnect, and registers again on reconnect:
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
import { LitElement, html } from "lit";
|
|
525
|
+
|
|
526
|
+
function definePanel(styles: StylesheetIntegrationCapabilityV1) {
|
|
527
|
+
if (customElements.get("my-styled-panel")) return;
|
|
528
|
+
customElements.define("my-styled-panel", class extends LitElement {
|
|
529
|
+
constructor() {
|
|
530
|
+
super();
|
|
531
|
+
styles.createController(this, { sharedUi: true });
|
|
532
|
+
}
|
|
533
|
+
render() {
|
|
534
|
+
return html`<button class="ui-button">Example action</button>`;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
For a framework-independent alternative, use `registerRoot(root, { sharedUi: true })` and keep its cleanup function. A retained closed root works too; open roots are not required:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
function definePanel(styles: StylesheetIntegrationCapabilityV1) {
|
|
544
|
+
if (customElements.get("my-styled-panel")) return;
|
|
545
|
+
customElements.define("my-styled-panel", class extends HTMLElement {
|
|
546
|
+
private readonly root = this.attachShadow({ mode: "closed" });
|
|
547
|
+
private releaseStyles: (() => void) | undefined;
|
|
548
|
+
|
|
549
|
+
constructor() {
|
|
550
|
+
super();
|
|
551
|
+
const button = document.createElement("button");
|
|
552
|
+
button.className = "ui-button";
|
|
553
|
+
button.textContent = "Example action";
|
|
554
|
+
this.root.append(button);
|
|
555
|
+
}
|
|
556
|
+
connectedCallback() {
|
|
557
|
+
this.releaseStyles?.();
|
|
558
|
+
this.releaseStyles = styles.registerRoot(this.root, { sharedUi: true });
|
|
559
|
+
}
|
|
560
|
+
disconnectedCallback() {
|
|
561
|
+
this.releaseStyles?.();
|
|
562
|
+
this.releaseStyles = undefined;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Use one of these `definePanel` implementations, not both. Pass the capability to nested participating components as well. If a framework injects it after connection or replaces it, release any old registration and register the current root with the new value. Do not create repeated Lit controllers on property updates. Releasing registrations on disconnect prevents the host from retaining abandoned roots; each independent registration needs its own cleanup. The bundled [Files components](https://github.com/jmfederico/pi-web/tree/main/pi-web-plugins/files) show delayed capability injection and nested Lit roots; [Relay](https://github.com/jmfederico/pi-web/tree/main/pi-packages/relays) uses plain DOM lifecycle hooks.
|
|
569
|
+
|
|
570
|
+
#### Reacting to stylesheet changes
|
|
571
|
+
|
|
572
|
+
Components that copy computed CSS values into a canvas or another rendering system can use the same capability's `subscribe` method:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
const releaseRoot = styles.registerRoot(root);
|
|
576
|
+
refreshFromComputedStyles();
|
|
577
|
+
const unsubscribe = styles.subscribe(() => refreshFromComputedStyles());
|
|
578
|
+
|
|
579
|
+
// On disconnect, unsubscribe before releasing the root.
|
|
580
|
+
unsubscribe();
|
|
581
|
+
releaseRoot();
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Listeners run synchronously after a changed active stylesheet list has been adopted in every registered root, including removal and successful files from a partly failed update. Initial subscription, root registration, unchanged publications, and obsolete loads do not notify. Read initial values after root registration. Listener failures are logged without blocking other subscribers. Release subscriptions on disconnect and subscribe again on reconnect or capability replacement. This signal covers PI WEB stylesheet publication, not arbitrary DOM/CSS mutations, media-query changes, or completion of font/image loading.
|
|
585
|
+
|
|
586
|
+
### CSS-first themes
|
|
587
|
+
|
|
588
|
+
Themes extend the built-in base theme: they can supply layout and control rules as well as palette properties. The base provides the default shell, responsive layout, shared controls, and transcript presentation in the `pi-web-base` cascade layer. Normal unlayered theme rules override those defaults. This separation is in place before a stable layout-selector contract; specialised local component/plugin styles are still being migrated. See [theme architecture](theme-architecture.md) for composition, examples, and current limits.
|
|
589
|
+
|
|
590
|
+
V5 themes declare CSS files, not TypeScript `tokens`. V4 themes retain their required `tokens: ThemeTokens` palette contract for compatibility; they cannot declare stylesheet contributions. Their original palette properties still style the current UI, while newer semantic properties use fallbacks. Switching away from a v4 theme clears its palette overrides. The built-in Dark, Light, and Classic palettes use this same contribution API. Put palette custom properties on `:root` so they inherit into shadow trees; use ordinary selectors in the same file when you need direct rules in participating roots.
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
import type { PiWebPlugin } from "@jmfederico/pi-web/plugin-api";
|
|
594
|
+
|
|
595
|
+
export default {
|
|
596
|
+
apiVersion: 5,
|
|
597
|
+
name: "Ocean themes",
|
|
598
|
+
activate: () => ({
|
|
599
|
+
contributions: {
|
|
600
|
+
themes: [{
|
|
601
|
+
id: "ocean-dark", name: "Ocean Dark", colorScheme: "dark",
|
|
602
|
+
stylesheets: [{ id: "palette", url: new URL("./dark.css", import.meta.url).href }],
|
|
603
|
+
}, {
|
|
604
|
+
id: "ocean-light", name: "Ocean Light", colorScheme: "light",
|
|
605
|
+
stylesheets: [{ id: "palette", url: new URL("./light.css", import.meta.url).href }],
|
|
606
|
+
}],
|
|
607
|
+
themePairs: [{ id: "ocean", name: "Ocean", light: "ocean-light", dark: "ocean-dark" }],
|
|
608
|
+
},
|
|
609
|
+
}),
|
|
610
|
+
} satisfies PiWebPlugin;
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
`dark.css` might start with:
|
|
614
|
+
|
|
615
|
+
```css
|
|
616
|
+
:root {
|
|
617
|
+
--pi-bg: #071923;
|
|
618
|
+
--pi-surface: #102b38;
|
|
619
|
+
--pi-text: #e4f5ff;
|
|
620
|
+
--pi-accent: #60d7ee;
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
#### Semantic appearance properties
|
|
625
|
+
|
|
626
|
+
Themes can override these properties independently of the base palette:
|
|
627
|
+
|
|
628
|
+
| Properties | Purpose |
|
|
629
|
+
| --- | --- |
|
|
630
|
+
| `--pi-font`, `--pi-mono` | UI and code font-family stacks; control sizes and line heights stay component-owned |
|
|
631
|
+
| `--pi-thinking`, `--pi-tool` | Thinking and tool activity tones |
|
|
632
|
+
| `--pi-syntax-comment`, `--pi-syntax-keyword`, `--pi-syntax-string`, `--pi-syntax-number`, `--pi-syntax-title`, `--pi-syntax-deletion` | Syntax-highlighting colours, independent of success/warning/error feedback |
|
|
633
|
+
| `--pi-chat-conversation-bg`, `--pi-chat-conversation-border` | Ordinary conversation surface and leading edge |
|
|
634
|
+
| `--pi-chat-user-bg`, `--pi-chat-user-border` | User-message surface and leading edge |
|
|
635
|
+
| `--pi-chat-system-bg` | System-message surface |
|
|
636
|
+
| `--pi-chat-activity-bg`, `--pi-chat-compacting-bg` | Session activity and compaction surfaces |
|
|
637
|
+
|
|
638
|
+
Define these on `:root`, alongside the base palette. Font properties accept font-family stacks, not complete `font` shorthands; keep `--pi-mono` monospaced for code alignment. The terminal's xterm grid and copy-selection overlay retain their configured font so cell geometry stays aligned. Existing themes may omit them: components fall back to base palette colours, existing colour mixes, and system fonts. Built-in themes declare them explicitly; most colours alias or mix base palette properties. Syntax deletions default to `--pi-danger`; the legacy `--pi-error` property remains a fallback for older external themes. Theme properties control appearance; direct CSS rules can also customise migrated base layouts. Components retain interaction and scroll controllers, while the base theme supplies default responsive CSS and visual accessibility treatments. These properties, like shared UI classes, remain experimental.
|
|
639
|
+
|
|
640
|
+
The example above is a partial palette. Use the [built-in palette files](https://github.com/jmfederico/pi-web/tree/main/src/client/src/plugins/themes) as a reference for the shared `--pi-*` vocabulary. `colorScheme` remains required metadata for native controls and browser appearance; declaring CSS alone does not set it. Theme pair `light` and `dark` values are local theme IDs from the same plugin. Auto mode follows the device preference within the pair; unpaired themes stay selected. See [theme selection and defaults](config.md#theme-default-and-device-choices) for device choices, fallback, and the preserved built-in IDs. Machine-bound remote theme contributions remain ignored; themes are app-wide.
|
|
641
|
+
|
|
642
|
+
### Read-only catalogs and scoped backends (v5)
|
|
643
|
+
|
|
644
|
+
Browser v5 action, panel, label, and invoked message-action contexts include `catalog`. It lists and resolves registered projects, their workspaces, and saved sessions without requiring a plugin backend. Each catalog captures `machineId`; changing selection creates a new callback context, never retargets an existing catalog. Activation still provides plugin identity, template helpers, selection observation, and lifecycle signals—not a selection-bound catalog.
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
export default {
|
|
648
|
+
apiVersion: 5,
|
|
649
|
+
name: "Project dashboard",
|
|
650
|
+
activate({ html }) {
|
|
651
|
+
return {
|
|
652
|
+
contributions: {
|
|
653
|
+
applicationPanels: [{
|
|
654
|
+
id: "dashboard",
|
|
655
|
+
title: "Projects",
|
|
656
|
+
render(context) {
|
|
657
|
+
return html`<my-dashboard .catalog=${context.catalog}
|
|
658
|
+
.backend=${context.backend}></my-dashboard>`;
|
|
659
|
+
},
|
|
660
|
+
}],
|
|
661
|
+
},
|
|
662
|
+
};
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Components and event handlers can call `catalog.projects.list()` / `get(projectId)`, `catalog.workspaces.list(projectId)` / `get({ projectId, workspaceId })`, and `catalog.sessions.list({ projectId, workspaceId })` / `get({ projectId, workspaceId, sessionId })`. Reads accept an optional abort signal. Session snapshots include public metadata and `runtime` (`not-hosted`, `idle`, or `busy`); discovery does not open a saved session or expose its transcript/private file path.
|
|
668
|
+
|
|
669
|
+
Paired plugins also receive `backend` in these browser contexts. `backend.forScope({ kind: "machine" })`, `{ kind: "project", projectId }`, or `{ kind: "workspace", projectId, workspaceId }` returns the package's paired request/channel interface on that context's machine. Portable browser-only plugins have no paired backend. Existing workspace `peer` helpers remain available.
|
|
670
|
+
|
|
671
|
+
Server v5 peer callbacks receive `scope` instead of always requiring `project` and `workspace`. Machine scope carries no project; project scope contains host-resolved `project`; workspace scope contains host-resolved `project` and `workspace`. Browser/server package revisions, cancellation, and host authority checks still apply. Server v3 retains its workspace-only callback shape and cannot accept machine/project-scoped calls.
|
|
672
|
+
|
|
673
|
+
Backend plugins declare `PI_WEB_HOST_CATALOG_CAPABILITY` in `requires`, then resolve it in `start({ capabilities })`. This provides the same read-only discovery locally to that daemon. Workspace providers must not depend on the catalog: it becomes available only after provider topology settles. No project, workspace, or session mutation methods are added.
|
|
674
|
+
|
|
675
|
+
Machine-specific contributions are visible only on their owning machine; portable browser contributions follow selection. Switching machines does not stop backend work. Plugins must discard obsolete asynchronous UI results and clean up component-owned connections when their context changes.
|
|
314
676
|
|
|
315
677
|
A few design boundaries matter before implementation:
|
|
316
678
|
|
|
@@ -2,7 +2,7 @@ import type { PiWebPlugin, WorkspacePanelContext } from "@jmfederico/pi-web/plug
|
|
|
2
2
|
import { isReview, type Review } from "./protocol.js";
|
|
3
3
|
|
|
4
4
|
interface PanelState { reviews: Review[]; selected?: Review; busy: boolean; status: string }
|
|
5
|
-
const plugin: PiWebPlugin = {
|
|
5
|
+
const plugin: Extract<PiWebPlugin, { apiVersion: 4 }> = {
|
|
6
6
|
apiVersion: 4,
|
|
7
7
|
name: "Workspace Reviews",
|
|
8
8
|
activate({ html }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmfederico/pi-web",
|
|
3
|
-
"version": "1.202610.
|
|
3
|
+
"version": "1.202610.5",
|
|
4
4
|
"description": "Web UI for persistent Pi Coding Agent sessions in real workspaces.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Federico Jaramillo Martinez",
|
|
@@ -79,16 +79,8 @@
|
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@codemirror/commands": "^6.11.1",
|
|
82
|
-
"@codemirror/lang-css": "^6.3.1",
|
|
83
|
-
"@codemirror/lang-go": "^6.0.1",
|
|
84
|
-
"@codemirror/lang-html": "^6.4.12",
|
|
85
|
-
"@codemirror/lang-javascript": "^6.2.5",
|
|
86
|
-
"@codemirror/lang-json": "^6.0.2",
|
|
87
82
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
88
|
-
"@codemirror/lang-python": "^6.2.1",
|
|
89
|
-
"@codemirror/lang-rust": "^6.0.2",
|
|
90
83
|
"@codemirror/language": "^6.13.1",
|
|
91
|
-
"@codemirror/legacy-modes": "^6.5.5",
|
|
92
84
|
"@codemirror/state": "^6.7.6",
|
|
93
85
|
"@codemirror/view": "6.43.13",
|
|
94
86
|
"@fastify/compress": "^9.2.2",
|
|
@@ -96,8 +88,10 @@
|
|
|
96
88
|
"@fastify/websocket": "^11.3.4",
|
|
97
89
|
"@xterm/addon-fit": "^0.11.0",
|
|
98
90
|
"@xterm/xterm": "^6.0.0",
|
|
91
|
+
"css-tree": "^3.2.1",
|
|
99
92
|
"diff": "^9.0.0",
|
|
100
93
|
"fastify": "^5.12.7",
|
|
94
|
+
"highlight.js": "^11.12.0",
|
|
101
95
|
"image-size": "^2.0.4",
|
|
102
96
|
"lit": "^3.3.3",
|
|
103
97
|
"marked": "^18.1.1",
|
|
@@ -108,10 +102,11 @@
|
|
|
108
102
|
},
|
|
109
103
|
"devDependencies": {
|
|
110
104
|
"@changesets/cli": "^3.0.3",
|
|
111
|
-
"@earendil-works/pi-agent-core": "^1.
|
|
112
|
-
"@earendil-works/pi-ai": "^1.
|
|
113
|
-
"@earendil-works/pi-coding-agent": "^1.
|
|
105
|
+
"@earendil-works/pi-agent-core": "^1.3.0",
|
|
106
|
+
"@earendil-works/pi-ai": "^1.3.0",
|
|
107
|
+
"@earendil-works/pi-coding-agent": "^1.3.0",
|
|
114
108
|
"@eslint/js": "^10.0.1",
|
|
109
|
+
"@types/css-tree": "^3.2.0",
|
|
115
110
|
"@types/node": "^26.6.5",
|
|
116
111
|
"@types/ws": "^8.18.2",
|
|
117
112
|
"dompurify": "^3.4.17",
|