@jmfederico/pi-web 1.202608.0 → 1.202608.2
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 +4 -4
- package/dist/cli.js +287 -113
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/CodeViewer-BMWwxG7q.js +4 -0
- package/dist/client/assets/{TerminalPanel-fYrKc3jh.js → TerminalPanel-CacQDIYn.js} +6 -6
- package/dist/client/assets/index-DUW2xnoV.js +4279 -0
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/index.html +3 -3
- package/dist/config.js +110 -73
- package/dist/config.js.map +1 -1
- package/dist/docker/piWebDockerCommandPlan.js +4 -3
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +602 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +374 -71
- package/dist/nativeServices/serviceDoctor.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +1 -1
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/{pi-web-plugins → pi-packages}/relays/markdownDocument.js +1 -1
- package/dist/pi-packages/relays/package.json +11 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/pi-web-plugin.js +4 -4
- package/dist/pi-packages/relays/prompts/relay-worktree.md +157 -0
- package/dist/pi-packages/relays/prompts/relay.md +152 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/relayDiscovery.js +1 -1
- package/dist/{pi-web-plugins → pi-packages}/relays/relaysPanelElement.js +1 -1
- package/dist/pi-packages/relays/skills/relay/SKILL.md +123 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/{server/git/gitService.js → pi-web-plugins/git/git-backend.js} +209 -60
- package/dist/pi-web-plugins/git/package.json +16 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +16 -2
- package/dist/pi-web-plugins/info/package.json +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +2 -2
- package/dist/pi-web-plugins/updates/package.json +1 -1
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +3 -3
- package/dist/piWebVersionReport.js +63 -10
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +33 -16
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +27 -19
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +41 -15
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +3 -21
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/knownAutoInstallPiPackages.js +30 -0
- package/dist/server/knownAutoInstallPiPackages.js.map +1 -0
- package/dist/server/machines/machineClient.js +23 -4
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +74 -17
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +165 -8
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineService.js +23 -0
- package/dist/server/machines/machineService.js.map +1 -1
- package/dist/server/piPackageIdentity.js +25 -0
- package/dist/server/piPackageIdentity.js.map +1 -0
- package/dist/server/piPackageService.js +69 -8
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +584 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +159 -261
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +41 -9
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/projectService.js +3 -1
- package/dist/server/projects/projectService.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +26 -11
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/requestCancellation.js +32 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +29 -30
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -1
- package/dist/server/sessiond/autoInstallPiPackages.js +63 -0
- package/dist/server/sessiond/autoInstallPiPackages.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +24 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -2
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +2 -1
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +284 -98
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/authService.js +15 -54
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +4 -3
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +18 -67
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +495 -142
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +24 -1
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +1 -14
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionModelScope.js +160 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +6 -6
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +55 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionSummaryScanner.js +96 -224
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +5 -9
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +1 -1
- package/dist/server/status/machineStatusRoutes.js +8 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +181 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +87 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/storage/piPackageDismissalStore.js +78 -0
- package/dist/server/storage/piPackageDismissalStore.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js +2 -1
- package/dist/server/terminalProxyRoutes.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +20 -13
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +18 -9
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +0 -5
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +47 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +1 -3
- package/dist/server/workspaces/workspaceContext.js.map +1 -1
- package/dist/server/workspaces/workspaceDeletionRoutes.js +35 -125
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
- package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +256 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +140 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/activeAgentProfile.js +3 -22
- package/dist/sessiond/activeAgentProfile.js.map +1 -1
- package/dist/sessiond/config.js +11 -0
- package/dist/sessiond/config.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +11 -9
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/activeAgentProfile.js +2 -41
- package/dist/shared/activeAgentProfile.js.map +1 -1
- package/dist/shared/activity.js +0 -3
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.js +7 -5
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +8 -6
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +36 -5
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machinePluginIds.js +4 -2
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +33 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +150 -0
- package/dist/shared/pluginApiTypes.js +3 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +5 -0
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/workspaceFiles.js +41 -2
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/config.md +105 -52
- package/docs/plugins.md +403 -146
- package/examples/workspace-provider-plugin/README.md +52 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +64 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/package.json +30 -11
- package/server-plugin-api.d.ts +1 -0
- package/dist/client/assets/CodeViewer-D91Sp61M.js +0 -4
- package/dist/client/assets/UnifiedDiffViewer-BafLWGtF.js +0 -39
- package/dist/client/assets/index-CA8q9_o7.js +0 -4025
- package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
- package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
- package/dist/pi-web-plugins/relays/package.json +0 -9
- package/dist/plugin-api/unstable.d.ts +0 -22
- package/dist/server/activity/workspaceActivityRoutes.js +0 -4
- package/dist/server/activity/workspaceActivityRoutes.js.map +0 -1
- package/dist/server/git/gitService.js.map +0 -1
- package/dist/server/gitRoutes.js +0 -23
- package/dist/server/gitRoutes.js.map +0 -1
- package/dist/server/sessions/parentSessionLocator.js +0 -75
- package/dist/server/sessions/parentSessionLocator.js.map +0 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
- package/dist/server/workspaces/imagePreviewService.js +0 -40
- package/dist/server/workspaces/imagePreviewService.js.map +0 -1
- package/dist/server/workspaces/workspaceService.js +0 -52
- package/dist/server/workspaces/workspaceService.js.map +0 -1
- package/dist/shared/apiTypes.d.ts +0 -1217
- package/dist/shared/thinkingLevels.d.ts +0 -27
- package/plugin-api/unstable.d.ts +0 -1
- /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/README.md +0 -0
- /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/marked.esm.js +0 -0
package/docs/plugins.md
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
# PI WEB plugin API
|
|
2
2
|
|
|
3
|
-
PI WEB plugins are trusted browser
|
|
3
|
+
PI WEB plugins are trusted packages that can extend the browser UI, provide workspace semantics in the session daemon, or pair both entries. They are intended for personal, team, and project-local customization, and simple enough for an LLM to create or modify directly.
|
|
4
4
|
|
|
5
5
|
Plugins can currently:
|
|
6
6
|
|
|
7
7
|
- add action-palette commands;
|
|
8
|
-
- add workspace tools/panels next to Files
|
|
8
|
+
- add workspace tools/panels next to Files and Terminal;
|
|
9
9
|
- add compact workspace-label items in the workspace list, panel header, and status bar;
|
|
10
10
|
- call browser APIs and documented PI WEB plugin context helpers;
|
|
11
11
|
- read workspace files and start workspace terminal commands through documented helpers;
|
|
12
|
-
- serve
|
|
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.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Browser entries run in the PI WEB page through browser plugin API v2. Declared server entries run in the session daemon through the separate server-plugin API v1. Plugins do not get raw Fastify access, arbitrary routes, concrete core services, a generic event bus, Pi model-provider registration, or a general server-hook API. Neither entry is sandboxed.
|
|
15
16
|
|
|
16
17
|
## Pi packages, Pi extensions, and PI WEB plugins
|
|
17
18
|
|
|
18
|
-
**Pi packages** are distribution bundles managed by Pi (`pi install`, `pi remove`, `pi update`). A Pi package can provide Pi extensions, skills, prompt templates, themes, context/system prompt files, and/or PI WEB
|
|
19
|
+
**Pi packages** are distribution bundles managed by Pi (`pi install`, `pi remove`, `pi update`). A Pi package can provide Pi extensions, skills, prompt templates, themes, context/system prompt files, and/or PI WEB plugins. Many Pi packages do not include a PI WEB plugin.
|
|
19
20
|
|
|
20
|
-
**Pi extensions** are runtime modules loaded by the session daemon. They can register Pi tools, hooks, commands, and model providers. They are not PI WEB plugins.
|
|
21
|
+
**Pi extensions** are runtime modules loaded by the session daemon. They can register Pi tools, hooks, commands, and model providers. They are not PI WEB plugins and use a different API and lifecycle.
|
|
21
22
|
|
|
22
|
-
**PI WEB plugins** are
|
|
23
|
+
**PI WEB plugins** are packages discovered from bundled, local, dev, and installed Pi-package sources. A plugin can declare a browser `module`, a sessiond `serverModule`, or both. A server entry can participate only in the documented PI WEB plugin lifecycle and workspace-provider contract; it cannot register Pi model providers or arbitrary hooks.
|
|
24
|
+
|
|
25
|
+
Pi loads ordinary Pi resources from installed Pi packages and from its user and project resource locations. PI WEB plugin discovery loads only the browser and server entries declared in `piWeb.plugins`; enabling or disabling a PI WEB plugin does not add or remove the package's Pi extensions, skills, prompt templates, themes, or context/system prompt files.
|
|
23
26
|
|
|
24
27
|
Use **Settings → Pi packages** to view configured Pi packages or install/remove/update a package. Enter only the package source, such as `npm:@scope/package`, a git/URL source, or a local path. PI WEB uses Pi's default package location, equivalent to `pi install <source>`, and does not ask for an install location.
|
|
25
28
|
|
|
26
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.
|
|
27
30
|
|
|
28
|
-
Use **Settings → PI WEB plugins** to
|
|
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.
|
|
29
32
|
|
|
30
|
-
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.
|
|
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).
|
|
31
34
|
|
|
32
35
|
## Pi extension dialogs in PI WEB
|
|
33
36
|
|
|
@@ -45,14 +48,27 @@ One browser-local caveat: reloading the browser while a new session is still bei
|
|
|
45
48
|
|
|
46
49
|
## Trust model
|
|
47
50
|
|
|
48
|
-
|
|
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
|
|
49
62
|
|
|
50
|
-
-
|
|
51
|
-
- they can read workspace files and start terminal commands through documented plugin helpers;
|
|
52
|
-
- they can render arbitrary Lit templates/custom elements in plugin contribution areas;
|
|
53
|
-
- they should not be installed from untrusted sources.
|
|
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:
|
|
54
64
|
|
|
55
|
-
|
|
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.
|
|
56
72
|
|
|
57
73
|
## What to ask AI to build
|
|
58
74
|
|
|
@@ -89,7 +105,7 @@ Before coding, read the PI WEB plugin docs:
|
|
|
89
105
|
https://pi-web.dev/plugins
|
|
90
106
|
Full API reference:
|
|
91
107
|
https://pi-web.dev/plugins.md
|
|
92
|
-
Keep the
|
|
108
|
+
Keep the browser entry on API v2 and any server entry on API v1.
|
|
93
109
|
After editing, check the manifest endpoint and browser-console failure cases.
|
|
94
110
|
```
|
|
95
111
|
|
|
@@ -123,7 +139,7 @@ Package metadata:
|
|
|
123
139
|
"private": true,
|
|
124
140
|
"piWeb": {
|
|
125
141
|
"plugins": [
|
|
126
|
-
{ "id": "info", "module": "pi-web-plugin.js" }
|
|
142
|
+
{ "id": "info", "browserRoot": ".", "module": "pi-web-plugin.js" }
|
|
127
143
|
]
|
|
128
144
|
}
|
|
129
145
|
}
|
|
@@ -133,7 +149,7 @@ Module shape excerpt:
|
|
|
133
149
|
|
|
134
150
|
```js
|
|
135
151
|
export default {
|
|
136
|
-
apiVersion:
|
|
152
|
+
apiVersion: 2,
|
|
137
153
|
name: "Info Plugin",
|
|
138
154
|
activate: ({ html, svg }) => ({
|
|
139
155
|
contributions: {
|
|
@@ -147,13 +163,42 @@ export default {
|
|
|
147
163
|
|
|
148
164
|
When copying the Info plugin, choose a new plugin id so it does not conflict with the bundled `info` plugin.
|
|
149
165
|
|
|
150
|
-
The Info panel doubles as an always-available PI WEB status view: it renders the host-provided `context.state.piWebStatus` (versions, installation, release state, machine, and workspace details) without issuing its own requests, and its action copies a plain-text diagnostics summary suitable for bug reports.
|
|
166
|
+
The Info panel doubles as an always-available PI WEB status view: it renders the host-provided `context.state.piWebStatus` (PI WEB and Pi versions, installation, release state, machine, and workspace details) without issuing its own requests, and its action copies a plain-text diagnostics summary suitable for bug reports.
|
|
151
167
|
|
|
152
168
|
PI WEB also ships an `updates` plugin that demonstrates dynamic `visible` and `badge` callbacks for tabs that only appear when the host has status messages or needs extra install visibility.
|
|
153
169
|
|
|
170
|
+
## Canonical dual-entry provider: bundled Git
|
|
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:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import type { PiWebPlugin } from "@jmfederico/pi-web/plugin-api";
|
|
186
|
+
import type { PiWebServerPlugin, WorkspaceProvider } from "@jmfederico/pi-web/server-plugin-api";
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Git declares `machineSpecific: true`, contributes a fallback workspace provider, and implements its status/diff backend and removal plan through the public provider callbacks. It receives no raw routes or private PI WEB services. Use it to understand the demonstrated contract, not as a template for Git-specific fields: replacement providers define their own private data, public metadata, backend operations, and removal wording.
|
|
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.
|
|
196
|
+
|
|
197
|
+
Copy the example from a checkout or from `node_modules/@jmfederico/pi-web/examples/workspace-provider-plugin`, then follow its README. It deliberately does not advertise removal; use the removal contract below when adding that capability.
|
|
198
|
+
|
|
154
199
|
## Local plugin usage
|
|
155
200
|
|
|
156
|
-
This works with the production native-service install. PI WEB discovers
|
|
201
|
+
This works with the production native-service install. PI WEB discovers packages from `~/.pi-web/plugins/<plugin-package>/`; if `PI_WEB_DATA_DIR` is set, use `$PI_WEB_DATA_DIR/plugins` instead. No PI WEB rebuild is required.
|
|
157
202
|
|
|
158
203
|
Symlink a plugin folder into PI WEB's local plugin directory:
|
|
159
204
|
|
|
@@ -162,46 +207,50 @@ mkdir -p ~/.pi-web/plugins
|
|
|
162
207
|
ln -s /path/to/plugin-folder ~/.pi-web/plugins/plugin-id
|
|
163
208
|
```
|
|
164
209
|
|
|
165
|
-
|
|
210
|
+
For a browser-only package, reload the PI WEB tab after installing or editing it. PI WEB uses a package-content revision in the module URL; hard reload if an already-open page still holds old JavaScript. For a package with `serverModule`, restart sessiond to activate the startup snapshot, then reload the browser. Editing files alone never hot-reloads or unloads server code.
|
|
166
211
|
|
|
167
212
|
## Remote machine plugins
|
|
168
213
|
|
|
169
|
-
When [machine federation](https://pi-web.dev/machines) is enabled, PI WEB
|
|
214
|
+
When [machine federation](https://pi-web.dev/machines) is enabled, PI WEB loads the selected remote machine's compatible browser plugins through the gateway and runs its server entries in that remote machine's session daemon. Contributions and helpers are machine-scoped:
|
|
215
|
+
|
|
216
|
+
- actions, workspace panels, and workspace labels appear only for the applicable selected machine;
|
|
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.
|
|
170
222
|
|
|
171
|
-
|
|
172
|
-
- plugin file and terminal helpers run against that machine;
|
|
173
|
-
- plugin code is loaded best-effort through the current gateway and cached for the browser page lifetime;
|
|
174
|
-
- if the gateway and remote machine both have an enabled plugin with the same original id, `machineSpecific` metadata decides whether the gateway copy is reused or only the selected machine's copy can appear;
|
|
175
|
-
- remote theme contributions are ignored for now because themes are app-wide;
|
|
176
|
-
- mixed PI WEB versions across federated machines are best-effort and not guaranteed compatible.
|
|
223
|
+
The remote manifest and backend bridge use a versioned lifecycle contract. A future/unsupported lifecycle version, a missing backend route, or a mismatched frontend/backend revision produces an explicit compatibility error; PI WEB does not silently run an unpaired server-backed UI.
|
|
177
224
|
|
|
178
|
-
|
|
225
|
+
Plugin/provider compatibility is intentionally all-or-nothing during a mixed-version fleet rollout. A newer gateway rejects an older target's whole remote plugin manifest when the target lacks the current lifecycle contract, so even that target's browser-only plugin contributions and Git panel are unavailable. In the other upgrade order, an older gateway still calls the legacy core Git routes removed by an updated target, so remote Git status/diff requests return `404`. Upgrade the gateway and target together, restart their updated web/API processes and the target session daemon, then reload the gateway tab. Other machine features remain subject to their own capability negotiation.
|
|
226
|
+
|
|
227
|
+
Remote desired enablement is stored in the remote machine's PI WEB config. Select that machine in **Settings → PI WEB plugins** to edit it, or open the machine directly/edit its config. Browser-only changes need a page reload. Server-backed changes need a restart of that remote session daemon followed by a page reload.
|
|
179
228
|
|
|
180
229
|
Plugin package metadata may set `machineSpecific: true` when the plugin's meaning is tied to the selected PI WEB machine:
|
|
181
230
|
|
|
182
|
-
- Omitted or `false`: use the gateway copy when the same
|
|
183
|
-
- `true`: the gateway copy only
|
|
231
|
+
- Omitted or `false`: valid for browser-only plugins; use the gateway copy when the same id is present remotely.
|
|
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.
|
|
184
233
|
|
|
185
|
-
For portable plugin assets, prefer URLs relative to the plugin module
|
|
234
|
+
For portable plugin assets, prefer URLs relative to the plugin module:
|
|
186
235
|
|
|
187
236
|
```js
|
|
188
237
|
const url = new URL("./asset.json", import.meta.url);
|
|
189
238
|
```
|
|
190
239
|
|
|
191
|
-
|
|
240
|
+
In browser API v2, activation `pluginId` is always the stable package/source id, locally and through federation. Compare it directly with `workspace.provider.pluginId` for ownership. `runtimePluginId` is the separately named host-unique id for qualified contribution references such as `${runtimePluginId}:workspace.panel`; a remote host may machine-scope it.
|
|
192
241
|
|
|
193
|
-
|
|
242
|
+
Do not construct absolute plugin asset routes from either identity. Module-relative URLs keep the host-selected runtime scope and deployment base automatically; hard-coded `/pi-web-plugins/...` paths can point at the wrong machine or break nested deployments.
|
|
194
243
|
|
|
195
|
-
|
|
244
|
+
## Manage PI WEB plugins
|
|
196
245
|
|
|
197
|
-
|
|
246
|
+
Open **Settings → PI WEB plugins** to compare desired package/config state with the active sessiond startup snapshot on the selected machine. The list includes bundled, local, dev, and Pi-package-supplied plugins, disabled discovered entries, and entries known only to the still-active snapshot. It reports browser-only, active, failed, incompatible, disabled, not-active, unknown, conflict, stale-revision, health, safe-mode, and restart-required states. Settings and diagnostics expose fingerprints and revisions, not plugin setting values.
|
|
198
247
|
|
|
199
|
-
Plugin
|
|
248
|
+
Plugin enablement is separate from package installation. Use **Settings → Pi packages** to install, remove, or update a Pi package. The PI WEB plugin panel writes the selected machine's top-level `plugins` config key. It remains possible to edit desired config while sessiond is unavailable, although active state cannot then be verified.
|
|
200
249
|
|
|
201
250
|
```json
|
|
202
251
|
{
|
|
203
252
|
"plugins": {
|
|
204
|
-
"
|
|
253
|
+
"git": {
|
|
205
254
|
"enabled": true,
|
|
206
255
|
"settings": {}
|
|
207
256
|
},
|
|
@@ -212,16 +261,56 @@ Plugin preferences are stored under the top-level `plugins` config key in the PI
|
|
|
212
261
|
}
|
|
213
262
|
```
|
|
214
263
|
|
|
215
|
-
Plugins are enabled by default.
|
|
264
|
+
Plugins are enabled by default. `plugins.<id>.enabled: false` removes a browser-only entry on the next page load and prevents a server entry from loading on the next sessiond start. The optional `settings` object must be JSON-compatible and is captured for a server entry only at sessiond startup.
|
|
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:
|
|
271
|
+
|
|
272
|
+
1. Install or update the package on the target machine.
|
|
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.
|
|
276
|
+
|
|
277
|
+
> **Manual session-daemon restart:** for the native systemd user service, run `systemctl --user restart pi-web-sessiond` (the unit is `pi-web-sessiond.service`). Restarting sessiond may interrupt active sessions and runtime ownership. Web/UI autoreload, restarting only the web/API service, browser reload, and Pi's `/reload` command do not activate server-plugin changes.
|
|
278
|
+
|
|
279
|
+
### Offline disable and safe start
|
|
216
280
|
|
|
217
|
-
|
|
281
|
+
Recovery commands edit global PI WEB config without contacting sessiond, discovering packages, or importing plugin code. Run them on the affected target machine. Add `--config /path/to/config.json` when its services use a non-default `PI_WEB_CONFIG`.
|
|
282
|
+
|
|
283
|
+
```bash
|
|
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
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- `disable` sets that plugin's desired `enabled` value to `false` while preserving unrelated config.
|
|
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.
|
|
218
300
|
|
|
219
301
|
## Built-in plugins
|
|
220
302
|
|
|
221
|
-
PI WEB ships core, discoverable plugins in the main `@jmfederico/pi-web` npm package. No separate `pi install` step is required
|
|
303
|
+
PI WEB ships core, discoverable plugins in the main `@jmfederico/pi-web` npm package. No separate `pi install` step is required. After updating PI WEB, manually restart sessiond so bundled server entries use the installed revision, then reload the browser tab. Browser-only bundled entries need only the tab reload.
|
|
222
304
|
|
|
223
305
|
Built-in plugins can be managed from **Settings → PI WEB plugins** or with the top-level `plugins` config key.
|
|
224
306
|
|
|
307
|
+
### Git
|
|
308
|
+
|
|
309
|
+
**Plugin id:** `git`
|
|
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.
|
|
311
|
+
|
|
312
|
+
Git is enabled by default and is the only production workspace provider bundled with PI WEB. An enabled primary third-party provider can claim a project before Git. Disabling `git` and restarting sessiond leaves the kernel project-folder workspace available; reload the browser afterward so Git contributions disappear. The generic Files, Terminal, and Session features continue to work in that folder workspace.
|
|
313
|
+
|
|
225
314
|
### Updates
|
|
226
315
|
|
|
227
316
|
**Plugin id:** `updates`
|
|
@@ -297,7 +386,7 @@ Review task configs before running them, especially in shared projects. Workspac
|
|
|
297
386
|
### Relays
|
|
298
387
|
|
|
299
388
|
**Plugin id:** `relays`
|
|
300
|
-
**What it does:** 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.
|
|
389
|
+
**What it does:** the `@jmfederico/pi-relay` Pi package supplies the generic Relay workflow through the `/relay` and `/relay-worktree` prompt templates and the `relay` skill. 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.
|
|
301
390
|
|
|
302
391
|
A relay is a directory of markdown notes under `.pi-web/relays/<name>/` in the workspace root — the convention used by the Relay method for chaining agent sessions. The tab lists each relay's documents with `status.md`, `charter.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.
|
|
303
392
|
|
|
@@ -305,19 +394,15 @@ Documents in subfolders are listed too. Folders appear as chips in the document
|
|
|
305
394
|
|
|
306
395
|
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.
|
|
307
396
|
|
|
308
|
-
|
|
397
|
+
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 skill 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.
|
|
309
398
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
"relays": { "enabled": false }
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
399
|
+
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` skill; 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.
|
|
400
|
+
|
|
401
|
+
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.
|
|
317
402
|
|
|
318
403
|
## Discovery and packaging
|
|
319
404
|
|
|
320
|
-
|
|
405
|
+
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`:
|
|
321
406
|
|
|
322
407
|
1. Bundled plugins in the PI WEB package:
|
|
323
408
|
|
|
@@ -335,7 +420,7 @@ PI WEB builds the gateway `/pi-web-plugins/manifest.json` from these sources:
|
|
|
335
420
|
|
|
336
421
|
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.
|
|
337
422
|
|
|
338
|
-
Remote machines expose their
|
|
423
|
+
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.
|
|
339
424
|
|
|
340
425
|
Plugin package directory names and plugin ids must be valid identifiers:
|
|
341
426
|
|
|
@@ -343,15 +428,27 @@ Plugin package directory names and plugin ids must be valid identifiers:
|
|
|
343
428
|
^[a-z][a-z0-9.-]*$
|
|
344
429
|
```
|
|
345
430
|
|
|
346
|
-
A package can expose one or more PI WEB plugin
|
|
431
|
+
A package can expose one or more PI WEB plugin entries. There is exactly one supported `package.json` metadata shape:
|
|
347
432
|
|
|
348
433
|
```json
|
|
349
434
|
{
|
|
350
435
|
"private": true,
|
|
436
|
+
"type": "module",
|
|
351
437
|
"piWeb": {
|
|
352
438
|
"plugins": [
|
|
353
|
-
{
|
|
354
|
-
|
|
439
|
+
{
|
|
440
|
+
"id": "review",
|
|
441
|
+
"browserRoot": "dist/review",
|
|
442
|
+
"module": "dist/review/index.js"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"id": "workspaces",
|
|
446
|
+
"browserRoot": "dist/browser",
|
|
447
|
+
"module": "dist/browser/index.js",
|
|
448
|
+
"serverModule": "dist/server.js",
|
|
449
|
+
"machineSpecific": true
|
|
450
|
+
},
|
|
451
|
+
{ "id": "server-only", "serverModule": "dist/server-only.js" }
|
|
355
452
|
]
|
|
356
453
|
}
|
|
357
454
|
}
|
|
@@ -360,103 +457,224 @@ A package can expose one or more PI WEB plugin modules. There is exactly one sup
|
|
|
360
457
|
Rules:
|
|
361
458
|
|
|
362
459
|
- `piWeb.plugins` must be an array of objects.
|
|
363
|
-
- Each entry must have an explicit `id` and `module`.
|
|
364
|
-
- `id` must match `^[a-z][a-z0-9.-]*$`.
|
|
365
|
-
-
|
|
366
|
-
- `
|
|
367
|
-
-
|
|
460
|
+
- Each entry must have an explicit `id` and at least one of `module` or `serverModule`.
|
|
461
|
+
- `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.
|
|
462
|
+
- 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.
|
|
463
|
+
- 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.
|
|
464
|
+
- 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.
|
|
465
|
+
- `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`.
|
|
466
|
+
- `plugins.<id>.settings` must be JSON-compatible for server entries; sessiond captures a private copy at startup and diagnostics expose only a fingerprint.
|
|
467
|
+
- 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.
|
|
368
468
|
- Legacy shortcuts such as `piWeb.plugin`, string entries in `piWeb.plugins`, `piWeb.id` fallback ids, and no-`package.json` fallbacks are not supported.
|
|
369
469
|
|
|
470
|
+
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.
|
|
471
|
+
|
|
370
472
|
### Manifest and assets
|
|
371
473
|
|
|
372
|
-
The manifest contains each
|
|
474
|
+
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:
|
|
373
475
|
|
|
374
476
|
```json
|
|
375
477
|
{
|
|
478
|
+
"lifecycleVersion": 1,
|
|
376
479
|
"plugins": [
|
|
377
480
|
{
|
|
378
|
-
"id": "
|
|
379
|
-
"module": "/pi-web-plugins/
|
|
481
|
+
"id": "workspaces",
|
|
482
|
+
"module": "/pi-web-plugins/workspaces/dist/browser/index.js?v=<content-revision>",
|
|
483
|
+
"backendRevision": "<active-server-revision>",
|
|
380
484
|
"source": "local",
|
|
381
485
|
"scope": "local",
|
|
382
|
-
"machineSpecific":
|
|
486
|
+
"machineSpecific": true
|
|
383
487
|
}
|
|
384
488
|
]
|
|
385
489
|
}
|
|
386
490
|
```
|
|
387
491
|
|
|
388
|
-
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.
|
|
492
|
+
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.
|
|
389
493
|
|
|
390
|
-
`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.
|
|
494
|
+
`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.
|
|
391
495
|
|
|
392
|
-
At an origin-root deployment, a
|
|
496
|
+
At an origin-root deployment, a browser-public file is available under its package-relative path:
|
|
393
497
|
|
|
394
498
|
```text
|
|
395
|
-
/pi-web-plugins/<plugin-id>/<path-
|
|
499
|
+
/pi-web-plugins/<plugin-id>/<package-relative-path-under-browserRoot>
|
|
396
500
|
```
|
|
397
501
|
|
|
398
|
-
|
|
502
|
+
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.
|
|
503
|
+
|
|
504
|
+
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:
|
|
399
505
|
|
|
400
506
|
```js
|
|
401
507
|
const iconUrl = new URL("./assets/icon.svg", import.meta.url);
|
|
402
508
|
```
|
|
403
509
|
|
|
404
|
-
The final installed plugin package must contain `assets/icon.svg` at that path relative to the final built module
|
|
510
|
+
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.
|
|
511
|
+
|
|
512
|
+
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.
|
|
513
|
+
|
|
514
|
+
## Pi packages shipped alongside bundled plugins
|
|
515
|
+
|
|
516
|
+
`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)).
|
|
517
|
+
|
|
518
|
+
`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` skill 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.
|
|
519
|
+
|
|
520
|
+
**Automatic install, with opt-out.** For a plain `pi` user this package is only ever installed by explicit `pi install`. For PI WEB, though, the session daemon reconciles a small registry of known auto-installable Pi packages (currently just `@jmfederico/pi-relay`) at startup, for the active agent profile: if a package matching one of these by its own declared `package.json` name is not already configured for that profile, sessiond installs it from its shipped local path automatically, the same way the Settings UI's Install action would. This reconciliation is best-effort — a failure (offline, a read-only agent directory, a package-manager error) is logged and never blocks or crashes session-daemon startup.
|
|
405
521
|
|
|
406
|
-
|
|
522
|
+
**Dismissal is remembered per profile.** Removing a known auto-installable package from **Settings → Pi packages** records that removal in a small store under `$PI_WEB_DATA_DIR` (state, not user-editable configuration — alongside `projects.json`/`machines.json`, not in `$PI_WEB_CONFIG`), keyed by the active agent profile directory and the package's declared name. Once dismissed for a profile, startup reconciliation does not reinstall it again for that profile; only an explicit reinstall brings it back.
|
|
523
|
+
|
|
524
|
+
**One-click reinstall.** A profile that dismissed (or never installed) a known auto-installable package can install it again from **Settings → Pi packages** without typing its on-disk path: an **Available packages** section lists every known package not currently configured for the selected target, each with an **Install** button that installs it from its shipped location directly. The section disappears once every known package is configured.
|
|
525
|
+
|
|
526
|
+
## Browser module shape and v2 migration
|
|
527
|
+
|
|
528
|
+
TypeScript browser entries should use a type-only import from the published declaration entrypoint. The built JavaScript must not import PI WEB source internals:
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
import type { PiWebPlugin } from "@jmfederico/pi-web/plugin-api";
|
|
532
|
+
|
|
533
|
+
const plugin: PiWebPlugin = {
|
|
534
|
+
apiVersion: 2,
|
|
535
|
+
name: "My Plugin",
|
|
536
|
+
activate: ({ pluginId, runtimePluginId, html }) => ({
|
|
537
|
+
contributions: {
|
|
538
|
+
actions: [{
|
|
539
|
+
id: "workspace.open",
|
|
540
|
+
title: "Open my panel",
|
|
541
|
+
run: ({ selectWorkspaceTool }) => {
|
|
542
|
+
selectWorkspaceTool(`${runtimePluginId}:workspace.my-panel`);
|
|
543
|
+
},
|
|
544
|
+
}],
|
|
545
|
+
workspacePanels: [{
|
|
546
|
+
id: "workspace.my-panel",
|
|
547
|
+
title: "My panel",
|
|
548
|
+
visible: ({ workspace }) => workspace.provider?.pluginId === pluginId,
|
|
549
|
+
render: ({ workspace }) => html`<p>${workspace.label}</p>`,
|
|
550
|
+
}],
|
|
551
|
+
},
|
|
552
|
+
}),
|
|
553
|
+
};
|
|
407
554
|
|
|
408
|
-
|
|
555
|
+
export default plugin;
|
|
556
|
+
```
|
|
409
557
|
|
|
410
|
-
The
|
|
558
|
+
The activation boundary is:
|
|
411
559
|
|
|
412
560
|
```ts
|
|
413
561
|
interface PiWebPlugin {
|
|
414
|
-
apiVersion:
|
|
562
|
+
apiVersion: 2;
|
|
415
563
|
name: string;
|
|
416
|
-
activate
|
|
564
|
+
activate(context: PluginActivationContext): PluginActivationResult;
|
|
417
565
|
}
|
|
418
566
|
|
|
419
567
|
interface PluginActivationContext {
|
|
420
|
-
apiVersion:
|
|
421
|
-
pluginId: string;
|
|
422
|
-
|
|
423
|
-
|
|
568
|
+
readonly apiVersion: 2;
|
|
569
|
+
readonly pluginId: string;
|
|
570
|
+
readonly runtimePluginId: string;
|
|
571
|
+
readonly html: HtmlTemplateTag;
|
|
572
|
+
readonly svg: SvgTemplateTag;
|
|
424
573
|
}
|
|
574
|
+
```
|
|
425
575
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
576
|
+
`activate()` is called once when the UI loads the plugin. Keep it cheap and synchronous: define contributions there, but move expensive or async work into actions, custom elements, or explicit user interactions.
|
|
577
|
+
|
|
578
|
+
Browser API v2 is a deliberate break: the host rejects browser v1 entries with the plugin/module identity and expected version; there is no v1 compatibility shim. Migrate a browser entry by setting `apiVersion: 2`, using stable `pluginId` for package/provider ownership, and using `runtimePluginId` when constructing a host-qualified contribution reference. Replace browser-v1 `refreshGit` with `refreshWorkspacePanels()` plus panel `onInvalidate()`. The browser-v1 `isGitRepo`, `isGitWorktree`, and top-level `workspace.branch` aliases were removed; use the provider-authored `workspace.label` for generic presentation, and keep provider-specific facts in `workspace.provider.metadata` or the owning backend. The former `@jmfederico/pi-web/plugin-api/unstable` type path is not part of v2 and is no longer exported.
|
|
579
|
+
|
|
580
|
+
Contribution ids authored in arrays remain local to the plugin. PI WEB qualifies them internally under the runtime identity:
|
|
581
|
+
|
|
582
|
+
```text
|
|
583
|
+
<runtime-plugin-id>:<local-contribution-id>
|
|
429
584
|
```
|
|
430
585
|
|
|
431
|
-
|
|
586
|
+
For a local plugin the runtime and source ids are normally equal. A federated registration may machine-scope `runtimePluginId`, while `pluginId` and `workspace.provider.pluginId` remain the same source id.
|
|
432
587
|
|
|
433
|
-
|
|
434
|
-
|
|
588
|
+
## Server module and workspace provider shape
|
|
589
|
+
|
|
590
|
+
TypeScript server entries import the separately published Node declarations with `import type`:
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
import type {
|
|
594
|
+
PiWebServerPlugin,
|
|
595
|
+
WorkspaceProvider,
|
|
596
|
+
} from "@jmfederico/pi-web/server-plugin-api";
|
|
597
|
+
|
|
598
|
+
const provider: WorkspaceProvider = {
|
|
599
|
+
async probe(project, signal) {
|
|
600
|
+
// Return "claim" only when this provider owns the project's semantics.
|
|
601
|
+
return await projectIsSupported(project, signal) ? "claim" : "pass";
|
|
602
|
+
},
|
|
603
|
+
async list(project, signal) {
|
|
604
|
+
return await listProviderWorkspaces(project, signal);
|
|
605
|
+
},
|
|
606
|
+
async request({ project, workspace, operation, input, signal }) {
|
|
607
|
+
return await handleProviderOperation({ project, workspace, operation, input, signal });
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const plugin: PiWebServerPlugin = {
|
|
435
612
|
apiVersion: 1,
|
|
436
|
-
name: "My
|
|
437
|
-
activate
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
}
|
|
613
|
+
name: "My Workspace Provider",
|
|
614
|
+
activate(context) {
|
|
615
|
+
return {
|
|
616
|
+
workspaceProvider: provider,
|
|
617
|
+
health: async (signal) => ({ status: "healthy" }),
|
|
618
|
+
stop: async (signal) => { /* release plugin-owned resources */ },
|
|
619
|
+
};
|
|
620
|
+
},
|
|
444
621
|
};
|
|
622
|
+
|
|
623
|
+
export default plugin;
|
|
445
624
|
```
|
|
446
625
|
|
|
447
|
-
|
|
626
|
+
The default export has `apiVersion: 1`, a non-empty `name`, and `activate(context)`. The activation result can contain:
|
|
448
627
|
|
|
449
|
-
|
|
628
|
+
```ts
|
|
629
|
+
interface ServerPluginActivation {
|
|
630
|
+
workspaceProvider?: WorkspaceProvider;
|
|
631
|
+
start?(signal: AbortSignal): void | Promise<void>;
|
|
632
|
+
stop?(signal: AbortSignal): void | Promise<void>;
|
|
633
|
+
health?(signal: AbortSignal): ServerPluginHealth | Promise<ServerPluginHealth>;
|
|
634
|
+
}
|
|
635
|
+
```
|
|
450
636
|
|
|
451
|
-
|
|
452
|
-
|
|
637
|
+
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.
|
|
638
|
+
|
|
639
|
+
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.
|
|
640
|
+
|
|
641
|
+
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.
|
|
642
|
+
|
|
643
|
+
### Workspace provider contract
|
|
644
|
+
|
|
645
|
+
```ts
|
|
646
|
+
interface WorkspaceProvider {
|
|
647
|
+
fallback?: boolean;
|
|
648
|
+
probe(project: ProjectInput, signal: AbortSignal): Promise<"claim" | "pass">;
|
|
649
|
+
list(project: ProjectInput, signal: AbortSignal): Promise<ProviderWorkspace[]>;
|
|
650
|
+
request?(context: ProviderRequestContext): Promise<JsonValue>;
|
|
651
|
+
prepareRemove?(context: ProviderRemoveContext): Promise<WorkspaceRemovePlan>;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
interface ProviderWorkspace {
|
|
655
|
+
key: string;
|
|
656
|
+
path: string;
|
|
657
|
+
label: string;
|
|
658
|
+
isMain: boolean;
|
|
659
|
+
data?: JsonValue;
|
|
660
|
+
publicMetadata?: JsonObject;
|
|
661
|
+
removal?: { actionLabel: string; confirmation: string };
|
|
662
|
+
}
|
|
453
663
|
```
|
|
454
664
|
|
|
455
|
-
|
|
665
|
+
- `probe()` must return only `"claim"` or `"pass"`. Leave `fallback` unset/false for a replacement that should run before bundled Git.
|
|
666
|
+
- `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.
|
|
667
|
+
- `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.
|
|
668
|
+
- `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.
|
|
669
|
+
- `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.
|
|
670
|
+
- `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.
|
|
671
|
+
- Provider failures and conflicts are diagnostics. A claimant that fails `list()` does not permit fallback takeover for the same resolution.
|
|
672
|
+
|
|
673
|
+
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.
|
|
456
674
|
|
|
457
675
|
## Contributions
|
|
458
676
|
|
|
459
|
-
|
|
677
|
+
The workspace-related contribution arrays returned by `activate()` are:
|
|
460
678
|
|
|
461
679
|
```ts
|
|
462
680
|
interface PluginContributions {
|
|
@@ -493,6 +711,7 @@ interface PluginAction {
|
|
|
493
711
|
title: string;
|
|
494
712
|
description?: string;
|
|
495
713
|
shortcut?: string;
|
|
714
|
+
shortcutAliases?: QualifiedContributionId[];
|
|
496
715
|
group?: string;
|
|
497
716
|
enabled?: (context: PluginRuntimeContext) => boolean;
|
|
498
717
|
disabledReason?: (context: PluginRuntimeContext) => string | undefined;
|
|
@@ -510,6 +729,8 @@ interface PluginRuntimeContext {
|
|
|
510
729
|
selectedMachine?: PluginMachine;
|
|
511
730
|
selectedWorkspace?: Workspace;
|
|
512
731
|
selectedSession?: unknown;
|
|
732
|
+
workspaceTool?: string;
|
|
733
|
+
mainView?: string;
|
|
513
734
|
piWebStatus?: PiWebStatusResponse;
|
|
514
735
|
};
|
|
515
736
|
prompt: PluginPromptEditor;
|
|
@@ -518,11 +739,15 @@ interface PluginRuntimeContext {
|
|
|
518
739
|
addProject: () => void | Promise<void>;
|
|
519
740
|
configureAuth: () => void | Promise<void>;
|
|
520
741
|
logoutAuth: () => void | Promise<void>;
|
|
742
|
+
openThemePicker: () => void;
|
|
743
|
+
selectMainView: (view: string) => void;
|
|
521
744
|
selectWorkspaceTool: (tool: QualifiedContributionId) => void;
|
|
522
745
|
openTerminal: (options?: { terminalId?: string }) => void;
|
|
523
746
|
refreshFiles: () => void | Promise<void>;
|
|
524
|
-
|
|
747
|
+
refreshWorkspacePanels: (panelId?: QualifiedContributionId) => void | Promise<void>;
|
|
748
|
+
refreshAppData: () => void | Promise<void>;
|
|
525
749
|
checkForPiWebUpdates?: () => void | Promise<void>;
|
|
750
|
+
reloadPage: () => void;
|
|
526
751
|
startSession: () => void | Promise<void>;
|
|
527
752
|
archiveSession: () => void | Promise<void>;
|
|
528
753
|
stopActiveWork: () => void | Promise<void>;
|
|
@@ -532,13 +757,15 @@ interface PluginRuntimeContext {
|
|
|
532
757
|
Notes:
|
|
533
758
|
|
|
534
759
|
- `state` is a snapshot of current UI state when actions are built.
|
|
535
|
-
- The stable state fields are `state.selectedMachine`, `state.selectedWorkspace`, `state.selectedSession`, 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.
|
|
760
|
+
- 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.
|
|
536
761
|
- Other `state` fields may exist at runtime, but they are private PI WEB internals that may graduate into stable helpers, change shape, or disappear.
|
|
537
762
|
- `enabled` is evaluated when the action palette asks for actions.
|
|
763
|
+
- `shortcutAliases` is for migration only: list former fully qualified action ids whose saved shortcut preference should still apply to this action.
|
|
538
764
|
- `selectWorkspaceTool()` expects a qualified panel id such as `my-plugin:workspace.info`.
|
|
539
765
|
- `openTerminal()` switches to the built-in terminal panel. Pass `{ terminalId }` to deep-link to a specific terminal.
|
|
766
|
+
- `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.
|
|
540
767
|
- `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.
|
|
541
|
-
- Only fields documented here and declared
|
|
768
|
+
- 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.
|
|
542
769
|
|
|
543
770
|
### Prompt editor API
|
|
544
771
|
|
|
@@ -589,7 +816,7 @@ workspacePanels: [
|
|
|
589
816
|
</svg>
|
|
590
817
|
`,
|
|
591
818
|
order: 100,
|
|
592
|
-
visible: ({ workspace }) => workspace.
|
|
819
|
+
visible: ({ workspace }) => workspace.isMain,
|
|
593
820
|
render: ({ workspace }) => html`
|
|
594
821
|
<section class="toolbar"><strong>Info</strong></section>
|
|
595
822
|
<section class="viewer">
|
|
@@ -609,8 +836,10 @@ interface WorkspacePanelContribution {
|
|
|
609
836
|
title: string;
|
|
610
837
|
icon?: TemplateResult;
|
|
611
838
|
order?: number;
|
|
839
|
+
routeAliases?: string[];
|
|
612
840
|
visible?: (context: WorkspacePanelContext) => boolean;
|
|
613
841
|
badge?: (context: WorkspacePanelContext) => string | number | TemplateResult | undefined;
|
|
842
|
+
onInvalidate?: (context: WorkspacePanelContext) => void | Promise<void>;
|
|
614
843
|
render: (context: WorkspacePanelContext) => TemplateResult;
|
|
615
844
|
}
|
|
616
845
|
|
|
@@ -625,6 +854,9 @@ interface WorkspacePanelContext {
|
|
|
625
854
|
deleteFile(path: string): Promise<DeleteWorkspaceFileResponse>;
|
|
626
855
|
moveFile(fromPath: string, toPath: string, options?: MoveWorkspaceFileOptions): Promise<MoveWorkspaceFileResponse>;
|
|
627
856
|
};
|
|
857
|
+
backend?: {
|
|
858
|
+
request(operation: string, input: JsonValue): Promise<JsonValue>;
|
|
859
|
+
};
|
|
628
860
|
prompt: PluginPromptEditor;
|
|
629
861
|
terminal: {
|
|
630
862
|
open(options?: { terminalId?: string }): void;
|
|
@@ -643,9 +875,7 @@ interface WorkspacePanelContext {
|
|
|
643
875
|
|
|
644
876
|
`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.
|
|
645
877
|
|
|
646
|
-
`machine`, `workspace`, `files`, `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 workspace files](#writing-workspace-files). 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.
|
|
647
|
-
|
|
648
|
-
For compatibility, PI WEB still provides the old `context.openTerminal()` workspace-panel helper at runtime. It is deprecated, intentionally omitted from the public TypeScript declarations, and planned for removal in v2. Existing JavaScript plugins keep working, while typed plugins should migrate to `context.terminal.open()`.
|
|
878
|
+
`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`.
|
|
649
879
|
|
|
650
880
|
Useful workspace and machine shapes:
|
|
651
881
|
|
|
@@ -657,18 +887,21 @@ interface PluginMachine {
|
|
|
657
887
|
}
|
|
658
888
|
|
|
659
889
|
interface Workspace {
|
|
660
|
-
id: string;
|
|
661
|
-
projectId: string;
|
|
662
|
-
path: string;
|
|
663
|
-
label: string;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
890
|
+
readonly id: string;
|
|
891
|
+
readonly projectId: string;
|
|
892
|
+
readonly path: string;
|
|
893
|
+
readonly label: string;
|
|
894
|
+
readonly isMain: boolean;
|
|
895
|
+
readonly provider?: {
|
|
896
|
+
readonly pluginId: string;
|
|
897
|
+
readonly capabilities: { readonly request: boolean; readonly remove: boolean };
|
|
898
|
+
readonly metadata?: JsonObject;
|
|
899
|
+
};
|
|
900
|
+
readonly removal?: { readonly actionLabel: string; readonly confirmation: string };
|
|
668
901
|
}
|
|
669
902
|
```
|
|
670
903
|
|
|
671
|
-
`machine.id` is included in panel contexts so plugins can keep caches machine-scoped. Do not infer the selected machine from global browser state.
|
|
904
|
+
`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.
|
|
672
905
|
|
|
673
906
|
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.
|
|
674
907
|
|
|
@@ -716,13 +949,16 @@ interface WorkspaceLabelContext {
|
|
|
716
949
|
deleteFile(path: string): Promise<DeleteWorkspaceFileResponse>;
|
|
717
950
|
moveFile(fromPath: string, toPath: string, options?: MoveWorkspaceFileOptions): Promise<MoveWorkspaceFileResponse>;
|
|
718
951
|
};
|
|
952
|
+
backend?: {
|
|
953
|
+
request(operation: string, input: JsonValue): Promise<JsonValue>;
|
|
954
|
+
};
|
|
719
955
|
host: {
|
|
720
956
|
requestRender(): void;
|
|
721
957
|
};
|
|
722
958
|
}
|
|
723
959
|
```
|
|
724
960
|
|
|
725
|
-
`machine`, `workspace`, `files`, 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 workspace files](#writing-workspace-files). Include `machine.id` in
|
|
961
|
+
`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.
|
|
726
962
|
|
|
727
963
|
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.
|
|
728
964
|
|
|
@@ -754,7 +990,7 @@ Use render items when a label contribution needs custom UI, async data, or cachi
|
|
|
754
990
|
class MyWorkspaceBadge extends HTMLElement {
|
|
755
991
|
set workspace(value) {
|
|
756
992
|
this._workspace = value;
|
|
757
|
-
this.textContent = value?.
|
|
993
|
+
this.textContent = value?.label ?? "workspace";
|
|
758
994
|
}
|
|
759
995
|
}
|
|
760
996
|
|
|
@@ -763,7 +999,7 @@ if (!customElements.get("my-workspace-badge")) {
|
|
|
763
999
|
}
|
|
764
1000
|
|
|
765
1001
|
export default {
|
|
766
|
-
apiVersion:
|
|
1002
|
+
apiVersion: 2,
|
|
767
1003
|
name: "My Plugin",
|
|
768
1004
|
activate: ({ html }) => ({
|
|
769
1005
|
contributions: {
|
|
@@ -782,6 +1018,23 @@ export default {
|
|
|
782
1018
|
};
|
|
783
1019
|
```
|
|
784
1020
|
|
|
1021
|
+
## Calling paired workspace backends
|
|
1022
|
+
|
|
1023
|
+
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:
|
|
1024
|
+
|
|
1025
|
+
```js
|
|
1026
|
+
if (context.backend === undefined) throw new Error("Workspace backend unavailable");
|
|
1027
|
+
const result = await context.backend.request("summary", {
|
|
1028
|
+
includeIgnored: false,
|
|
1029
|
+
});
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
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()`.
|
|
1033
|
+
|
|
1034
|
+
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.
|
|
1035
|
+
|
|
1036
|
+
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.
|
|
1037
|
+
|
|
785
1038
|
## Reading workspace files
|
|
786
1039
|
|
|
787
1040
|
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.
|
|
@@ -859,7 +1112,7 @@ workspaceLabels: [
|
|
|
859
1112
|
]
|
|
860
1113
|
```
|
|
861
1114
|
|
|
862
|
-
The file response includes fields such as `path`, `content`, `truncated`, and `binary`. Be careful with sensitive files such as `.env`:
|
|
1115
|
+
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.
|
|
863
1116
|
|
|
864
1117
|
## Listing workspace files
|
|
865
1118
|
|
|
@@ -975,7 +1228,7 @@ After any mutation (`writeFile`, `deleteFile`, or `moveFile`), the File Explorer
|
|
|
975
1228
|
|
|
976
1229
|
### Security
|
|
977
1230
|
|
|
978
|
-
|
|
1231
|
+
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.
|
|
979
1232
|
|
|
980
1233
|
## Running workspace terminal commands
|
|
981
1234
|
|
|
@@ -996,9 +1249,9 @@ Review command strings carefully. They are trusted shell commands executed in th
|
|
|
996
1249
|
|
|
997
1250
|
## Private and experimental PI WEB APIs
|
|
998
1251
|
|
|
999
|
-
PI WEB's `/api/...` HTTP and WebSocket routes
|
|
1252
|
+
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.
|
|
1000
1253
|
|
|
1001
|
-
|
|
1254
|
+
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.
|
|
1002
1255
|
|
|
1003
1256
|
## Async data and caching
|
|
1004
1257
|
|
|
@@ -1014,21 +1267,21 @@ PI WEB does not provide a plugin cache/invalidation framework. Keep host callbac
|
|
|
1014
1267
|
|
|
1015
1268
|
If you are an AI agent building or editing a PI WEB plugin, follow this checklist:
|
|
1016
1269
|
|
|
1017
|
-
1. Create or update a
|
|
1018
|
-
2. Use
|
|
1019
|
-
3.
|
|
1020
|
-
4.
|
|
1021
|
-
5.
|
|
1022
|
-
6.
|
|
1023
|
-
7.
|
|
1024
|
-
8.
|
|
1025
|
-
9.
|
|
1026
|
-
10.
|
|
1027
|
-
11.
|
|
1028
|
-
12.
|
|
1029
|
-
13.
|
|
1030
|
-
14.
|
|
1031
|
-
15.
|
|
1270
|
+
1. Create or update a package folder with `package.json` and at least one built JavaScript entry. Declare `"type": "module"` when a `.js` server entry is present, or emit it as `.mjs`.
|
|
1271
|
+
2. Use `piWeb.plugins` entries shaped as `{ id, browserRoot?, module?, serverModule?, machineSpecific? }`; declare at least one module, give every browser entry a safe root containing its module, and use non-reserved ids matching `^[a-z][a-z0-9.-]*$`.
|
|
1272
|
+
3. Import browser types only from `@jmfederico/pi-web/plugin-api` and server types only from `@jmfederico/pi-web/server-plugin-api`, always with `import type`; do not use private subpaths or source internals.
|
|
1273
|
+
4. Default-export `{ apiVersion: 2, name, activate }` from a browser entry and `{ apiVersion: 1, name, activate }` from a server entry.
|
|
1274
|
+
5. In a browser entry, use source `pluginId` for ownership and `runtimePluginId` for qualified contribution references; return contributions synchronously and use the activation context's `html`/`svg` tags.
|
|
1275
|
+
6. Add actions for command-palette operations, panels for larger workspace UI, and labels for compact inline metadata.
|
|
1276
|
+
7. Return arrays synchronously from workspace label `items()`; return an empty array to render nothing.
|
|
1277
|
+
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.
|
|
1278
|
+
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.
|
|
1279
|
+
10. Make provider claims conservative. Return exactly one main workspace, stable keys, absolute accessible directories, JSON data/metadata, and optional request/removal capabilities.
|
|
1280
|
+
11. Keep backend operations JSON-only, bounded, and provider-owned. Put no secrets in `publicMetadata`, browser responses, removal wording, or diagnostics.
|
|
1281
|
+
12. Keep the installed package at or below 4,096 entries and 16 MiB, and keep every browser-public file inside a narrow `browserRoot`.
|
|
1282
|
+
13. Treat both entries as trusted code. A server module shares sessiond's process and user permissions.
|
|
1283
|
+
14. For browser-only edits, reload or hard-reload the page. For a server-backed edit, restart sessiond and then reload the page.
|
|
1284
|
+
15. Warn that restarting `pi-web-sessiond.service` may interrupt active sessions/runtime ownership.
|
|
1032
1285
|
|
|
1033
1286
|
## Troubleshooting
|
|
1034
1287
|
|
|
@@ -1046,13 +1299,17 @@ curl http://127.0.0.1:8504/pi-web-plugins/my-plugin/pi-web-plugin.js
|
|
|
1046
1299
|
|
|
1047
1300
|
Common issues:
|
|
1048
1301
|
|
|
1049
|
-
- invalid plugin
|
|
1050
|
-
- missing default export;
|
|
1051
|
-
- missing `
|
|
1052
|
-
- missing `package.json` or incorrect `piWeb.plugins` metadata;
|
|
1302
|
+
- invalid plugin or contribution id;
|
|
1303
|
+
- missing default export, browser `apiVersion: 2` or server `apiVersion: 1`, non-empty `name`, or `activate` function;
|
|
1304
|
+
- 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`;
|
|
1053
1305
|
- legacy shortcuts such as `piWeb.plugin`, string plugin entries, or no-`package.json` fallback;
|
|
1054
|
-
- duplicate plugin ids;
|
|
1055
|
-
- entry
|
|
1056
|
-
-
|
|
1057
|
-
-
|
|
1058
|
-
-
|
|
1306
|
+
- duplicate plugin ids; records are diagnosed, skipped rather than merged, and never renamed;
|
|
1307
|
+
- 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;
|
|
1308
|
+
- package is not installed through Pi or under `$PI_WEB_DATA_DIR/plugins` (`~/.pi-web/plugins` by default);
|
|
1309
|
+
- browser import/activation/render failure; check the browser console;
|
|
1310
|
+
- server state is failed, incompatible, unhealthy, disabled, missing, stale, or conflicted; check **Settings → PI WEB plugins** and `pi-web logs` on the target machine;
|
|
1311
|
+
- 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;
|
|
1312
|
+
- a federated target lacks the lifecycle/backend capability; update and restart PI WEB on that target instead of falling back to the gateway;
|
|
1313
|
+
- recovery is needed before plugin discovery/import; use `pi-web plugins safe-start show`, offline disable, or one of the documented safe-start levels.
|
|
1314
|
+
|
|
1315
|
+
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.
|