@jmfederico/pi-web 1.202609.0 → 1.202610.0
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/README.md +3 -3
- package/dist/.plugins-ready +1 -0
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/index-_1GwJfSs.js +4176 -0
- package/dist/client/assets/vendor-editor-core-YWog6mAR.js +10 -0
- package/dist/client/assets/vendor-editor-languages-DOCw3vc9.js +28 -0
- package/dist/client/index.html +4 -4
- package/dist/docker/piWebDockerCommandPlan.js +9 -1
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/nativeServices/installedServiceDefinitions.js +24 -17
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -1
- package/dist/nativeServices/serviceAction.js +38 -26
- package/dist/nativeServices/serviceAction.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +3 -3
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/pi-packages/captains-log/README.md +11 -0
- package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
- package/dist/pi-packages/captains-log/dist/browser/index.js +145 -0
- package/dist/pi-packages/captains-log/dist/browser/markdown.js +67 -0
- package/dist/pi-packages/captains-log/dist/browser/panel.js +107 -0
- package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
- package/dist/pi-packages/captains-log/dist/companion.js +126 -0
- package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
- package/dist/pi-packages/captains-log/dist/server.js +210 -0
- package/dist/pi-packages/captains-log/dist/store.js +75 -0
- package/dist/pi-packages/captains-log/docs/usage.md +32 -0
- package/dist/pi-packages/captains-log/package.json +35 -0
- package/dist/pi-packages/captains-log/tsconfig.json +21 -0
- package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
- package/dist/pi-packages/relays/pi-web-plugin.js +1 -1
- package/dist/pi-packages/relays/prompts/relay.md +1 -1
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +21 -13
- package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +439 -0
- package/dist/pi-web-plugins/files/package.json +15 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +54 -24
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/git/git-backend.js +5 -5
- package/dist/pi-web-plugins/git/server-plugin.js +5 -3
- package/dist/pi-web-plugins/info/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +3590 -0
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +6 -0
- package/dist/pi-web-plugins/mermaid/package.json +13 -0
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +210 -0
- package/dist/pi-web-plugins/terminal/package.json +16 -0
- package/dist/pi-web-plugins/terminal/server-plugin.js +365 -0
- package/dist/{server/terminals → pi-web-plugins/terminal}/terminalService.js +159 -79
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
- package/dist/piWebVersionReport.js +12 -4
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +235 -23
- package/dist/plugin-api.js +1 -0
- package/dist/pluginRecoveryCli.js +3 -2
- package/dist/pluginRecoveryCli.js.map +1 -1
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +32 -28
- package/dist/server/app.js.map +1 -1
- package/dist/server/browserMessageProjection.js +39 -20
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/knownPiPackages.js +12 -0
- package/dist/server/knownPiPackages.js.map +1 -0
- package/dist/server/machines/machineClient.js +2 -2
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +53 -11
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +101 -7
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/notices/serverNoticeStore.js +45 -4
- package/dist/server/notices/serverNoticeStore.js.map +1 -1
- package/dist/server/piPackageService.js +5 -5
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +46 -12
- package/dist/server/piWebPluginCatalog.js.map +1 -1
- package/dist/server/piWebPluginLifecycle.js +26 -3
- package/dist/server/piWebPluginLifecycle.js.map +1 -1
- package/dist/server/piWebPluginService.js +37 -2
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/pluginCallbackDrain.js +35 -0
- package/dist/server/pluginCallbackDrain.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +52 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +38 -31
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +740 -0
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +1028 -107
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
- package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
- package/dist/server/projects/projectLifecycleService.js +91 -0
- package/dist/server/projects/projectLifecycleService.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +22 -9
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +393 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +10 -7
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/projectMutationRoutes.js +38 -0
- package/dist/server/sessiond/projectMutationRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +9 -2
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +24 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +99 -24
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/builtinCommands.js +2 -2
- package/dist/server/sessions/builtinCommands.js.map +1 -1
- package/dist/server/sessions/clientSessionPreview.js +14 -0
- package/dist/server/sessions/clientSessionPreview.js.map +1 -0
- package/dist/server/sessions/piSessionEventConnections.js +65 -0
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +8 -2
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +411 -102
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionActivityMarker.js +105 -0
- package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/sessionMediaIndex.js +184 -0
- package/dist/server/sessions/sessionMediaIndex.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +3 -2
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +64 -4
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionUnreadStore.js +26 -1
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +7 -2
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +7 -2
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/transcriptMessages.js +54 -0
- package/dist/server/sessions/transcriptMessages.js.map +1 -0
- package/dist/server/terminals/requiredTerminalService.js +103 -0
- package/dist/server/terminals/requiredTerminalService.js.map +1 -0
- package/dist/server/webSocketBridge.js +339 -0
- package/dist/server/webSocketBridge.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +1 -1
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/filePreviewService.js +27 -3
- package/dist/server/workspaces/filePreviewService.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +23 -3
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceCatalog.js +3 -1
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceProviderRegistry.js +64 -149
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
- package/dist/server/workspaces/workspaceRemovalService.js +23 -1
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
- package/dist/server-plugin-api.d.ts +216 -22
- package/dist/server-plugin-api.js +319 -2
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/serverPluginRecovery.js +4 -0
- package/dist/serverPluginRecovery.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +27 -3
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/apiTypes.js +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +47 -17
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machinePluginIds.js +28 -7
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +21 -1
- package/dist/shared/pluginApiTypes.js +0 -1
- package/dist/shared/pluginBackendProtocol.js +163 -2
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginIds.js +8 -1
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/requiredTerminalPlugin.js +6 -0
- package/dist/shared/requiredTerminalPlugin.js.map +1 -0
- package/dist/shared/serverNoticeContract.js +88 -0
- package/dist/shared/serverNoticeContract.js.map +1 -0
- package/dist/shared/sessionDefaults.js +41 -0
- package/dist/shared/sessionDefaults.js.map +1 -0
- package/dist/shared/sessionMedia.js +6 -0
- package/dist/shared/sessionMedia.js.map +1 -0
- package/docs/config.md +50 -36
- package/docs/plugins.md +157 -1212
- package/examples/session-bridge-plugin/README.md +20 -0
- package/examples/session-bridge-plugin/docs/usage.md +54 -0
- package/examples/session-bridge-plugin/package.json +26 -0
- package/examples/session-bridge-plugin/src/browser/index.ts +84 -0
- package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
- package/examples/session-bridge-plugin/src/companion.ts +65 -0
- package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
- package/examples/session-bridge-plugin/src/server.ts +85 -0
- package/examples/session-bridge-plugin/src/store.ts +57 -0
- package/examples/session-bridge-plugin/tsconfig.json +21 -0
- package/examples/workspace-provider-plugin/README.md +4 -4
- package/examples/workspace-provider-plugin/package.json +1 -1
- package/examples/workspace-provider-plugin/src/browser/index.ts +9 -9
- package/examples/workspace-provider-plugin/src/server.ts +15 -11
- package/package.json +19 -12
- package/dist/client/assets/CodeViewer-CGAlg9S8.js +0 -4
- package/dist/client/assets/TerminalPanel-xhJRhOas.js +0 -187
- package/dist/client/assets/index-DXQKhn1P.js +0 -4326
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +0 -12
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +0 -46
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
- package/dist/server/terminalProxyRoutes.js +0 -141
- package/dist/server/terminalProxyRoutes.js.map +0 -1
- package/dist/server/terminals/terminalRoutes.js +0 -155
- package/dist/server/terminals/terminalRoutes.js.map +0 -1
- package/dist/server/terminals/terminalService.js.map +0 -1
- package/dist/server/terminals/terminalSize.js +0 -17
- package/dist/server/terminals/terminalSize.js.map +0 -1
package/docs/plugins.md
CHANGED
|
@@ -1,1319 +1,264 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Plugins and Pi packages
|
|
2
2
|
|
|
3
|
-
PI WEB
|
|
3
|
+
Customize PI WEB with workspace tools, useful shortcuts, and integrations. You can install an existing package or ask an agent to build one for your workflow. You do not need to modify PI WEB itself.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This guide explains what is possible and what to expect. For implementation, use the [public contracts and examples](#agent-development), rather than treating this page as a second API reference.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- add workspace tools/panels next to Files and Terminal;
|
|
9
|
-
- add compact workspace-label items in the workspace list, panel header, and status bar;
|
|
10
|
-
- call browser APIs and documented PI WEB plugin context helpers;
|
|
11
|
-
- read workspace files and start workspace terminal commands through documented helpers;
|
|
12
|
-
- serve browser-public files from an explicitly declared `browserRoot`;
|
|
13
|
-
- contribute one server-side workspace provider, with optional JSON backend requests and workspace-removal planning.
|
|
7
|
+
## What can be built
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
When machine federation is enabled, **Settings → Pi packages** targets the currently selected machine. The panel labels whether changes will run on the local/gateway machine or on a selected remote PI WEB machine.
|
|
30
|
-
|
|
31
|
-
Use **Settings → PI WEB plugins** to edit the desired enablement of discovered plugins on the selected machine. Browser-only changes apply after a page reload. A server-backed change also requires a session-daemon restart before its paired browser module can load against the new active revision. If an older or unavailable remote PI WEB server does not support the versioned plugin lifecycle, PI WEB reports plugin settings as unsupported or unavailable instead of silently falling back to the gateway.
|
|
32
|
-
|
|
33
|
-
After installing, removing, or updating a Pi package, type `/reload` in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. For PI WEB plugins, manually restart the target session daemon when the package has a server entry, then reload the browser page. A provider-registering Pi extension follows a separate daemon-start policy; see [Pi extension provider baseline](https://pi-web.dev/config#pi-extension-provider-baseline).
|
|
34
|
-
|
|
35
|
-
## Pi extension dialogs in PI WEB
|
|
36
|
-
|
|
37
|
-
Pi extensions running under PI WEB's session daemon can ask the user questions with `ctx.ui.confirm()`, `ctx.ui.select()`, and `ctx.ui.input()`. PI WEB reports `ctx.hasUI === true`, and for these three dialog methods that is true in fact: the call renders a dialog card inline in the session transcript and the returned Promise resolves with the user's actual answer — a boolean for confirm, the chosen option for select, the typed text for input.
|
|
38
|
-
|
|
39
|
-
- **Works from hooks, without the prompt queue.** Answers travel over a dedicated session-daemon channel, so a dialog opened inside an in-flight `tool_call` hook parks safely — the agent loop waits for the hook and the run continues with the answer. Consent-gating a tool from a `tool_call` hook is a supported pattern.
|
|
40
|
-
- **`session_start` dialogs are reachable.** A dialog opened from a `session_start` hook is answerable while the session is still starting, both when creating a session and when opening an existing one; startup completes once the dialog settles.
|
|
41
|
-
- **Survives browser reloads; first answer wins.** Reloading the browser re-renders open dialogs from the session status. With several tabs on the same session, the first answer settles the dialog and the other tabs re-render the settled card.
|
|
42
|
-
- **Settled cards stay until dismissed.** An answered or closed dialog leaves its outcome card in the transcript so the user can see what became of it — answers travel to the extension alone, so the card is the only record of the exchange. The card is browser-local: only a browser that saw the dialog open renders it, and switching sessions or reloading drops it.
|
|
43
|
-
- **Timeouts.** The extension's own `timeout` option applies, and the daemon adds an unattended-dialog safety valve, `extensionDialogsTimeoutMs` (default 5 minutes, `0` waits forever — see [Extension dialogs](https://pi-web.dev/config#extension-dialogs)). The effective deadline is the sooner of the two. A dialog that closes without an answer resolves with its kind's cancel value: `false` for confirm, `undefined` for select and input.
|
|
44
|
-
- **Abort and runtime replacement.** Aborting the current run settles a dialog opened during that run immediately, at abort-request time, with its cancel value. Replacing the session runtime (`/reload`, session disposal) settles any still-open dialog the same way; hooks on the new runtime open fresh dialogs. The extension's own `AbortSignal` is honored: aborting it dismisses the dialog and resolves with the cancel value.
|
|
45
|
-
- **Other UI surfaces are still no-ops.** `ExtensionUIContext` methods beyond the three dialogs (widgets, status, editor, `custom`) remain unimplemented under PI WEB even though `hasUI` is `true`; do not rely on `hasUI` alone to detect them.
|
|
46
|
-
|
|
47
|
-
One browser-local caveat: reloading the browser while a new session is still being created loses the browser-local pending-start row, so the dialog card disappears from view. The daemon-side dialog still settles at its deadline and the session appears in the sidebar once creation completes.
|
|
48
|
-
|
|
49
|
-
## Trust model
|
|
50
|
-
|
|
51
|
-
Treat every plugin package as trusted code:
|
|
52
|
-
|
|
53
|
-
- browser entries can call browser APIs, read workspace files, start terminal commands through helpers, and render arbitrary UI;
|
|
54
|
-
- server entries execute in-process inside sessiond with the PI WEB service user's filesystem, environment, and process permissions, and they share sessiond's event loop;
|
|
55
|
-
- a CPU-bound, blocking, or deadlocked callback can stall sessions, terminals, and health endpoints; abort/deadline signals bound only cooperative asynchronous code and cannot preempt code that ignores them;
|
|
56
|
-
- ordinary import, activation, start, health, and stop failures are attributed and quarantined where the host can catch them, but this is a stability boundary rather than a security boundary;
|
|
57
|
-
- plugins should not be installed from untrusted sources.
|
|
58
|
-
|
|
59
|
-
PI WEB's `/api/...` HTTP and WebSocket endpoints are internal implementation details. Browser code should use documented context helpers, including `context.backend.request()` for a paired server entry. Server code should use only `@jmfederico/pi-web/server-plugin-api`. Private routes, runtime objects, and source-internal imports are experimental and may change or disappear.
|
|
60
|
-
|
|
61
|
-
## Workspace providers and replacement ownership
|
|
62
|
-
|
|
63
|
-
Sessiond is the single authority for project workspace discovery and spawned-session target validation. One active plugin exclusively owns a project's workspace semantics:
|
|
64
|
-
|
|
65
|
-
1. Healthy or degraded primary providers probe first.
|
|
66
|
-
2. If exactly one primary returns `"claim"`, it owns the project.
|
|
67
|
-
3. If no primary claims, fallback providers probe. Bundled Git is a fallback provider.
|
|
68
|
-
4. Multiple claimants in the winning tier produce a visible conflict; PI WEB does not select by plugin id or import order.
|
|
69
|
-
5. If no provider claims, PI WEB exposes the project folder as the kernel workspace. If a winner claims and then fails to list, PI WEB reports degraded state instead of silently switching owners.
|
|
70
|
-
|
|
71
|
-
PI WEB ships only the bundled Git production provider. Replacement integrations, including Jujutsu providers, are owned and distributed by third parties. An installed and enabled primary provider can claim its projects and suppress fallback Git without any PI WEB core changes; this documentation does not promise or ship a reference replacement.
|
|
72
|
-
|
|
73
|
-
## What to ask AI to build
|
|
74
|
-
|
|
75
|
-
Humans should not need to hand-code plugins. Give an AI agent a concrete UI goal and ask it to create or modify a local plugin.
|
|
76
|
-
|
|
77
|
-
Good plugin requests:
|
|
78
|
-
|
|
79
|
-
- "Show a workspace badge with the dev server URL from `.env`."
|
|
80
|
-
- "Add a workspace panel with links to logs, dashboards, and local services for this repo."
|
|
81
|
-
- "Add an action-palette command that starts a standard code-review prompt."
|
|
82
|
-
- "Show whether the current workspace is a git worktree, main checkout, staging env, or feature branch."
|
|
83
|
-
- "Add a compact status badge based on a project health file or command output saved in the repo."
|
|
84
|
-
|
|
85
|
-
Copy-paste prompt for creating a plugin:
|
|
9
|
+
| Goal | Plugin feature |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Show project health, service links, or environment information | Workspace labels and panels |
|
|
12
|
+
| Add a dashboard, file viewer, or project-specific tool | Workspace panels, file helpers, and browser UI |
|
|
13
|
+
| Make common actions easier to find | Action-palette commands and shortcuts |
|
|
14
|
+
| Run builds, tests, or development servers | Workspace terminal commands |
|
|
15
|
+
| Customize the appearance | Themes and light/dark theme pairs |
|
|
16
|
+
| Preview diagrams or other text formats in chat and Files | Browser content renderers |
|
|
17
|
+
| Read or change workspace files | File listing, reading, writing, moving, deleting, and uploads |
|
|
18
|
+
| Show live backend results | Requests or streaming channels between a plugin's browser and server entries |
|
|
19
|
+
| Save plugin-owned results or preferences | A persistent server-side plugin directory |
|
|
20
|
+
| Start a Pi conversation or collaborate with a Pi extension | Host session capabilities and session-local messaging |
|
|
21
|
+
| Replace Git's workspace discovery with another system | A workspace provider |
|
|
86
22
|
|
|
87
|
-
|
|
88
|
-
Build a PI WEB plugin for this project.
|
|
89
|
-
Goal: <describe the UI behavior>.
|
|
90
|
-
Before coding, read the PI WEB plugin docs:
|
|
91
|
-
https://pi-web.dev/plugins
|
|
92
|
-
Full API reference:
|
|
93
|
-
https://pi-web.dev/plugins.md
|
|
94
|
-
Create it as a local plugin under ~/.pi-web/plugins/<plugin-id>.
|
|
95
|
-
Use the appropriate extension points from the docs.
|
|
96
|
-
Validate by checking /pi-web-plugins/manifest.json and explain how to reload/debug it.
|
|
97
|
-
Do not modify PI WEB itself.
|
|
98
|
-
```
|
|
23
|
+
Panels can support deep links and browser history, and refresh when workspace files change or agent work finishes. File and terminal helpers target the panel's machine and workspace, including remote machines.
|
|
99
24
|
|
|
100
|
-
|
|
25
|
+
For example, ask an agent to build “a panel that runs our checks and shows their results,” “a badge linking to this workspace's preview deployment,” or “a review tool that starts a conversation and saves its findings.”
|
|
101
26
|
|
|
102
|
-
|
|
103
|
-
Improve the PI WEB plugin at <path>.
|
|
104
|
-
Before coding, read the PI WEB plugin docs:
|
|
105
|
-
https://pi-web.dev/plugins
|
|
106
|
-
Full API reference:
|
|
107
|
-
https://pi-web.dev/plugins.md
|
|
108
|
-
Keep the browser entry on API v2 and any server entry on API v1.
|
|
109
|
-
After editing, check the manifest endpoint and browser-console failure cases.
|
|
110
|
-
```
|
|
27
|
+
## Pi packages, extensions, and plugins
|
|
111
28
|
|
|
112
|
-
|
|
29
|
+
These are related, but serve different purposes:
|
|
113
30
|
|
|
114
|
-
|
|
31
|
+
- **Pi packages** are installable bundles. They can contain extensions, skills, prompt templates, themes, and PI WEB plugins.
|
|
32
|
+
- **Pi extensions** customize the agent: tools, commands, hooks, model providers, and session behavior.
|
|
33
|
+
- **PI WEB plugins** customize the web application and its workspace integrations.
|
|
115
34
|
|
|
116
|
-
|
|
35
|
+
One package can contain all three kinds of customization. Installing a package and enabling its PI WEB plugin are separate decisions. Disabling a PI WEB plugin does not remove the package's Pi extensions, skills, or prompts.
|
|
117
36
|
|
|
118
|
-
|
|
37
|
+
Use a Pi extension for agent behavior and a PI WEB plugin for web UI. When a feature needs both, ship them together and let them communicate through the hosted session connection.
|
|
119
38
|
|
|
120
|
-
|
|
121
|
-
pi-web-plugins/info/package.json
|
|
122
|
-
pi-web-plugins/info/pi-web-plugin.ts
|
|
123
|
-
pi-web-plugins/info/infoInternals.ts
|
|
124
|
-
```
|
|
39
|
+
## How plugins work
|
|
125
40
|
|
|
126
|
-
|
|
41
|
+
A plugin package declares a browser entry, a server entry, or both:
|
|
127
42
|
|
|
128
|
-
|
|
43
|
+
- **Browser entries** add actions, panels, labels, themes, and content renderers. They use host helpers for workspace files, terminals, and prompt editing.
|
|
44
|
+
- **Server entries** run in the session daemon. They can serve their browser entry, store plugin data, use host capabilities, or provide workspaces.
|
|
45
|
+
- **Package peers** connect a plugin's browser and server entries. The host handles the selected machine and workspace; a plugin does not need to own that workspace to serve it.
|
|
46
|
+
- **Capabilities** let a plugin declare the host or plugin functionality it requires. Dependencies must be available at the requested version before the plugin starts.
|
|
129
47
|
|
|
130
|
-
|
|
131
|
-
dist/pi-web-plugins/info/pi-web-plugin.js
|
|
132
|
-
```
|
|
48
|
+
Plugins declare contributions in `activate()`, initialize dependency-backed work in `start()`, and release resources in `dispose()`. Long-lived work follows the plugin's `lifetimeSignal`. Simple browser plugins only need to return their contributions.
|
|
133
49
|
|
|
134
|
-
|
|
50
|
+
### Opening workspace files from chat
|
|
135
51
|
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
"name": "@pi-web/info-plugin",
|
|
139
|
-
"private": true,
|
|
140
|
-
"piWeb": {
|
|
141
|
-
"plugins": [
|
|
142
|
-
{ "id": "info", "browserRoot": ".", "module": "pi-web-plugin.js" }
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
52
|
+
Chat Markdown links to relative files (including `./file`) or absolute paths inside the session workspace open in the bundled Files panel. The link retains a download URL for modifier/new-tab clicks and when no panel accepts it. File access still uses server-side workspace containment checks.
|
|
147
53
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```js
|
|
151
|
-
export default {
|
|
152
|
-
apiVersion: 2,
|
|
153
|
-
name: "Info Plugin",
|
|
154
|
-
activate: ({ html, svg }) => ({
|
|
155
|
-
contributions: {
|
|
156
|
-
actions: [/* action definitions */],
|
|
157
|
-
workspaceLabels: [/* compact label definitions */],
|
|
158
|
-
workspacePanels: [/* panel definitions using html, optional icons using svg */],
|
|
159
|
-
},
|
|
160
|
-
}),
|
|
161
|
-
};
|
|
162
|
-
```
|
|
54
|
+
A workspace panel can opt in with `fileOpenQuery(context, path)`. This synchronous hook receives its contribution-scoped context and a decoded workspace-relative path; return a navigation query such as `{ file: path }`, or `undefined` to decline. Keep the hook free of side effects: the host opens the first accepting visible, enabled panel for the selected machine, ordered by panel `order` then title, and applies its namespaced query through normal panel navigation. The panel reads the selection from `context.navigation.query`; no Files-plugin dependency is required.
|
|
163
55
|
|
|
164
|
-
|
|
56
|
+
### Panel and tab navigation
|
|
165
57
|
|
|
166
|
-
The
|
|
58
|
+
The URL's `view` selects a responsive panel: `navigation`, `chat`, or `workspace`. The independent `tool` parameter selects a workspace tab by contribution ID. Opening a workspace tool sets `view=workspace` and `tool` to its ID; switching to chat keeps the selected tool. Contribution IDs are not accepted in `view`. Browser plugins use `selectMainView("workspace")` to show the workspace panel without changing its selected tab, or `selectWorkspaceTool(panelId)` to select and show a particular tool.
|
|
167
59
|
|
|
168
|
-
|
|
60
|
+
Invalid values remain in the URL rather than triggering a redirect. An invalid `view` shows a warning and displays navigation on mobile; on two-column layouts, navigation remains alongside a valid requested tool or, otherwise, chat. Desktop keeps its normal columns. A valid workspace view with an invalid tool shows an unavailable-tab message inside the workspace panel, without selecting another tab or adding a duplicate warning. Omitted parameters use defaults and are not errors.
|
|
169
61
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
The bundled `git` plugin is the production example for a paired browser module and workspace-provider server module. Both entries use the same public contracts available to an installed plugin:
|
|
173
|
-
|
|
174
|
-
```text
|
|
175
|
-
pi-web-plugins/git/package.json
|
|
176
|
-
pi-web-plugins/git/browser/pi-web-plugin.ts
|
|
177
|
-
pi-web-plugins/git/server-plugin.ts
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Its built `.js` server entry is a Node ES module, so its package declares `"type": "module"`. Do the same for an installed provider whose `serverModule` ends in `.js`, or emit `.mjs`; do not rely on Node's typeless-module reparsing.
|
|
181
|
-
|
|
182
|
-
Its TypeScript entries import declarations only from the published package subpaths:
|
|
62
|
+
Action and workspace-panel contexts expose `navigate(destination): Promise<void>` for complete destinations:
|
|
183
63
|
|
|
184
64
|
```ts
|
|
185
|
-
|
|
186
|
-
|
|
65
|
+
await context.navigate({
|
|
66
|
+
machineId: context.machine.id,
|
|
67
|
+
projectId: context.workspace.projectId,
|
|
68
|
+
workspaceId: context.workspace.id,
|
|
69
|
+
sessionId: sourceSessionId,
|
|
70
|
+
view: "chat",
|
|
71
|
+
});
|
|
187
72
|
```
|
|
188
73
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
## Copyable standalone workspace-provider example
|
|
192
|
-
|
|
193
|
-
The repository and published npm package include [`examples/workspace-provider-plugin/`](https://github.com/jmfederico/pi-web/tree/main/examples/workspace-provider-plugin), a small standalone package you can copy without PI WEB source imports. It includes package metadata, a strict NodeNext TypeScript configuration, browser and server source, and build/install instructions.
|
|
194
|
-
|
|
195
|
-
The example uses browser API v2 and server API v1. Its browser entry compares `workspace.provider.pluginId` with the stable source `pluginId`, uses `runtimePluginId` only to open its qualified panel, displays non-secret `publicMetadata`, and calls the backend owned by the selected workspace. Its server provider conservatively claims only projects containing `.pi-web/example-workspace-provider`. The explicit `browserRoot: "dist/browser"` keeps `dist/server.js`, source, package metadata, and dependencies outside browser asset routes.
|
|
74
|
+
All destination fields are optional: `machineId`, `projectId`, `workspaceId`, `sessionId`, `view` (`navigation`, `chat`, or `workspace`), and `tool` (a qualified contribution ID). Omitted `machineId` means the machine selected when called. This is not a route patch: omitted fields use normal host restoration defaults rather than copying the current route's session, tool, or contribution query. Those defaults can select a remembered session. Supply the project/workspace scope when opening a known session; the host does not search for IDs or create missing destinations.
|
|
196
75
|
|
|
197
|
-
|
|
76
|
+
The promise settles after host restoration, or normally when newer navigation supersedes it. Missing or unavailable destinations use the normal host UI and do not also reject the promise. Malformed argument types and invalid `view` values reject with `TypeError` before changing the URL or UI. Captain's Log uses this API for **Open source session** on translations that record a source session.
|
|
198
77
|
|
|
199
|
-
|
|
78
|
+
### Content previews in chat and Files
|
|
200
79
|
|
|
201
|
-
This
|
|
80
|
+
Browser plugins can contribute `contentRenderers` with an `id`, `languages` (Markdown fence labels), `fileExtensions` (without a dot), and a synchronous `render(input)` returning a Lit template. Selectors are case-insensitive and match by language OR file extension. `renderMode?: 'manual' | 'automatic'` defaults to `manual`: raw source and a **Render** button appear without calling the renderer. This is the initial policy, not a restriction on explicit user intent; unrelated prose updates retain activation. Authors may explicitly opt into `automatic` and are responsible for efficient activation and asynchronous work. The same renderer serves chat fences, Files Markdown preview fences, and standalone text files. Selection follows the effective machine's plugin availability and portable/machine-specific precedence. When several renderers match, a chooser lets you compare alternatives for each diagram or file, with only the selected renderer mounted. Choices default to alphabetical source plugin ID order (not remote runtime prefixes), then local contribution ID, using locale-independent, case-sensitive code-unit comparison. The chooser labels identify the plugin and contribution. Chat remembers explicit renderer and Raw/Preview choices per code block in this browser tab for 15 minutes from the last explicit choice. Viewing or remounting never extends that deadline; expiry applies on revisit, without removing a visible preview. Entries are bounded to the 128 most recently chosen blocks and scoped by machine, session, message/entry, part, block and exact source. Changed source or an unavailable selected renderer invalidates the choice and restores the deterministic default and its policy. Automatic rendering alone creates no remembered override. DOM is not cached; previews render again on remount. Reloading or closing the tab clears this memory. Files Markdown previews do not use chat intent memory. For standalone files, the plugin policy supplies the initial default only when no browser-local Raw/Preview preference exists. Saved Preview authorizes all file rendering, including manual renderers inside Markdown fences; saved Raw suppresses previews. Per-block Raw and renderer choices remain available within Markdown Preview. URL mode still takes precedence. Defaults are not automatically saved as explicit choices. Built-in file previews retain their existing defaults. There is no plugin order field or sorting UI.
|
|
202
81
|
|
|
203
|
-
|
|
82
|
+
Chat keeps **Raw**, **Preview**, and **Copy source** available in a toolbar on each supported diagram block. Standalone Files previews use the file header's **Raw**/**Preview** controls beside **Download** and follow the saved file-view mode; diagrams within Markdown files retain per-block controls. Incomplete fences stay raw and copyable; closed fences preview before the message finishes. Appending prose does not restart an unchanged completed block. Preview failure shows the source instead. Unknown formats retain normal code rendering.
|
|
204
83
|
|
|
205
|
-
|
|
206
|
-
mkdir -p ~/.pi-web/plugins
|
|
207
|
-
ln -s /path/to/plugin-folder ~/.pi-web/plugins/plugin-id
|
|
208
|
-
```
|
|
84
|
+
`input` contains `text`, an abort `signal`, and `fail(error)`. Render synchronously, own async work in a component, clean up on disconnect or abort, and report asynchronous failures with `fail`. Obsolete failures are ignored. Renderers receive untrusted text: escape or sanitize output, avoid executing source, and do not relax the surrounding Markdown policy. The host skips plugin rendering for truncated input and blocks over 100,000 UTF-16 code units; Files also retains its inline byte-size limit. Ordinary chat Markdown and Files Markdown retain their distinct sanitizers.
|
|
209
85
|
|
|
210
|
-
|
|
86
|
+
A browser consumer such as Files declares the host capability `{ pluginId: "pi-web", id: "content-rendering", version: 1, parse }` in `requires`, then resolves it in `start`. The exported `ContentRenderingCapability` type describes `listRenderers(request)`, `renderText(request)`, and `renderMarkdown({ machineId, text, truncated?, toSafeHtml, allowManualPreview? })`. `listRenderers` returns sorted eligible `{ id, label, renderMode }` choices without invoking renderers. `renderText` accepts an optional `rendererId` only with `controls: "external"`; absent or unavailable IDs select the first match. Embedded controls own their selection and ignore externally supplied IDs. Use `controls: "external"` when the consumer owns the renderer chooser, mode switching, and raw-source access (as Files does in its header beside Download); otherwise controls remain embedded. Consumers own their user-intent policy and may pass `allowManualPreview: true` to `renderText` or `renderMarkdown` to authorize manual previews (for example, an explicit Render click or Files’ saved or URL Preview preference). For Markdown this applies to eligible fences without overriding per-block Raw choices. Omitting it or passing false retains renderer defaults, including automatic previews. Chat never passes this override. External Raw controls must remove the preview. The public capability stores no remembered intent. Chat’s bounded per-block memory is private host policy, not a plugin option. Markdown requests have their own shape without file-path or language selectors; languages come from fences. Creating a `renderText` template does not invoke a renderer. Markdown diagrams each own an independent chooser. Switching renderers cancels the previous preview and clears its failure state. `renderMarkdown` requires the consumer's trusted sanitizer; it is not permission to insert untrusted HTML. 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) for complete implementations.
|
|
211
87
|
|
|
212
|
-
|
|
88
|
+
### Conversations and companion extensions
|
|
213
89
|
|
|
214
|
-
|
|
90
|
+
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.
|
|
215
91
|
|
|
216
|
-
|
|
217
|
-
- file and terminal helpers run against that machine;
|
|
218
|
-
- `context.backend.request()` is routed through the gateway to the current workspace owner on that machine;
|
|
219
|
-
- a server-backed browser module is published only when its package source, scope, settings fingerprint, browser revision, and backend revision match the active sessiond snapshot and the backend is not unhealthy;
|
|
220
|
-
- if gateway and remote packages share an original id, `machineSpecific` controls whether the portable gateway copy is reused or the selected machine's own copy is required;
|
|
221
|
-
- remote theme contributions are ignored for now because themes are app-wide.
|
|
92
|
+
**The conversation belongs to the user once it is published.** Finishing the initial task, closing the browser, or disposing the initiating plugin does not discard it or stop later user work. Users can continue it like any other conversation.
|
|
222
93
|
|
|
223
|
-
|
|
94
|
+
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.
|
|
224
95
|
|
|
225
|
-
|
|
96
|
+
### Storage and background work
|
|
226
97
|
|
|
227
|
-
|
|
98
|
+
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.
|
|
228
99
|
|
|
229
|
-
|
|
100
|
+
Requests and channels are bounded and can fail, time out, or disconnect. A successful send does not guarantee delivery, and the host does not automatically retry uncertain work. Long-running jobs should maintain their own state and let the UI reconnect without accidentally starting the job twice.
|
|
230
101
|
|
|
231
|
-
|
|
232
|
-
- `true`: the gateway copy appears only for the local machine, and a selected remote machine uses only its own copy. Dual browser/server entries are always machine-specific; omitting the field defaults them to `true`, while explicitly setting `false` is invalid.
|
|
102
|
+
### Workspace providers
|
|
233
103
|
|
|
234
|
-
|
|
104
|
+
A provider decides which workspaces belong to a project. A primary provider can replace bundled Git for projects it claims. Git is the fallback; without a claimant, the project folder remains usable as a workspace.
|
|
235
105
|
|
|
236
|
-
|
|
237
|
-
const url = new URL("./asset.json", import.meta.url);
|
|
238
|
-
```
|
|
106
|
+
Conflicting claims produce a visible error. A provider that claims a project and then fails does not silently hand ownership to another provider. Providers can also offer workspace removal, which runs as a visible terminal operation.
|
|
239
107
|
|
|
240
|
-
|
|
108
|
+
Workspace discovery must be available before host session services start. If a package needs both a provider and session-backed features, use two plugin entries in the same package. There is no need to split the distribution into separate packages.
|
|
241
109
|
|
|
242
|
-
|
|
110
|
+
## Install and manage
|
|
243
111
|
|
|
244
|
-
|
|
112
|
+
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.
|
|
245
113
|
|
|
246
|
-
|
|
114
|
+
| Change | What to do next |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| Install or edit a browser-only plugin | Reload the browser page |
|
|
117
|
+
| Install, update, configure, enable, or disable a server-backed plugin | Restart the target session daemon, then reload the browser |
|
|
118
|
+
| Change ordinary Pi resources such as extensions, skills, or prompts | Run `/reload` in each idle session |
|
|
119
|
+
| Change an extension that registers model providers | Follow the separate [provider restart guidance](https://pi-web.dev/config#pi-extension-provider-baseline) |
|
|
247
120
|
|
|
248
|
-
|
|
121
|
+
**Restarting the session daemon may interrupt active sessions and terminals.** Inspect active work first and restart from outside the sessions it hosts. For the native systemd user install:
|
|
249
122
|
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
"plugins": {
|
|
253
|
-
"git": {
|
|
254
|
-
"enabled": true,
|
|
255
|
-
"settings": {}
|
|
256
|
-
},
|
|
257
|
-
"info": {
|
|
258
|
-
"enabled": false
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
123
|
+
```sh
|
|
124
|
+
systemctl --user restart pi-web-sessiond
|
|
262
125
|
```
|
|
263
126
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
### Desired versus active state
|
|
267
|
-
|
|
268
|
-
Sessiond resolves one immutable enabled server-plugin snapshot at startup and remains the workspace authority for its lifetime. Saving config or replacing package files changes **desired** state only. The existing backend can remain active until sessiond restarts; conversely, its paired browser module is withheld when active and desired revisions no longer match. A web/API restart or browser reload does not change sessiond's active provider registry.
|
|
269
|
-
|
|
270
|
-
Use this sequence:
|
|
127
|
+
A browser reload, web/API restart, or Pi's `/reload` does not activate server-plugin changes. Until the daemon restarts, Settings can show different desired and active states. A paired browser entry is withheld when it no longer matches the active server entry, rather than running incompatible code.
|
|
271
128
|
|
|
272
|
-
|
|
273
|
-
2. Set the desired plugin enablement/settings.
|
|
274
|
-
3. For a browser-only plugin, reload the browser tab.
|
|
275
|
-
4. For a server-backed plugin, manually restart sessiond, wait for it to become available, then reload the browser tab.
|
|
129
|
+
Most plugins are enabled by default. Packages can opt out, and Settings can override the default. Plugin settings live in the normal [PI WEB configuration](https://pi-web.dev/config), not inside the package's installed files.
|
|
276
130
|
|
|
277
|
-
|
|
131
|
+
### Local development
|
|
278
132
|
|
|
279
|
-
|
|
133
|
+
An agent can develop a package anywhere and symlink it into the local plugin directory:
|
|
280
134
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
pi-web plugins disable <plugin-id> --restart
|
|
285
|
-
pi-web plugins safe-start show
|
|
286
|
-
pi-web plugins safe-start set bundled-only --restart
|
|
287
|
-
pi-web plugins safe-start set none --restart
|
|
288
|
-
pi-web plugins safe-start clear --restart
|
|
135
|
+
```sh
|
|
136
|
+
mkdir -p ~/.pi-web/plugins
|
|
137
|
+
ln -s /path/to/plugin-folder ~/.pi-web/plugins/my-plugin
|
|
289
138
|
```
|
|
290
139
|
|
|
291
|
-
|
|
292
|
-
- `bundled-only` persists safe start and filters discovery before external local or Pi-package server modules are considered.
|
|
293
|
-
- `none` persists the emergency level and imports no server plugins; the kernel folder workspace remains available.
|
|
294
|
-
- `clear` returns the next startup to ordinary configured discovery.
|
|
295
|
-
- `--restart` requests an automatic restart only when PI WEB recognizes a safe installed-service action. Otherwise the command prints manual guidance.
|
|
296
|
-
|
|
297
|
-
An unsupported `serverPlugins.safeStart` shape or value in otherwise valid JSON fails closed as effective `none`: sessiond imports no server plugins and reports a diagnostic. Use `safe-start show`, then `set` or `clear`, to repair it offline. Recovery config is written before an automatic restart is attempted; if that service-manager command fails, restart sessiond manually.
|
|
298
|
-
|
|
299
|
-
Ordinary plugin failures are normally quarantined, but safe start provides a recovery path for code that blocks or terminates sessiond before normal containment can help. Any restart can interrupt active sessions/runtime ownership; inspect active work before using `--restart` or running the manual service command.
|
|
300
|
-
|
|
301
|
-
## Built-in plugins
|
|
140
|
+
If `PI_WEB_DATA_DIR` is set, use `$PI_WEB_DATA_DIR/plugins` instead. No PI WEB rebuild is required. The package must contain its built JavaScript; PI WEB does not compile arbitrary installed plugin source.
|
|
302
141
|
|
|
303
|
-
|
|
142
|
+
## Remote machines
|
|
304
143
|
|
|
305
|
-
|
|
144
|
+
Plugin installation and settings target the machine selected in Settings. A remote plugin's server code runs on that remote machine; its browser UI appears through the gateway.
|
|
306
145
|
|
|
307
|
-
|
|
146
|
+
File, terminal, and peer helpers keep operations on the selected machine. Plugins tied to a particular machine use that machine's own installation. Portable browser-only plugins can reuse a gateway copy; themes remain app-wide and remote theme contributions are ignored.
|
|
308
147
|
|
|
309
|
-
|
|
310
|
-
**What it does:** claims Git projects as a fallback workspace provider, discovers worktrees, supplies provider-owned removal plans, and adds the Git status/diff workspace panel through its paired backend.
|
|
148
|
+
Keep gateways and targets compatible. During this plugin API transition, upgrade them together, restart the updated web/API processes and affected session daemons, then reload the browser. Mixed versions can make remote plugins and Git unavailable; PI WEB does not silently substitute a gateway backend.
|
|
311
149
|
|
|
312
|
-
|
|
150
|
+
## Included tools and optional packages
|
|
313
151
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
**
|
|
317
|
-
**
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Updates is enabled by default. It declares `machineSpecific: true` so the gateway Updates tab and action only appear for the local machine; while a remote machine is selected, that remote machine's Updates plugin is used if available. To hide it, disable `updates` in **Settings → PI WEB plugins** or set:
|
|
322
|
-
|
|
323
|
-
```json
|
|
324
|
-
{
|
|
325
|
-
"plugins": {
|
|
326
|
-
"updates": { "enabled": false }
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
152
|
+
- **Terminal** supplies terminals and command runs. It is required in normal operation; disable it only through emergency safe start.
|
|
153
|
+
- **Files** supplies file browsing, previews, and uploads. Disabling its panel does not remove other plugins' file helpers.
|
|
154
|
+
- **Git** discovers Git workspaces and provides status/diff. Disabling it leaves the project-folder workspace available unless another provider takes over.
|
|
155
|
+
- **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.
|
|
156
|
+
- **Info** displays PI WEB status and copyable diagnostics.
|
|
157
|
+
- **Updates** shows update/restart guidance when relevant and offers a manual update check.
|
|
158
|
+
- **Workspace Tasks** turns project commands into runnable buttons.
|
|
330
159
|
|
|
331
160
|
### Workspace Tasks
|
|
332
161
|
|
|
333
|
-
|
|
334
|
-
**Config file:** `.pi-web/tasks.json`
|
|
335
|
-
**What it does:** adds a **Tasks** workspace tab for running configured shell commands in dedicated PI WEB terminals.
|
|
336
|
-
|
|
337
|
-
Workspace Tasks is enabled by default. To hide it, disable `workspace-tasks` in **Settings → PI WEB plugins** or set:
|
|
338
|
-
|
|
339
|
-
```json
|
|
340
|
-
{
|
|
341
|
-
"plugins": {
|
|
342
|
-
"workspace-tasks": { "enabled": false }
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
Configure workspace tasks in `.pi-web/tasks.json`:
|
|
162
|
+
Create `.pi-web/tasks.json` in a project:
|
|
348
163
|
|
|
349
164
|
```json
|
|
350
165
|
{
|
|
351
166
|
"version": 1,
|
|
352
167
|
"tasks": [
|
|
353
|
-
{
|
|
354
|
-
|
|
355
|
-
"title": "Start app",
|
|
356
|
-
"group": "Development",
|
|
357
|
-
"description": "Start the local development server.",
|
|
358
|
-
"command": "npm run dev"
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
"id": "db.reset",
|
|
362
|
-
"title": "Reset DB",
|
|
363
|
-
"group": "Database",
|
|
364
|
-
"command": "go -C klingit-go run ./cli db reset",
|
|
365
|
-
"confirm": true
|
|
366
|
-
}
|
|
168
|
+
{ "id": "app.start", "title": "Start app", "command": "npm run dev" },
|
|
169
|
+
{ "id": "db.reset", "title": "Reset database", "command": "npm run db:reset", "confirm": true }
|
|
367
170
|
]
|
|
368
171
|
}
|
|
369
172
|
```
|
|
370
173
|
|
|
371
|
-
Open
|
|
372
|
-
|
|
373
|
-
Task fields:
|
|
374
|
-
|
|
375
|
-
- `version`: must be `1`.
|
|
376
|
-
- `tasks`: array of task definitions.
|
|
377
|
-
- `id`: stable task id, matching `^[a-z][a-z0-9.-]*$`.
|
|
378
|
-
- `title`: button label.
|
|
379
|
-
- `command`: literal shell command sent to the terminal.
|
|
380
|
-
- `description`: optional explanatory text.
|
|
381
|
-
- `group`: optional group heading.
|
|
382
|
-
- `confirm`: optional boolean. When true, the browser asks before dispatching the command.
|
|
383
|
-
|
|
384
|
-
Review task configs before running them, especially in shared projects. Workspace Tasks runs trusted shell commands from your repositories.
|
|
174
|
+
Open the **Tasks** tab to run a command in a workspace terminal. Tasks can also have a `description`. Review commands before running them, especially in shared repositories. Disabling the plugin hides the tab without changing the project file.
|
|
385
175
|
|
|
386
176
|
### Relays
|
|
387
177
|
|
|
388
|
-
**
|
|
389
|
-
**What it does:** the `@jmfederico/pi-relay` Pi package supplies a tool-agnostic `relay` foundation, the opinionated `relay-runner` software-delivery profile, a human-gated `/relay` preparation prompt, and `/relay-worktree` as its fresh-worktree compatibility alias. Its browser-only `relays` PI WEB plugin adds a read-only **Relays** workspace tab for browsing the workspace's relays, plus an **Open Workspace Relays** action for the selected workspace that opens the same tab.
|
|
390
|
-
|
|
391
|
-
Before dispatch, `/relay` performs bounded repository discovery and creates an incrementally refined four-document draft packet so the user can review the proposed goal and edges in the Relays UI. Draft creation is allowed before approval; only `spawn_session` is gated. The preparer establishes only the first bounded leg, not a roadmap, fixed leg count, or work-package plan, then requires explicit approval against the final drafts. In fresh-worktree mode, it may draft in the preparation checkout and moves the packet into the target worktree before dispatch without leaving a stale copy.
|
|
392
|
-
|
|
393
|
-
The runner profile keeps `charter.md` focused on the plain-language goal and scope edges; repository bindings, verification, review, and delivery mechanics live separately in `operations.md`, while project skills and documentation remain the quality authority. Under this profile, Relay completion requires both the chartered outcome and the review, approval, and delivery gates recorded in `operations.md`. Review is evidence gathering, not a finding quota: unsupported hypothetical concerns do not block approval, and re-review carries earlier decisions forward instead of restarting defect hunting. The normal path is an initial whole-work review plus one re-review when needed. A third attempt is an exceptional, justified contingency; a Relay that remains blocked then stops for human intervention instead of continuing the automatic review/remediation loop.
|
|
394
|
-
|
|
395
|
-
Under the shipped `relay-runner` profile, a Relay packet is a directory of markdown notes under `.pi-web/relays/<name>/` in the workspace root. The tool-agnostic base method does not require this storage convention. The tab lists each relay's documents with `status.md`, `charter.md`, `operations.md`, and `log.md` first (in that order), followed by any other files alphabetically, and opens `status.md` by default. Markdown documents render as sanitized HTML; other files render as preformatted text, and binary files have no preview. Truncated documents show a notice, and **Refresh** re-scans the workspace and reloads the open document.
|
|
396
|
-
|
|
397
|
-
Documents in subfolders are listed too. Folders appear as chips in the document strip, and expanding one inserts its files inline right after it — accordion-style, so expanding a folder collapses its siblings on the same level. An expanded folder wraps its chip and documents in a group bubble, so nested entries stay visually contained. Collapsing the folder that holds the open document keeps the selection and highlights the folder instead. Relay trees deeper than five levels, larger than 200 documents, or with more than 50 folders are listed partially, with a notice.
|
|
398
|
-
|
|
399
|
-
With several relays, a picker pre-selects the most recently modified one; a single relay opens directly. A workspace without `.pi-web/relays/` shows an empty state explaining the convention. The tab never creates, edits, or deletes relay files.
|
|
400
|
-
|
|
401
|
-
Relay ships as a standalone Pi package: its source lives at `pi-packages/relays/` and its built copy ships inside `@jmfederico/pi-web` at `dist/pi-packages/relays/`, alongside (but outside) the bundled plugins in `pi-web-plugins/`/`dist/pi-web-plugins/`. PI WEB does not discover it from those plugin directories. Installing `@jmfederico/pi-relay` for the active Pi agent profile lets Pi load its two prompt templates and two skills and lets PI WEB discover its browser plugin, just as it would for any other installed Pi package (see [Discovery and packaging](#discovery-and-packaging) and [Pi packages shipped alongside bundled plugins](#pi-packages-shipped-alongside-bundled-plugins)). After installing or removing the package, use `/reload` in each idle session to refresh Pi's resources and reload the browser page to refresh the plugin catalog.
|
|
402
|
-
|
|
403
|
-
Once the package is installed, `plugins.relays.enabled` controls only the Relays browser panel and action. Disabling it and reloading the page hides those PI WEB contributions without removing `/relay`, `/relay-worktree`, or the `relay` and `relay-runner` skills; re-enabling it restores the browser contributions without changing Pi's resources. Because the plugin is browser-only, changing this setting does not require a session-daemon restart.
|
|
404
|
-
|
|
405
|
-
PI WEB keeps the default setup zero-extra-steps: sessiond installs `@jmfederico/pi-relay` automatically for the active agent profile at startup if it is not already configured for that profile. Removing it from **Settings → Pi packages** removes the Pi resources and browser plugin and is remembered per profile (see [Pi packages shipped alongside bundled plugins](#pi-packages-shipped-alongside-bundled-plugins)), so a manual removal is not silently reinstalled later. A user who changes their mind can reinstall it again with one click from the same Settings screen, with no path to type.
|
|
406
|
-
|
|
407
|
-
## Discovery and packaging
|
|
408
|
-
|
|
409
|
-
The web/API catalog and sessiond startup catalog use the same package sources. The browser manifest includes only compatible entries that declare `module`; sessiond considers enabled entries that declare `serverModule`:
|
|
410
|
-
|
|
411
|
-
1. Bundled plugins in the PI WEB package:
|
|
412
|
-
|
|
413
|
-
```text
|
|
414
|
-
pi-web-plugins/<plugin-package>/
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
2. User-local plugins:
|
|
418
|
-
|
|
419
|
-
```text
|
|
420
|
-
~/.pi-web/plugins/<plugin-package>/
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
Entries may be real directories or symlinks. This is the recommended development workflow.
|
|
424
|
-
|
|
425
|
-
3. Installed Pi packages that expose PI WEB plugin metadata. Pi packages may be user or project scoped. Installing/removing/updating Pi packages is done from **Settings → Pi packages** (or Pi's package manager), not from the PI WEB plugin enable/disable list.
|
|
426
|
-
|
|
427
|
-
Remote machines expose their versioned browser manifests through the gateway at `/api/machines/<machine-id>/pi-web-plugins/manifest.json`. Those plugin modules are rewritten to gateway-scoped asset URLs and registered under machine-scoped runtime ids so package copies on different machines do not collide.
|
|
428
|
-
|
|
429
|
-
Plugin package directory names and plugin ids must be valid identifiers:
|
|
430
|
-
|
|
431
|
-
```text
|
|
432
|
-
^[a-z][a-z0-9.-]*$
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
A package can expose one or more PI WEB plugin entries. There is exactly one supported `package.json` metadata shape:
|
|
436
|
-
|
|
437
|
-
```json
|
|
438
|
-
{
|
|
439
|
-
"private": true,
|
|
440
|
-
"type": "module",
|
|
441
|
-
"piWeb": {
|
|
442
|
-
"plugins": [
|
|
443
|
-
{
|
|
444
|
-
"id": "review",
|
|
445
|
-
"browserRoot": "dist/review",
|
|
446
|
-
"module": "dist/review/index.js"
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
"id": "workspaces",
|
|
450
|
-
"browserRoot": "dist/browser",
|
|
451
|
-
"module": "dist/browser/index.js",
|
|
452
|
-
"serverModule": "dist/server.js",
|
|
453
|
-
"machineSpecific": true
|
|
454
|
-
},
|
|
455
|
-
{ "id": "server-only", "serverModule": "dist/server-only.js" }
|
|
456
|
-
]
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
Rules:
|
|
462
|
-
|
|
463
|
-
- `piWeb.plugins` must be an array of objects.
|
|
464
|
-
- Each entry must have an explicit `id` and at least one of `module` or `serverModule`.
|
|
465
|
-
- `id` must match `^[a-z][a-z0-9.-]*$`. Externally declared ids `core`, `themes`, and every `machine.*` id are reserved for the host and rejected with an attributed package diagnostic.
|
|
466
|
-
- Both module paths must be safe canonical relative paths to existing files inside the package root. Backslashes, absolute or Windows drive-qualified paths, and empty, `.`, `..`, `.git`, or `node_modules` segments are rejected.
|
|
467
|
-
- Every browser entry must declare `browserRoot`; a server-only entry must not. The root is `.` or a safe canonical package-relative directory with no empty, `.`, `..`, `.git`, or `node_modules` segment; Windows drive-qualified roots are rejected. It must resolve inside the package, and the browser module must remain inside it both logically and after symlink resolution.
|
|
468
|
-
- Server entries are imported as Node ES modules. When a `serverModule` uses a `.js` path, declare `"type": "module"` in that plugin package; `.mjs` is the explicit-extension alternative.
|
|
469
|
-
- `machineSpecific` is optional and must be boolean. Browser-only and server-only entries default to `false`. Dual browser/server entries default to `true` and cannot explicitly set it to `false`.
|
|
470
|
-
- `plugins.<id>.settings` must be JSON-compatible for server entries; sessiond captures a private copy at startup and diagnostics expose only a fingerprint.
|
|
471
|
-
- A plugin id has one package owner across its browser and server capabilities. Duplicates are diagnosed and never merged or auto-renamed; later package records are skipped.
|
|
472
|
-
- Legacy shortcuts such as `piWeb.plugin`, string entries in `piWeb.plugins`, `piWeb.id` fallback ids, and no-`package.json` fallbacks are not supported.
|
|
473
|
-
|
|
474
|
-
Discovery hashes the package (without traversing `.git` or `node_modules`) to produce one package-wide revision and enforce one package-wide artifact budget. A package is rejected when the scan exceeds **4,096 directory entries** or **16 MiB of file content**. These limits include files outside `browserRoot`, even though those files are not served. Keep generated caches and unrelated large artifacts out of the installed plugin package.
|
|
475
|
-
|
|
476
|
-
### Manifest and assets
|
|
477
|
-
|
|
478
|
-
The manifest contains a lifecycle version and each publishable browser module. Current PI WEB releases emit `module` as a leading application-root reference and include `backendRevision` only for a paired active server entry:
|
|
178
|
+
The shipped Relay package adds agent prompts and skills for carrying work across sessions, plus a read-only **Relays** tab for inspecting plans and progress under `.pi-web/relays/`. The tab does not start or edit a relay.
|
|
479
179
|
|
|
480
|
-
|
|
481
|
-
{
|
|
482
|
-
"lifecycleVersion": 1,
|
|
483
|
-
"plugins": [
|
|
484
|
-
{
|
|
485
|
-
"id": "workspaces",
|
|
486
|
-
"module": "/pi-web-plugins/workspaces/dist/browser/index.js?v=<content-revision>",
|
|
487
|
-
"backendRevision": "<active-server-revision>",
|
|
488
|
-
"source": "local",
|
|
489
|
-
"scope": "local",
|
|
490
|
-
"machineSpecific": true
|
|
491
|
-
}
|
|
492
|
-
]
|
|
493
|
-
}
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
The browser maps leading application-root references into the current application base, so the same manifest works at the origin root or under a reverse-proxy path prefix. Federated gateways additionally accept explicit manifest-relative references such as `./my-plugin/pi-web-plugin.js` and legacy plugin-root-relative references such as `nested/pi-web-plugin.js`; all accepted forms are rewritten to deployment-portable, gateway-relative references.
|
|
497
|
-
|
|
498
|
-
`source` describes where the plugin came from (`bundled`, `local`, or the Pi package source). `scope` is `bundled`, `local`, `user`, or `project`. `machineSpecific` controls whether the gateway copy is valid for remote machines or only each selected machine's own copy can appear. A server-only entry has no browser manifest record. A dual entry is omitted unless sessiond reports the exact active, compatible, non-unhealthy package pairing.
|
|
499
|
-
|
|
500
|
-
At an origin-root deployment, a browser-public file is available under its package-relative path:
|
|
501
|
-
|
|
502
|
-
```text
|
|
503
|
-
/pi-web-plugins/<plugin-id>/<package-relative-path-under-browserRoot>
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
Only files logically inside `browserRoot` and canonically inside that same directory after symlink resolution are captured and served. Files elsewhere in the package—including a sibling server module, source, metadata, and dependencies—return not found through plugin asset routes. Declaring `browserRoot: "."` therefore makes almost the whole scanned package browser-public; prefer a narrow output directory and never place secrets inside it. Unsafe, missing, package-escaping, or module-excluding roots fail discovery with an attributed diagnostic.
|
|
507
|
-
|
|
508
|
-
Prefer module-relative asset URLs so they also work for remote machine plugins and nested deployments. For example, a built plugin module can reference an SVG shipped beside it:
|
|
509
|
-
|
|
510
|
-
```js
|
|
511
|
-
const iconUrl = new URL("./assets/icon.svg", import.meta.url);
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
The final installed plugin package must contain `assets/icon.svg` at that path relative to the final built module and inside `browserRoot`. PI WEB serves files that already exist in the package; it does not copy a source `public/` directory or apply Vite-style public-directory semantics. Configure the plugin build and package contents to emit or copy the asset into its final module-relative location.
|
|
515
|
-
|
|
516
|
-
PI WEB returns executable JavaScript MIME types for both `.js` and `.mjs`. JSON, CSS, HTML, and SVG receive their corresponding content types; unknown file types are served as octet-stream.
|
|
517
|
-
|
|
518
|
-
## Pi packages shipped alongside bundled plugins
|
|
519
|
-
|
|
520
|
-
`pi-packages/` ships real, independently identified Pi packages inside `@jmfederico/pi-web`'s npm package, built into `dist/pi-packages/<name>/` alongside — but separate from — the bundled PI WEB plugins in `pi-web-plugins/`/`dist/pi-web-plugins/`. A package shipped this way is *not* discovered by PI WEB's bundled/local directory scan; it only becomes an active PI WEB plugin once it is installed as a Pi package for the active agent profile, exactly like an externally published one (see [Discovery and packaging](#discovery-and-packaging)).
|
|
521
|
-
|
|
522
|
-
`pi-packages/relays/` is shaped this way: its `package.json` carries the real package identity `@jmfederico/pi-relay` alongside its `piWeb.plugins` entry, and its `prompts/` and `skills/` directories already follow pi's package conventions. Installing it as a Pi package — with a plain `pi install <path-to-dist/pi-packages/relays>`, through **Settings → Pi packages**' existing free-text install form, or with the one-click **Available packages** install button described below — makes `/relay`, `/relay-worktree`, and the `relay` and `relay-runner` skills available in any `pi` session, and makes the Relays PI WEB plugin (its browser tab and workspace action) available once installed for the active agent profile. Publishing the package to npm remains deferred follow-up work; today, installing it uses its local shipped path.
|
|
180
|
+
PI WEB installs Relay automatically for the active agent profile if it is not configured. Removing it through **Settings → Pi packages** is remembered; it will not be silently reinstalled. Reinstall from **Available packages** if you change your mind. Disabling just the Relays plugin hides its tab but leaves its agent resources available.
|
|
523
181
|
|
|
524
|
-
|
|
182
|
+
### Try Captain's Log
|
|
525
183
|
|
|
526
|
-
|
|
184
|
+
Captain's Log is an optional example that retells a conversation's latest assistant reply as a pirate briefing. It demonstrates a browser panel, backend, and Pi companion working together.
|
|
527
185
|
|
|
528
|
-
|
|
186
|
+
1. On the target machine, install **Captain's Log** from **Settings → Pi packages → Available packages**.
|
|
187
|
+
2. Enable it in **Settings → PI WEB plugins**.
|
|
188
|
+
3. Restart that machine's session daemon when safe, then reload the browser.
|
|
189
|
+
4. Select a conversation, open **Captain's Log**, and choose **Let the Captain tell it**.
|
|
529
190
|
|
|
530
|
-
|
|
191
|
+
The package is prebuilt; no compilation is required. It reads the source reply without modifying that conversation and uses a separate pirate conversation. Model credentials are required, and the source text goes to the pirate's model provider. Previous results are saved. After a daemon restart, open the previous pirate conversation in Sessions if you want to reuse its context.
|
|
531
192
|
|
|
532
|
-
|
|
193
|
+
See the [Captain's Log usage guide](https://github.com/jmfederico/pi-web/blob/main/pi-packages/captains-log/docs/usage.md) for the demo's behavior and troubleshooting.
|
|
533
194
|
|
|
534
|
-
|
|
535
|
-
import type { PiWebPlugin } from "@jmfederico/pi-web/plugin-api";
|
|
536
|
-
|
|
537
|
-
const plugin: PiWebPlugin = {
|
|
538
|
-
apiVersion: 2,
|
|
539
|
-
name: "My Plugin",
|
|
540
|
-
activate: ({ pluginId, runtimePluginId, html }) => ({
|
|
541
|
-
contributions: {
|
|
542
|
-
actions: [{
|
|
543
|
-
id: "workspace.open",
|
|
544
|
-
title: "Open my panel",
|
|
545
|
-
run: ({ selectWorkspaceTool }) => {
|
|
546
|
-
selectWorkspaceTool(`${runtimePluginId}:workspace.my-panel`);
|
|
547
|
-
},
|
|
548
|
-
}],
|
|
549
|
-
workspacePanels: [{
|
|
550
|
-
id: "workspace.my-panel",
|
|
551
|
-
title: "My panel",
|
|
552
|
-
visible: ({ workspace }) => workspace.provider?.pluginId === pluginId,
|
|
553
|
-
render: ({ workspace }) => html`<p>${workspace.label}</p>`,
|
|
554
|
-
}],
|
|
555
|
-
},
|
|
556
|
-
}),
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
export default plugin;
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
The activation boundary is:
|
|
195
|
+
## Pi extension dialogs
|
|
563
196
|
|
|
564
|
-
|
|
565
|
-
interface PiWebPlugin {
|
|
566
|
-
apiVersion: 2;
|
|
567
|
-
name: string;
|
|
568
|
-
activate(context: PluginActivationContext): PluginActivationResult;
|
|
569
|
-
}
|
|
197
|
+
Pi extensions can ask for confirmation, a selection, or text input. PI WEB shows these questions inline in the conversation, including during session startup or while a tool is waiting. They remain answerable after a browser reload, and the first answer wins across tabs.
|
|
570
198
|
|
|
571
|
-
|
|
572
|
-
readonly apiVersion: 2;
|
|
573
|
-
readonly pluginId: string;
|
|
574
|
-
readonly runtimePluginId: string;
|
|
575
|
-
readonly html: HtmlTemplateTag;
|
|
576
|
-
readonly svg: SvgTemplateTag;
|
|
577
|
-
}
|
|
578
|
-
```
|
|
199
|
+
Dialogs use the extension's timeout and the host's configured [dialog timeout](https://pi-web.dev/config#extension-dialogs). Aborting work or replacing its runtime closes the relevant outstanding questions. Answered cards are browser-local and need not survive a reload. Reloading while a new session is still being created can temporarily lose its question card; the pending question still has its deadline.
|
|
579
200
|
|
|
580
|
-
|
|
201
|
+
These three dialog methods are supported; other Pi extension UI surfaces, such as custom editors and widgets, are not. An extension should not assume every UI feature works just because `hasUI` is true.
|
|
581
202
|
|
|
582
|
-
|
|
203
|
+
## Agent development
|
|
583
204
|
|
|
584
|
-
|
|
205
|
+
Give the agent a goal, the data it should use, and the actions it may take:
|
|
585
206
|
|
|
586
207
|
```text
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
TypeScript server entries import the separately published Node declarations with `import type`:
|
|
595
|
-
|
|
596
|
-
```ts
|
|
597
|
-
import type {
|
|
598
|
-
PiWebServerPlugin,
|
|
599
|
-
WorkspaceProvider,
|
|
600
|
-
} from "@jmfederico/pi-web/server-plugin-api";
|
|
601
|
-
|
|
602
|
-
const provider: WorkspaceProvider = {
|
|
603
|
-
async probe(project, signal) {
|
|
604
|
-
// Return "claim" only when this provider owns the project's semantics.
|
|
605
|
-
return await projectIsSupported(project, signal) ? "claim" : "pass";
|
|
606
|
-
},
|
|
607
|
-
async list(project, signal) {
|
|
608
|
-
return await listProviderWorkspaces(project, signal);
|
|
609
|
-
},
|
|
610
|
-
async request({ project, workspace, operation, input, signal }) {
|
|
611
|
-
return await handleProviderOperation({ project, workspace, operation, input, signal });
|
|
612
|
-
},
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
const plugin: PiWebServerPlugin = {
|
|
616
|
-
apiVersion: 1,
|
|
617
|
-
name: "My Workspace Provider",
|
|
618
|
-
activate(context) {
|
|
619
|
-
return {
|
|
620
|
-
workspaceProvider: provider,
|
|
621
|
-
health: async (signal) => ({ status: "healthy" }),
|
|
622
|
-
stop: async (signal) => { /* release plugin-owned resources */ },
|
|
623
|
-
};
|
|
624
|
-
},
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
export default plugin;
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
The default export has `apiVersion: 1`, a non-empty `name`, and `activate(context)`. The activation result can contain:
|
|
631
|
-
|
|
632
|
-
```ts
|
|
633
|
-
interface ServerPluginActivation {
|
|
634
|
-
workspaceProvider?: WorkspaceProvider;
|
|
635
|
-
start?(signal: AbortSignal): void | Promise<void>;
|
|
636
|
-
stop?(signal: AbortSignal): void | Promise<void>;
|
|
637
|
-
health?(signal: AbortSignal): ServerPluginHealth | Promise<ServerPluginHealth>;
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
A server plugin may contribute at most one `workspaceProvider`. The host-owned frozen activation context contains its `pluginId`, `packageRoot`, JSON settings snapshot, scoped logger, activation `AbortSignal`, and an argv-based `execFile()` helper. `execFile()` has host-owned timeout/output bounds; pass the current callback's signal into every command request. The API exposes no shell parser, Fastify instance, route registration, concrete service, event bus, or service locator.
|
|
642
|
-
|
|
643
|
-
Every activation, lifecycle, provider, and request signal is scoped to that one invocation. The host aborts it when the invocation times out or settles. Do not retain a signal as a plugin-lifetime shutdown notification; release plugin-owned resources in the explicit `stop()` callback. Deadlines remain cooperative, so plugins must observe each supplied signal.
|
|
644
|
-
|
|
645
|
-
Sessiond resolves the enabled catalog once per process start. It imports, validates, activates, and starts each server entry before publishing its contribution. A failed entry is attributed and skipped without aborting ordinary activation of other plugins; a failed `start` is rolled back with `stop` when available. Successful plugins stop in reverse activation order. Sessiond inspects each optional `health()` callback once while building the startup workspace authority; an unhealthy provider is excluded, a degraded provider remains eligible, and that inspection is not polled again during the process lifetime. Server entries are never hot-reloaded or unloaded after config/package edits.
|
|
646
|
-
|
|
647
|
-
### Workspace provider contract
|
|
648
|
-
|
|
649
|
-
```ts
|
|
650
|
-
interface WorkspaceProvider {
|
|
651
|
-
fallback?: boolean;
|
|
652
|
-
probe(project: ProjectInput, signal: AbortSignal): Promise<"claim" | "pass">;
|
|
653
|
-
list(project: ProjectInput, signal: AbortSignal): Promise<ProviderWorkspace[]>;
|
|
654
|
-
request?(context: ProviderRequestContext): Promise<JsonValue>;
|
|
655
|
-
prepareRemove?(context: ProviderRemoveContext): Promise<WorkspaceRemovePlan>;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
interface ProviderWorkspace {
|
|
659
|
-
key: string;
|
|
660
|
-
path: string;
|
|
661
|
-
label: string;
|
|
662
|
-
isMain: boolean;
|
|
663
|
-
data?: JsonValue;
|
|
664
|
-
publicMetadata?: JsonObject;
|
|
665
|
-
removal?: { actionLabel: string; confirmation: string };
|
|
666
|
-
}
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
- `probe()` must return only `"claim"` or `"pass"`. Leave `fallback` unset/false for a replacement that should run before bundled Git.
|
|
670
|
-
- `list()` runs only for the selected owner. Return stable provider-local keys, accessible absolute directory paths, unique paths/keys, non-empty labels, and exactly one main workspace.
|
|
671
|
-
- `data` is round-tripped privately to that provider during the current resolution. `publicMetadata` appears under `workspace.provider.metadata` and is visible to **all browser code and API consumers**. Never put secrets in `publicMetadata` or removal wording.
|
|
672
|
-
- `request()` is optional and receives a host-validated frozen current owner/workspace projection plus a bounded operation id, JSON input, and operation-scoped abort signal. It must return JSON.
|
|
673
|
-
- `removal` is display text only and requires `prepareRemove()`. It advertises removal for that specific workspace; browser `workspace.provider.capabilities.remove` is true only when that workspace advertises it and the owning provider implements removal.
|
|
674
|
-
- `prepareRemove()` returns a plan for a visible host-owned terminal run; returning the plan approves the operation but does **not** mean removal has completed. `command` is shell source interpreted by the host's login shell. The host chooses a safe current working directory outside the target, so the provider must use the supplied absolute `workspace.path`, shell-quote it, and keep removal in the foreground. The host records completion when the shell exits, with exit status 0 meaning success.
|
|
675
|
-
- Provider failures and conflicts are diagnostics. A claimant that fails `list()` does not permit fallback takeover for the same resolution.
|
|
676
|
-
|
|
677
|
-
The only supported plugin type entrypoints are the type-only package exports `@jmfederico/pi-web/plugin-api` and `@jmfederico/pi-web/server-plugin-api`. Use them with `import type`; there is no runtime JavaScript export. Private `dist/**` deep imports and any other plugin API subpath are not part of the package contract.
|
|
678
|
-
|
|
679
|
-
## Contributions
|
|
680
|
-
|
|
681
|
-
The workspace-related contribution arrays returned by `activate()` are:
|
|
682
|
-
|
|
683
|
-
```ts
|
|
684
|
-
interface PluginContributions {
|
|
685
|
-
actions?: PluginAction[];
|
|
686
|
-
workspacePanels?: WorkspacePanelContribution[];
|
|
687
|
-
workspaceLabels?: WorkspaceLabelContribution[];
|
|
688
|
-
}
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### Actions
|
|
692
|
-
|
|
693
|
-
Actions appear in the action palette. They can inspect app state and call UI/runtime helpers.
|
|
694
|
-
|
|
695
|
-
```js
|
|
696
|
-
actions: [
|
|
697
|
-
{
|
|
698
|
-
id: "copy-diagnostics",
|
|
699
|
-
title: "Copy PI WEB Diagnostics",
|
|
700
|
-
description: "Copy version, installation, and status details for this machine",
|
|
701
|
-
group: "Info",
|
|
702
|
-
run: async (context) => {
|
|
703
|
-
const version = context.state.piWebStatus?.components.web.runtimeVersion ?? "unknown";
|
|
704
|
-
await navigator.clipboard.writeText(`PI WEB ${version}`);
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
]
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
Action type:
|
|
711
|
-
|
|
712
|
-
```ts
|
|
713
|
-
interface PluginAction {
|
|
714
|
-
id: string;
|
|
715
|
-
title: string;
|
|
716
|
-
description?: string;
|
|
717
|
-
shortcut?: string;
|
|
718
|
-
shortcutAliases?: QualifiedContributionId[];
|
|
719
|
-
group?: string;
|
|
720
|
-
enabled?: (context: PluginRuntimeContext) => boolean;
|
|
721
|
-
disabledReason?: (context: PluginRuntimeContext) => string | undefined;
|
|
722
|
-
run: (context: PluginRuntimeContext) => void | Promise<void>;
|
|
723
|
-
}
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
If an action is disabled and returns `disabledReason`, PI WEB can keep it visible in the action palette with that explanation instead of hiding it.
|
|
727
|
-
|
|
728
|
-
Stable runtime context fields:
|
|
729
|
-
|
|
730
|
-
```ts
|
|
731
|
-
interface PluginRuntimeContext {
|
|
732
|
-
state: {
|
|
733
|
-
selectedMachine?: PluginMachine;
|
|
734
|
-
selectedWorkspace?: Workspace;
|
|
735
|
-
selectedSession?: unknown;
|
|
736
|
-
workspaceTool?: string;
|
|
737
|
-
mainView?: string;
|
|
738
|
-
piWebStatus?: PiWebStatusResponse;
|
|
739
|
-
};
|
|
740
|
-
prompt: PluginPromptEditor;
|
|
741
|
-
openActionPalette: () => void;
|
|
742
|
-
focusPrompt: () => void;
|
|
743
|
-
addProject: () => void | Promise<void>;
|
|
744
|
-
configureAuth: () => void | Promise<void>;
|
|
745
|
-
logoutAuth: () => void | Promise<void>;
|
|
746
|
-
openThemePicker: () => void;
|
|
747
|
-
selectMainView: (view: string) => void;
|
|
748
|
-
selectWorkspaceTool: (tool: QualifiedContributionId) => void;
|
|
749
|
-
openTerminal: (options?: { terminalId?: string }) => void;
|
|
750
|
-
refreshFiles: () => void | Promise<void>;
|
|
751
|
-
refreshWorkspacePanels: (panelId?: QualifiedContributionId) => void | Promise<void>;
|
|
752
|
-
refreshAppData: () => void | Promise<void>;
|
|
753
|
-
checkForPiWebUpdates?: () => void | Promise<void>;
|
|
754
|
-
reloadPage: () => void;
|
|
755
|
-
startSession: () => void | Promise<void>;
|
|
756
|
-
archiveSession: () => void | Promise<void>;
|
|
757
|
-
stopActiveWork: () => void | Promise<void>;
|
|
758
|
-
}
|
|
208
|
+
Build a PI WEB plugin for this project.
|
|
209
|
+
Goal: <describe the workflow and expected UI>.
|
|
210
|
+
Read https://pi-web.dev/plugins.md, then follow its public-contract
|
|
211
|
+
and example links for the PI WEB version installed here.
|
|
212
|
+
Use supported plugin APIs; do not modify PI WEB or call private routes.
|
|
213
|
+
Explain installation, any permissions or model use, and how to reload it.
|
|
214
|
+
Test the behavior, including failures and cleanup.
|
|
759
215
|
```
|
|
760
216
|
|
|
761
|
-
|
|
217
|
+
### Where to implement
|
|
762
218
|
|
|
763
|
-
|
|
764
|
-
- The stable state fields are `state.selectedMachine`, `state.selectedWorkspace`, `state.selectedSession`, `state.workspaceTool`, `state.mainView`, and `state.piWebStatus`. `state.selectedMachine` identifies the currently selected machine. `state.piWebStatus` describes the currently selected machine's PI WEB runtime, or the gateway/local runtime when the local machine is selected.
|
|
765
|
-
- Other `state` fields may exist at runtime, but they are private PI WEB internals that may graduate into stable helpers, change shape, or disappear.
|
|
766
|
-
- `enabled` is evaluated when the action palette asks for actions.
|
|
767
|
-
- `shortcutAliases` is for migration only: list former fully qualified action ids whose saved shortcut preference should still apply to this action.
|
|
768
|
-
- `selectWorkspaceTool()` expects a qualified panel id such as `my-plugin:workspace.info`.
|
|
769
|
-
- `openTerminal()` switches to the built-in terminal panel. Pass `{ terminalId }` to deep-link to a specific terminal.
|
|
770
|
-
- `refreshWorkspacePanels()` invokes `onInvalidate` for the selected workspace, either for every plugin panel or for one qualified `panelId`. The callback owns its refresh and should request a render when its visible state changes.
|
|
771
|
-
- `checkForPiWebUpdates()` forces a fresh update check on the selected machine and refreshes `state.piWebStatus`. It is optional so plugins remain compatible with older PI WEB hosts.
|
|
772
|
-
- Only fields documented here and declared by `@jmfederico/pi-web/plugin-api` are stable public browser API. Anything else is experimental: it may become public API later, change shape, or disappear.
|
|
219
|
+
Use the smallest example that fits. Source links below track development on `main`; **use the tag or installed declarations matching your PI WEB version** when implementing against a release.
|
|
773
220
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
The `prompt` helper on `PluginRuntimeContext` and `WorkspacePanelContext` provides stable access to the chat prompt editor:
|
|
777
|
-
|
|
778
|
-
| Method | Description |
|
|
221
|
+
| Need | Start here |
|
|
779
222
|
| --- | --- |
|
|
780
|
-
|
|
|
781
|
-
|
|
|
782
|
-
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
context.prompt.insertText("@file.txt");
|
|
789
|
-
|
|
790
|
-
// Read the current prompt and selection
|
|
791
|
-
const text = context.prompt.getText();
|
|
792
|
-
const selection = context.prompt.getSelection(); // { start, end, text } | null
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
Use `focusPrompt()` on `PluginRuntimeContext` to move focus to the prompt editor. Workspace panels can call `context.prompt.insertText()` from explicit user interactions such as button clicks; panel contexts target the currently selected session's mounted prompt editor.
|
|
796
|
-
|
|
797
|
-
#### Keyboard shortcuts
|
|
798
|
-
|
|
799
|
-
- App-level keyboard shortcuts must be attached to actions. PI WEB does not support standalone plugin keyboard commands; contribute an action first, then add a `shortcut` if it needs a keybinding.
|
|
800
|
-
- `shortcut` is the action's default keybinding. It is displayed in the action palette and handled by the global shortcut dispatcher when the action is enabled.
|
|
801
|
-
- Use modified shortcuts such as `mod+shift+p`; plain letter shortcuts are intentionally ignored so normal typing is never captured.
|
|
802
|
-
- Future PI WEB versions may allow users to override or disable action shortcuts by action id, so plugins should treat `shortcut` as a default rather than a guaranteed final binding.
|
|
803
|
-
- Choose shortcuts carefully to avoid conflicts. There is no user-facing shortcut override or conflict resolver yet.
|
|
804
|
-
- Local text input, terminal input, list navigation, and dialog keys such as Enter, Escape, and arrow keys do not need to be plugin actions unless they are app-level commands.
|
|
805
|
-
|
|
806
|
-
### Workspace panels
|
|
807
|
-
|
|
808
|
-
Workspace panels add tools next to built-in workspace tools. They render inside the workspace side panel on desktop and as mobile tabs on smaller screens.
|
|
809
|
-
|
|
810
|
-
```js
|
|
811
|
-
workspacePanels: [
|
|
812
|
-
{
|
|
813
|
-
id: "workspace.info",
|
|
814
|
-
title: "Info",
|
|
815
|
-
icon: svg`
|
|
816
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
817
|
-
<circle cx="12" cy="12" r="9"></circle>
|
|
818
|
-
<path d="M12 10v6"></path>
|
|
819
|
-
<path d="M12 7h.01"></path>
|
|
820
|
-
</svg>
|
|
821
|
-
`,
|
|
822
|
-
order: 100,
|
|
823
|
-
visible: ({ workspace }) => workspace.isMain,
|
|
824
|
-
render: ({ workspace }) => html`
|
|
825
|
-
<section class="toolbar"><strong>Info</strong></section>
|
|
826
|
-
<section class="viewer">
|
|
827
|
-
<p class="muted">${workspace.label}</p>
|
|
828
|
-
<p class="muted">${workspace.path}</p>
|
|
829
|
-
</section>
|
|
830
|
-
`,
|
|
831
|
-
},
|
|
832
|
-
]
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
Panel type:
|
|
836
|
-
|
|
837
|
-
```ts
|
|
838
|
-
interface WorkspacePanelContribution {
|
|
839
|
-
id: string;
|
|
840
|
-
title: string;
|
|
841
|
-
icon?: TemplateResult;
|
|
842
|
-
order?: number;
|
|
843
|
-
routeAliases?: string[];
|
|
844
|
-
visible?: (context: WorkspacePanelContext) => boolean;
|
|
845
|
-
badge?: (context: WorkspacePanelContext) => string | number | TemplateResult | undefined;
|
|
846
|
-
onInvalidate?: (context: WorkspacePanelContext) => void | Promise<void>;
|
|
847
|
-
render: (context: WorkspacePanelContext) => TemplateResult;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
interface WorkspacePanelContext {
|
|
851
|
-
machine: PluginMachine;
|
|
852
|
-
workspace: Workspace;
|
|
853
|
-
state?: PluginRuntimeState;
|
|
854
|
-
files: {
|
|
855
|
-
readFile(path: string): Promise<FileContentResponse>;
|
|
856
|
-
listFiles(path: string): Promise<FileTreeResponse>;
|
|
857
|
-
writeFile(path: string, content: string | Uint8Array, options?: WriteWorkspaceFileOptions): Promise<WriteWorkspaceFileResponse>;
|
|
858
|
-
deleteFile(path: string): Promise<DeleteWorkspaceFileResponse>;
|
|
859
|
-
moveFile(fromPath: string, toPath: string, options?: MoveWorkspaceFileOptions): Promise<MoveWorkspaceFileResponse>;
|
|
860
|
-
};
|
|
861
|
-
backend?: {
|
|
862
|
-
request(operation: string, input: JsonValue): Promise<JsonValue>;
|
|
863
|
-
};
|
|
864
|
-
prompt: PluginPromptEditor;
|
|
865
|
-
terminal: {
|
|
866
|
-
open(options?: { terminalId?: string }): void;
|
|
867
|
-
runCommand(input: {
|
|
868
|
-
title: string;
|
|
869
|
-
command: string;
|
|
870
|
-
metadata?: Record<string, string>;
|
|
871
|
-
open?: boolean;
|
|
872
|
-
}): Promise<TerminalCommandRunHandle>;
|
|
873
|
-
};
|
|
874
|
-
host: {
|
|
875
|
-
requestRender(): void;
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
`icon` is optional and is used in the compact mobile tab bar. Prefer an SVG rendered with the `svg` helper from `PluginActivationContext`; use `currentColor` so PI WEB themes can style it. If `icon` is omitted, mobile tabs fall back to initials from the panel title, or to the full title when initials collide.
|
|
881
|
-
|
|
882
|
-
`machine`, `workspace`, `files`, optional `backend`, `prompt`, `terminal`, and `host` are documented as stable for panel callbacks. The `files` helper supports `readFile`, `listFiles`, `writeFile`, `deleteFile`, and `moveFile` — see [Reading workspace files](#reading-workspace-files), [Listing workspace files](#listing-workspace-files), and [Writing, deleting, and moving workspace files](#writing-deleting-and-moving-workspace-files). A browser entry with a paired active provider uses `backend.request()` instead of constructing API routes — see [Calling paired workspace backends](#calling-paired-workspace-backends). The `prompt` helper supports panel interactions that insert workspace context into the current prompt — see [Prompt editor API](#prompt-editor-api). Use `terminal.open()` to switch to the built-in terminal panel; pass `{ terminalId }` to deep-link to a specific terminal. `routeAliases` is only for migrating former URL tool/view values. Implement `onInvalidate()` to refresh plugin-owned panel data when an action or host refresh calls `refreshWorkspacePanels()`; call `host.requestRender()` when async state changes should make PI WEB re-evaluate `badge`, `visible`, or `render`.
|
|
883
|
-
|
|
884
|
-
Useful workspace and machine shapes:
|
|
885
|
-
|
|
886
|
-
```ts
|
|
887
|
-
interface PluginMachine {
|
|
888
|
-
id: string;
|
|
889
|
-
name: string;
|
|
890
|
-
kind: "local" | "remote";
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
interface Workspace {
|
|
894
|
-
readonly id: string;
|
|
895
|
-
readonly projectId: string;
|
|
896
|
-
readonly path: string;
|
|
897
|
-
readonly label: string;
|
|
898
|
-
readonly isMain: boolean;
|
|
899
|
-
readonly provider?: {
|
|
900
|
-
readonly pluginId: string;
|
|
901
|
-
readonly capabilities: { readonly request: boolean; readonly remove: boolean };
|
|
902
|
-
readonly metadata?: JsonObject;
|
|
903
|
-
};
|
|
904
|
-
readonly removal?: { readonly actionLabel: string; readonly confirmation: string };
|
|
905
|
-
}
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
`machine.id` is included in panel contexts so plugins can keep caches machine-scoped. Do not infer the selected machine from global browser state. Use the provider-authored `workspace.label` for provider-neutral presentation. `workspace.provider.pluginId` is the stable source id, and provider-published details such as Git status live in `workspace.provider.metadata`, which the server provider fills from browser-public `publicMetadata`. Provider-specific browser code may interpret metadata it owns; PI WEB core does not assign branch semantics to the generic workspace shape. `capabilities.remove` describes only this workspace, not the provider in general. The browser-v1 `isGitRepo`, `isGitWorktree`, and top-level `branch` aliases were removed.
|
|
909
|
-
|
|
910
|
-
Use existing classes such as `toolbar`, `viewer`, `empty`, and `muted` for panel content when possible. Do not assume a panel owns the whole page; keep layout contained.
|
|
911
|
-
|
|
912
|
-
### Workspace labels
|
|
913
|
-
|
|
914
|
-
Workspace labels add compact inline metadata wherever PI WEB displays a workspace label: workspace list, workspace panel header, and status bar.
|
|
915
|
-
|
|
916
|
-
Use them for short facts like project environment, local URL, branch status, container name, or health state.
|
|
917
|
-
|
|
918
|
-
```js
|
|
919
|
-
workspaceLabels: [
|
|
920
|
-
{
|
|
921
|
-
id: "dev-url",
|
|
922
|
-
order: 10,
|
|
923
|
-
visible: ({ workspace }) => workspace.path.includes("my-app"),
|
|
924
|
-
items: () => [{
|
|
925
|
-
type: "link",
|
|
926
|
-
text: "web:5173",
|
|
927
|
-
href: "http://localhost:5173",
|
|
928
|
-
title: "Open dev server",
|
|
929
|
-
target: "_blank",
|
|
930
|
-
}],
|
|
931
|
-
},
|
|
932
|
-
]
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
Label contribution type:
|
|
936
|
-
|
|
937
|
-
```ts
|
|
938
|
-
interface WorkspaceLabelContribution {
|
|
939
|
-
id: string;
|
|
940
|
-
order?: number;
|
|
941
|
-
visible?: (context: WorkspaceLabelContext) => boolean;
|
|
942
|
-
items: (context: WorkspaceLabelContext) => WorkspaceLabelItem[];
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
interface WorkspaceLabelContext {
|
|
946
|
-
machine: PluginMachine;
|
|
947
|
-
workspace: Workspace;
|
|
948
|
-
state?: PluginRuntimeState;
|
|
949
|
-
files: {
|
|
950
|
-
readFile(path: string): Promise<FileContentResponse>;
|
|
951
|
-
listFiles(path: string): Promise<FileTreeResponse>;
|
|
952
|
-
writeFile(path: string, content: string | Uint8Array, options?: WriteWorkspaceFileOptions): Promise<WriteWorkspaceFileResponse>;
|
|
953
|
-
deleteFile(path: string): Promise<DeleteWorkspaceFileResponse>;
|
|
954
|
-
moveFile(fromPath: string, toPath: string, options?: MoveWorkspaceFileOptions): Promise<MoveWorkspaceFileResponse>;
|
|
955
|
-
};
|
|
956
|
-
backend?: {
|
|
957
|
-
request(operation: string, input: JsonValue): Promise<JsonValue>;
|
|
958
|
-
};
|
|
959
|
-
host: {
|
|
960
|
-
requestRender(): void;
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
`machine`, `workspace`, `files`, optional `backend`, and `host` are documented as stable for label callbacks. The `files` helper supports `readFile`, `listFiles`, `writeFile`, `deleteFile`, and `moveFile` — see [Reading workspace files](#reading-workspace-files), [Listing workspace files](#listing-workspace-files), and [Writing, deleting, and moving workspace files](#writing-deleting-and-moving-workspace-files). A browser entry with a paired active provider can call `backend.request()` from a label-owned async cache after checking that the optional helper is present. Include `machine.id` in caches that depend on workspace data. Call `host.requestRender()` when async plugin-owned state changes should make PI WEB re-evaluate label `visible` or `items` callbacks.
|
|
966
|
-
|
|
967
|
-
Items are sorted by `order` and then id. Return an empty array to render nothing. Keep callbacks synchronous and lightweight; start async work from the callback, return cached items, then call `host.requestRender()` when the cache changes.
|
|
968
|
-
|
|
969
|
-
#### Text items
|
|
970
|
-
|
|
971
|
-
```js
|
|
972
|
-
{ type: "text", text: "staging", title: "Staging workspace" }
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
#### Link items
|
|
976
|
-
|
|
977
|
-
```js
|
|
978
|
-
{
|
|
979
|
-
type: "link",
|
|
980
|
-
text: "web:5173",
|
|
981
|
-
href: "http://localhost:5173",
|
|
982
|
-
title: "Open dev server",
|
|
983
|
-
target: "_blank"
|
|
984
|
-
}
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
PI WEB renders the anchor and adds safe defaults such as `rel="noopener noreferrer"` for `_blank` links. `javascript:` and `data:` links are rendered as plain text instead of links.
|
|
988
|
-
|
|
989
|
-
#### Render items
|
|
223
|
+
| Browser contributions and helpers | [`plugin-api.ts`](https://github.com/jmfederico/pi-web/blob/main/src/plugin-api.ts), published as `@jmfederico/pi-web/plugin-api` |
|
|
224
|
+
| Server lifecycle, peers, providers, and host capabilities | [`server-plugin-api.ts`](https://github.com/jmfederico/pi-web/blob/main/src/server-plugin-api.ts), published as `@jmfederico/pi-web/server-plugin-api` |
|
|
225
|
+
| A small browser plugin | [Info](https://github.com/jmfederico/pi-web/tree/main/pi-web-plugins/info) |
|
|
226
|
+
| A standalone browser/server package | [Workspace-provider example](https://github.com/jmfederico/pi-web/tree/main/examples/workspace-provider-plugin) |
|
|
227
|
+
| A production workspace provider and paired UI | [Git](https://github.com/jmfederico/pi-web/tree/main/pi-web-plugins/git) |
|
|
228
|
+
| Hosted sessions, live updates, and a Pi companion | [Captain's Log](https://github.com/jmfederico/pi-web/tree/main/pi-packages/captains-log) |
|
|
229
|
+
| A simpler initial-prompt workflow | [Workspace Reviews example](https://github.com/jmfederico/pi-web/tree/main/examples/session-bridge-plugin) |
|
|
230
|
+
| Package discovery rules and diagnostics | [Plugin catalog](https://github.com/jmfederico/pi-web/blob/main/src/server/piWebPluginCatalog.ts) |
|
|
990
231
|
|
|
991
|
-
|
|
232
|
+
The current browser contract is **API v4** and the server contract is **API v3**. Older entries need migration; there is no compatibility shim. The public source contracts and their tests are the reference for signatures, validation, limits, and lifecycle details.
|
|
992
233
|
|
|
993
|
-
|
|
994
|
-
class MyWorkspaceBadge extends HTMLElement {
|
|
995
|
-
set workspace(value) {
|
|
996
|
-
this._workspace = value;
|
|
997
|
-
this.textContent = value?.label ?? "workspace";
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
if (!customElements.get("my-workspace-badge")) {
|
|
1002
|
-
customElements.define("my-workspace-badge", MyWorkspaceBadge);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
export default {
|
|
1006
|
-
apiVersion: 2,
|
|
1007
|
-
name: "My Plugin",
|
|
1008
|
-
activate: ({ html }) => ({
|
|
1009
|
-
contributions: {
|
|
1010
|
-
workspaceLabels: [
|
|
1011
|
-
{
|
|
1012
|
-
id: "badge",
|
|
1013
|
-
order: 10,
|
|
1014
|
-
items: ({ workspace }) => [{
|
|
1015
|
-
type: "render",
|
|
1016
|
-
render: () => html`<my-workspace-badge .workspace=${workspace}></my-workspace-badge>`,
|
|
1017
|
-
}],
|
|
1018
|
-
},
|
|
1019
|
-
],
|
|
1020
|
-
},
|
|
1021
|
-
}),
|
|
1022
|
-
};
|
|
1023
|
-
```
|
|
1024
|
-
|
|
1025
|
-
## Calling paired workspace backends
|
|
1026
|
-
|
|
1027
|
-
Workspace panel and label contexts include an optional JSON-only backend helper. It is present only for a browser entry paired with an active server backend:
|
|
1028
|
-
|
|
1029
|
-
```js
|
|
1030
|
-
if (context.backend === undefined) throw new Error("Workspace backend unavailable");
|
|
1031
|
-
const result = await context.backend.request("summary", {
|
|
1032
|
-
includeIgnored: false,
|
|
1033
|
-
});
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
PI WEB binds the request to the browser module's original package id and active backend revision, plus the callback's selected machine, project, and workspace. The host resolves the current workspace owner again before dispatch. The call succeeds only when that same active plugin still owns the workspace and implements `WorkspaceProvider.request()`.
|
|
1037
|
-
|
|
1038
|
-
Operation ids must match `^[a-z][a-z0-9.-]*$` and be at most 128 characters. Inputs and results must contain only finite JSON values; functions, classes, `undefined`, cycles, and non-finite numbers are rejected. Requests, responses, owner resolution, and provider callbacks are size- and time-bounded.
|
|
1039
|
-
|
|
1040
|
-
The same helper works locally and through machine federation. It preserves machine scoping and active frontend/backend revision pairing, so browser plugins must not construct `/api/plugin-backends/...` or `/api/machines/...` URLs themselves. Missing/inactive backends, stale revisions/workspaces, ownership changes/conflicts, unsupported operations, invalid JSON, failures, and timeouts reject the promise with an attributed error.
|
|
1041
|
-
|
|
1042
|
-
## Reading workspace files
|
|
1043
|
-
|
|
1044
|
-
Workspace panels and workspace labels can read files through the documented `files` helper. PI WEB binds this helper to the callback's machine and workspace, so it works the same for local and federated machines.
|
|
1045
|
-
|
|
1046
|
-
```js
|
|
1047
|
-
workspacePanels: [
|
|
1048
|
-
{
|
|
1049
|
-
id: "workspace.env",
|
|
1050
|
-
title: "Env",
|
|
1051
|
-
render: ({ files }) => html`
|
|
1052
|
-
<my-env-viewer .files=${files}></my-env-viewer>
|
|
1053
|
-
`,
|
|
1054
|
-
},
|
|
1055
|
-
]
|
|
1056
|
-
|
|
1057
|
-
class MyEnvViewer extends HTMLElement {
|
|
1058
|
-
set files(value) {
|
|
1059
|
-
this._files = value;
|
|
1060
|
-
void this.load();
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
async load() {
|
|
1064
|
-
try {
|
|
1065
|
-
const file = await this._files.readFile(".env.example");
|
|
1066
|
-
this.textContent = file.binary ? "Binary file" : file.content;
|
|
1067
|
-
} catch (error) {
|
|
1068
|
-
this.textContent = error instanceof Error ? error.message : String(error);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
```
|
|
1073
|
-
|
|
1074
|
-
Labels should use the same helper through a plugin-owned cache because `items()` itself must return synchronously:
|
|
1075
|
-
|
|
1076
|
-
```js
|
|
1077
|
-
const envCache = new Map();
|
|
1078
|
-
|
|
1079
|
-
function envKey(machine, workspace) {
|
|
1080
|
-
return `${machine.id}:${workspace.id}:.env.local`;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
function loadEnvLabel(context) {
|
|
1084
|
-
const key = envKey(context.machine, context.workspace);
|
|
1085
|
-
const cached = envCache.get(key);
|
|
1086
|
-
if (cached !== undefined) return cached;
|
|
1087
|
-
|
|
1088
|
-
const pending = { status: "loading", label: undefined };
|
|
1089
|
-
envCache.set(key, pending);
|
|
1090
|
-
context.files.readFile(".env.local")
|
|
1091
|
-
.then((file) => {
|
|
1092
|
-
pending.status = "ready";
|
|
1093
|
-
pending.label = file.content.match(/^DEV_URL=(.+)$/m)?.[1];
|
|
1094
|
-
context.host.requestRender();
|
|
1095
|
-
})
|
|
1096
|
-
.catch(() => {
|
|
1097
|
-
pending.status = "missing";
|
|
1098
|
-
context.host.requestRender();
|
|
1099
|
-
});
|
|
1100
|
-
return pending;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
workspaceLabels: [
|
|
1104
|
-
{
|
|
1105
|
-
id: "dev-url",
|
|
1106
|
-
items: (context) => {
|
|
1107
|
-
const cached = loadEnvLabel(context);
|
|
1108
|
-
return cached.label === undefined ? [] : [{
|
|
1109
|
-
type: "link",
|
|
1110
|
-
text: cached.label,
|
|
1111
|
-
href: cached.label,
|
|
1112
|
-
target: "_blank",
|
|
1113
|
-
}];
|
|
1114
|
-
},
|
|
1115
|
-
},
|
|
1116
|
-
]
|
|
1117
|
-
```
|
|
1118
|
-
|
|
1119
|
-
The file response includes fields such as `path`, `content`, `truncated`, and `binary`. Be careful with sensitive files such as `.env`: browser entries are trusted code, and file contents are exposed to the plugin.
|
|
1120
|
-
|
|
1121
|
-
## Listing workspace files
|
|
1122
|
-
|
|
1123
|
-
`files.listFiles(path)` lists the entries of a workspace directory. Pass `""` for the workspace root. Like `readFile`, PI WEB binds the call to the callback's machine and workspace, so it works the same for local and federated machines.
|
|
1124
|
-
|
|
1125
|
-
```js
|
|
1126
|
-
const listing = await context.files.listFiles("src");
|
|
1127
|
-
for (const entry of listing.entries) {
|
|
1128
|
-
// entry: { name, path, type: "file" | "directory" | "symlink", size?, modifiedAt? }
|
|
1129
|
-
}
|
|
1130
|
-
```
|
|
1131
|
-
|
|
1132
|
-
The listing response includes `path`, `entries`, `scannedAt`, and `truncated`. When `truncated` is true, the server cut the listing short, so treat the entries as partial.
|
|
1133
|
-
|
|
1134
|
-
`listFiles` rejects when the directory does not exist or cannot be read, matching `readFile` error behavior. When a directory is optional, catch the error and treat it as an empty listing:
|
|
1135
|
-
|
|
1136
|
-
```js
|
|
1137
|
-
async function listSubdirectoryNames(context, path) {
|
|
1138
|
-
try {
|
|
1139
|
-
const listing = await context.files.listFiles(path);
|
|
1140
|
-
return listing.entries.filter((entry) => entry.type === "directory").map((entry) => entry.name);
|
|
1141
|
-
} catch {
|
|
1142
|
-
return [];
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
```
|
|
1146
|
-
|
|
1147
|
-
## Writing, deleting, and moving workspace files
|
|
1148
|
-
|
|
1149
|
-
Workspace panels and workspace labels can write, delete, and move files through the documented `files` helper. Like `readFile`, PI WEB binds these helpers to the callback's machine and workspace, so they work the same for local and federated machines.
|
|
1150
|
-
|
|
1151
|
-
### Writing files
|
|
1152
|
-
|
|
1153
|
-
```js
|
|
1154
|
-
workspacePanels: [
|
|
1155
|
-
{
|
|
1156
|
-
id: "workspace.generate",
|
|
1157
|
-
title: "Generate",
|
|
1158
|
-
render: ({ files }) => html`
|
|
1159
|
-
<button @click=${async () => {
|
|
1160
|
-
const result = await files.writeFile("output/result.txt", "Generated content\n");
|
|
1161
|
-
console.log("Wrote", result.path, result.size, "bytes");
|
|
1162
|
-
}}>Generate</button>
|
|
1163
|
-
`,
|
|
1164
|
-
},
|
|
1165
|
-
]
|
|
1166
|
-
```
|
|
1167
|
-
|
|
1168
|
-
### Binary writes
|
|
1169
|
-
|
|
1170
|
-
Pass a `Uint8Array` for binary content such as images:
|
|
1171
|
-
|
|
1172
|
-
```js
|
|
1173
|
-
const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a]);
|
|
1174
|
-
await files.writeFile("screenshots/thumb.png", png);
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
### Options
|
|
1178
|
-
|
|
1179
|
-
`files.writeFile` accepts an optional third argument:
|
|
234
|
+
A few design boundaries matter before implementation:
|
|
1180
235
|
|
|
1181
|
-
- `
|
|
1182
|
-
-
|
|
236
|
+
- Declare entries in `package.json` under `piWeb.plugins`; copy packaging from a standalone example. A package can contain multiple entries.
|
|
237
|
+
- Use only the public package entrypoints. Internal routes, services, and `dist/**` imports are not supported APIs.
|
|
238
|
+
- Keep browser assets in a narrow `browserRoot`: everything inside it is browser-public. Include the built dependencies and assets it needs, and keep secrets outside it. The installed plugin package is limited to 4,096 entries and 16 MiB, excluding `.git` and `node_modules`.
|
|
239
|
+
- Use host helpers for machine/workspace operations and module-relative URLs for plugin assets. Avoid hard-coded application paths.
|
|
240
|
+
- Rendering can happen repeatedly without a panel being mounted again. Own asynchronous state and use ordinary component lifecycle cleanup for connections, timers, and listeners.
|
|
241
|
+
- Keep workspace discovery separate from session-backed features, and keep agent behavior in a Pi companion rather than importing host internals.
|
|
1183
242
|
|
|
1184
|
-
|
|
1185
|
-
// Create only — throw if the file already exists
|
|
1186
|
-
await files.writeFile("config/new-config.json", jsonContent, { overwrite: false });
|
|
1187
|
-
```
|
|
1188
|
-
|
|
1189
|
-
### Deleting files
|
|
1190
|
-
|
|
1191
|
-
`files.deleteFile` removes a workspace file. It is idempotent: deleting a file that does not exist returns `{ existed: false }` instead of throwing.
|
|
1192
|
-
|
|
1193
|
-
```js
|
|
1194
|
-
const result = await files.deleteFile("temp/cache.json");
|
|
1195
|
-
console.log(result.existed ? "File deleted" : "File did not exist");
|
|
1196
|
-
```
|
|
1197
|
-
|
|
1198
|
-
### Moving files
|
|
1199
|
-
|
|
1200
|
-
`files.moveFile` renames or moves a file within the workspace, like `mv`. The default is safe: it will not overwrite an existing target file.
|
|
1201
|
-
|
|
1202
|
-
```js
|
|
1203
|
-
// Rename a file
|
|
1204
|
-
await files.moveFile("old-name.txt", "new-name.txt");
|
|
1205
|
-
|
|
1206
|
-
// Move into a subdirectory (creates intermediate dirs by default)
|
|
1207
|
-
await files.moveFile("file.txt", "archive/file.txt");
|
|
1208
|
-
|
|
1209
|
-
// Overwrite an existing target
|
|
1210
|
-
await files.moveFile("incoming.txt", "current.txt", { overwrite: true });
|
|
1211
|
-
|
|
1212
|
-
// Move without creating intermediate directories
|
|
1213
|
-
await files.moveFile("file.txt", "deep/nested/file.txt", { createDirs: false }); // throws if dirs don't exist
|
|
1214
|
-
```
|
|
1215
|
-
|
|
1216
|
-
`files.moveFile` accepts an optional third argument:
|
|
1217
|
-
|
|
1218
|
-
- `createDirs` (default `true`): create intermediate directories for the target path.
|
|
1219
|
-
- `overwrite` (default `false`): overwrite the target file if it exists. The default is safer than `writeFile` because moving is a more destructive operation.
|
|
1220
|
-
|
|
1221
|
-
### Error handling
|
|
1222
|
-
|
|
1223
|
-
All file mutations share the same safety layer:
|
|
1224
|
-
|
|
1225
|
-
- `overwrite: false` on `writeFile` or existing target on `moveFile` (default) throws if the file already exists.
|
|
1226
|
-
- Path traversal (e.g., `../../etc/passwd`) is blocked by the workspace safety layer.
|
|
1227
|
-
- Writing to or moving to a path that is a directory returns an error.
|
|
1228
|
-
- Deleting a directory returns an error.
|
|
1229
|
-
- Intermediate directory creation with `createDirs: false` fails if the parent directory does not exist.
|
|
1230
|
-
|
|
1231
|
-
After any mutation (`writeFile`, `deleteFile`, or `moveFile`), the File Explorer updates automatically. No explicit `refreshFiles()` call is needed from plugin code. For label and badge updates, call `context.host.requestRender()` if the UI should reflect the change.
|
|
1232
|
-
|
|
1233
|
-
### Security
|
|
1234
|
-
|
|
1235
|
-
Browser plugin entries are trusted code. File writes go through the same path safety validation as reads — paths are resolved and checked to stay inside the workspace root.
|
|
243
|
+
## Trust and recovery
|
|
1236
244
|
|
|
1237
|
-
|
|
245
|
+
**Install only trusted plugins.** Browser entries run in your page. Server entries run inside the session daemon with its filesystem, environment, and process permissions. They are not sandboxed; blocking or faulty server code can affect every session on that daemon. Timeouts help with cooperative work but cannot stop blocking code.
|
|
1238
246
|
|
|
1239
|
-
|
|
247
|
+
If a plugin is missing or fails, first check **Settings → PI WEB plugins** on the affected machine. Confirm it is installed, enabled, compatible, and active. Check the browser console for browser failures and `pi-web logs` on the target for server failures. A restart-required or stale state usually needs a session-daemon restart followed by a browser reload.
|
|
1240
248
|
|
|
1241
|
-
|
|
1242
|
-
render: ({ terminal }) => html`
|
|
1243
|
-
<button @click=${() => terminal.runCommand({
|
|
1244
|
-
title: "Build",
|
|
1245
|
-
command: "npm run build",
|
|
1246
|
-
open: true,
|
|
1247
|
-
metadata: { "my-plugin.task": "build" },
|
|
1248
|
-
})}>Build</button>
|
|
1249
|
-
`
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
Review command strings carefully. They are trusted shell commands executed in the workspace terminal.
|
|
1253
|
-
|
|
1254
|
-
## Private and experimental PI WEB APIs
|
|
1255
|
-
|
|
1256
|
-
PI WEB's `/api/...` HTTP and WebSocket routes, runtime-only browser fields, source files, Fastify instance, and internal services are private implementation details. They are outside the supported browser-v2 and server-v1 package contracts and may change or disappear.
|
|
1257
|
-
|
|
1258
|
-
The stable browser API is the documented helpers and the type-only `@jmfederico/pi-web/plugin-api` export; the stable server API is the narrow type-only `@jmfederico/pi-web/server-plugin-api` export. Use `context.backend.request()` for paired browser/server work. If browser code intentionally relies on another private surface, keep that dependency local and expect to revisit it after PI WEB upgrades. A server plugin must not import PI WEB source internals or private `dist/**` declarations.
|
|
1259
|
-
|
|
1260
|
-
## Async data and caching
|
|
1261
|
-
|
|
1262
|
-
PI WEB does not provide a plugin cache/invalidation framework. Keep host callbacks cheap:
|
|
1263
|
-
|
|
1264
|
-
- simple contributions should be synchronous and cheap;
|
|
1265
|
-
- expensive or async work should live inside the plugin;
|
|
1266
|
-
- custom elements in `type: "render"` label items or panels are a good place to own async loading;
|
|
1267
|
-
- dedupe async reads/commands and avoid unbounded polling;
|
|
1268
|
-
- clean up intervals/event listeners in custom elements' `disconnectedCallback()`.
|
|
1269
|
-
|
|
1270
|
-
## Agent implementation checklist
|
|
1271
|
-
|
|
1272
|
-
If you are an AI agent building or editing a PI WEB plugin, follow this checklist:
|
|
249
|
+
When the UI cannot recover, run these commands on the affected machine:
|
|
1273
250
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
7. Return arrays synchronously from workspace label `items()`; return an empty array to render nothing.
|
|
1281
|
-
8. Use documented browser helpers first: `files`, `terminal`, `backend`, `host.requestRender`, `workspace`, `machine`, `state`, and `prompt`. Never construct PI WEB backend, federation, or absolute asset URLs.
|
|
1282
|
-
9. In a server entry, return only the demonstrated lifecycle callbacks and at most one `workspaceProvider`; treat every supplied `AbortSignal` as operation-scoped and forward it to bounded work.
|
|
1283
|
-
10. Make provider claims conservative. Return exactly one main workspace, stable keys, absolute accessible directories, JSON data/metadata, and optional request/removal capabilities.
|
|
1284
|
-
11. Keep backend operations JSON-only, bounded, and provider-owned. Put no secrets in `publicMetadata`, browser responses, removal wording, or diagnostics.
|
|
1285
|
-
12. Keep the installed package at or below 4,096 entries and 16 MiB, and keep every browser-public file inside a narrow `browserRoot`.
|
|
1286
|
-
13. Treat both entries as trusted code. A server module shares sessiond's process and user permissions.
|
|
1287
|
-
14. For browser-only edits, reload or hard-reload the page. For a server-backed edit, restart sessiond and then reload the page.
|
|
1288
|
-
15. Warn that restarting `pi-web-sessiond.service` may interrupt active sessions/runtime ownership.
|
|
1289
|
-
|
|
1290
|
-
## Troubleshooting
|
|
1291
|
-
|
|
1292
|
-
Check discovery:
|
|
1293
|
-
|
|
1294
|
-
```bash
|
|
1295
|
-
curl http://127.0.0.1:8504/pi-web-plugins/manifest.json
|
|
251
|
+
```sh
|
|
252
|
+
pi-web plugins disable <plugin-id> --restart
|
|
253
|
+
pi-web plugins safe-start show
|
|
254
|
+
pi-web plugins safe-start set bundled-only --restart
|
|
255
|
+
pi-web plugins safe-start set none --restart
|
|
256
|
+
pi-web plugins safe-start clear --restart
|
|
1296
257
|
```
|
|
1297
258
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
```
|
|
259
|
+
- **Disable** keeps a named optional plugin from loading on the next daemon start.
|
|
260
|
+
- **Bundled-only** excludes external server plugins while retaining bundled tools.
|
|
261
|
+
- **None** imports no server plugins. Diagnosis and project-folder workspaces remain available, but Terminal and terminal-backed workflows do not.
|
|
262
|
+
- **Clear** restores ordinary startup after you repair or remove the problem package.
|
|
1303
263
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
- invalid plugin or contribution id;
|
|
1307
|
-
- missing default export, browser `apiVersion: 2` or server `apiVersion: 1`, non-empty `name`, or `activate` function;
|
|
1308
|
-
- missing `package.json`, incorrect `piWeb.plugins` metadata, neither module declared, missing/unsafe `browserRoot`, a browser module outside its root, a `.js` server entry without `"type": "module"`, or a dual entry explicitly marked `machineSpecific: false`;
|
|
1309
|
-
- legacy shortcuts such as `piWeb.plugin`, string plugin entries, or no-`package.json` fallback;
|
|
1310
|
-
- duplicate plugin ids; records are diagnosed, skipped rather than merged, and never renamed;
|
|
1311
|
-
- entry/root path is unsafe, points outside the package, enters `.git`/`node_modules`, does not exist, or the package exceeds 4,096 entries/16 MiB;
|
|
1312
|
-
- package is not installed through Pi or under `$PI_WEB_DATA_DIR/plugins` (`~/.pi-web/plugins` by default);
|
|
1313
|
-
- browser import/activation/render failure; check the browser console;
|
|
1314
|
-
- server state is failed, incompatible, unhealthy, disabled, missing, stale, or conflicted; check **Settings → PI WEB plugins** and `pi-web logs` on the target machine;
|
|
1315
|
-
- a server-backed browser entry is absent because sessiond is unavailable or its active source/settings/revisions do not match desired package state; restart sessiond, then reload the tab;
|
|
1316
|
-
- a federated target lacks the lifecycle/backend capability; update and restart PI WEB on that target instead of falling back to the gateway;
|
|
1317
|
-
- recovery is needed before plugin discovery/import; use `pi-web plugins safe-start show`, offline disable, or one of the documented safe-start levels.
|
|
1318
|
-
|
|
1319
|
-
A manual restart of `pi-web-sessiond.service` may interrupt active sessions/runtime ownership. Inspect active work first and do not assume a web/UI restart is sufficient.
|
|
264
|
+
Safe start remains set until cleared. `--restart` restarts only when PI WEB recognizes a safe installed-service action; otherwise it prints manual instructions. These commands edit configuration without loading plugin code. Use `--config /path/to/config.json` for a non-default configuration, and inspect active work before any restart.
|