@hachej/boring-workspace 0.1.17 → 0.1.20

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.
Files changed (37) hide show
  1. package/README.md +36 -34
  2. package/dist/{FileTree-Dvaud3jU.js → FileTree-DHVB9rpk.js} +15 -15
  3. package/dist/{MarkdownEditor-sLkqTXDj.js → MarkdownEditor-L1KDH0bM.js} +1 -1
  4. package/dist/{WorkspaceLoadingState-zLzh1tGc.js → WorkspaceLoadingState-DYDxUYnx.js} +114 -110
  5. package/dist/WorkspaceProvider-CDPaAO5u.js +5971 -0
  6. package/dist/app-front.d.ts +94 -107
  7. package/dist/app-front.js +243 -233
  8. package/dist/app-server.d.ts +130 -15
  9. package/dist/app-server.js +1569 -304
  10. package/dist/{bootstrapServer-BreQ9QBc.d.ts → createInMemoryBridge-BDxDzihm.d.ts} +11 -26
  11. package/dist/manifest-CyNNdfYz.d.ts +58 -0
  12. package/dist/plugin.d.ts +199 -0
  13. package/dist/plugin.js +300 -0
  14. package/dist/server.d.ts +239 -4
  15. package/dist/server.js +901 -78
  16. package/dist/shared.d.ts +4 -112
  17. package/dist/surface-COYagY2m.d.ts +111 -0
  18. package/dist/testing.d.ts +19 -1
  19. package/dist/testing.js +2 -2
  20. package/dist/{agent-tool-DEtfQPVB.d.ts → ui-bridge-Gfh1MMgl.d.ts} +30 -30
  21. package/dist/workspace.css +36 -0
  22. package/dist/workspace.d.ts +165 -120
  23. package/dist/workspace.js +330 -377
  24. package/docs/INTERFACES.md +9 -9
  25. package/docs/PLUGIN_STRUCTURE.md +39 -145
  26. package/docs/PLUGIN_SYSTEM.md +355 -0
  27. package/docs/README.md +6 -1
  28. package/docs/plans/README.md +1 -0
  29. package/docs/plans/archive/HOT_RELOADABLE_AGENT_PLUGINS_PLAN.md +218 -0
  30. package/docs/plans/archive/RELOAD_PLUGGABILITY_PLAN.md +174 -0
  31. package/docs/plans/archive/UNIFIED_PLUGIN_SYSTEM_PLAN.md +769 -0
  32. package/package.json +11 -5
  33. package/dist/CommandPalette-CJHuTJlD.js +0 -5716
  34. package/docs/bridge.md +0 -135
  35. package/docs/panels.md +0 -102
  36. package/docs/plugins.md +0 -158
  37. /package/docs/plans/{MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md → archive/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  Last updated: 2026-05-02
4
4
 
5
- `@boring/workspace` is a workspace UI and bridge package. The app shell owns
5
+ `@hachej/boring-workspace` is a workspace UI and bridge package. The app shell owns
6
6
  auth, routing, application persistence, and the concrete chat component.
7
7
  Workspace owns layout runtime, layout preferences, plugin registries, bridge
8
8
  commands, and default workspace plugins.
@@ -19,16 +19,17 @@ commands, and default workspace plugins.
19
19
  and no agent package imports.
20
20
  - `src/app/` hosts front/server composition helpers such as
21
21
  `WorkspaceAgentFront` and `createWorkspaceAgentServer`, where workspace app
22
- code may compose with documented `@boring/agent/server` APIs.
22
+ code may compose with documented `@hachej/boring-agent/server` APIs.
23
23
 
24
24
  ## Core Contracts
25
25
 
26
- - Plugin outputs: `src/shared/plugins/types.ts`
27
- - `panel`, `left-tab`, `command`, `catalog`, `binding`, `provider`,
28
- `surface-resolver`, and `agent-tool`.
26
+ - Plugin contributions: `src/shared/plugins/frontFactory.ts`
27
+ - Front plugins are authored with `definePlugin({ panels, leftTabs, commands,
28
+ catalogs, bindings, providers, surfaceResolvers })`. Agent tools belong to
29
+ Pi/server runtime paths, not front plugin contributions.
29
30
  - Surface opening: `src/shared/types/surface.ts`
30
31
  - `SurfaceOpenRequest { kind, target, meta }` is resolved by plugin
31
- `surface-resolver` outputs into panel openings.
32
+ surface resolvers into panel openings.
32
33
  - UI bridge: `src/shared/ui-bridge.ts`
33
34
  - Agents and servers post `UiCommand` values. The front-end dispatches them
34
35
  against the workspace runtime.
@@ -42,8 +43,7 @@ commands, and default workspace plugins.
42
43
  - Server plugins: `src/server/plugins`
43
44
  - `defineServerPlugin()` validates tools, routes, provisioning, and native Pi
44
45
  package declarations.
45
- - `composeServerPlugins()` combines smaller server plugin fragments.
46
- - `piPackages` are passed to `@boring/agent` as in-memory Pi settings, so
46
+ - `piPackages` are passed to `@hachej/boring-agent` as in-memory Pi settings, so
47
47
  workspace adapters can depend on native Pi packages without requiring
48
48
  Boring-specific exports from those packages.
49
49
 
@@ -54,5 +54,5 @@ commands, and default workspace plugins.
54
54
  compatibility layer.
55
55
  - Use `openSurface` for domain targets that need resolver selection.
56
56
  - Use `openPanel` only when the caller intentionally names the concrete panel.
57
- - Front/shared workspace code does not value-import `@boring/agent`; app/server
57
+ - Front/shared workspace code does not value-import `@hachej/boring-agent`; app/server
58
58
  composition may import documented agent server APIs.
@@ -1,162 +1,56 @@
1
- # Workspace Plugin Structure
1
+ # Plugin Structure
2
2
 
3
- Last updated: 2026-05-01
3
+ Canonical quick reference for boring-ui plugin layouts. For the full current
4
+ contract, see [`PLUGIN_SYSTEM.md`](./PLUGIN_SYSTEM.md). For future hosted/runtime
5
+ architecture, see the repo-level
6
+ [`docs/runtime-plugin-v2-hot-reload-plan.md`](../../../docs/runtime-plugin-v2-hot-reload-plan.md).
4
7
 
5
- Workspace plugins use a small, predictable folder shape. The goal is
6
- ownership clarity, not ceremony: plugin domain behavior stays in the plugin,
7
- and workspace core stays a generic host.
8
+ ## Generated/runtime plugin
8
9
 
9
- See `INTERFACES.md` for the package-level contracts these plugins contribute
10
- to.
10
+ Use the workspace-local CLI from inside the agent/runtime workspace:
11
11
 
12
- ## Standard Layout
13
-
14
- Start from `packages/workspace/templates/plugin/` when creating a new plugin,
15
- then delete the files the plugin does not need.
16
-
17
- ```txt
18
- <plugin>/
19
- front/
20
- index.tsx # front plugin factory and public front exports
21
- panels.tsx # panel definitions
22
- catalogs.ts # catalog outputs/config helpers
23
- surfaceResolver.ts # openSurface target -> panel resolution
24
- bindings.tsx # React side-effect bindings, if needed
25
- data/ # plugin-owned client data API/hooks/cache
26
- server/
27
- index.ts # server plugin factory and public server exports
28
- tools.ts # agent tools, if needed
29
- routes.ts # server routes, if needed
30
- shared/
31
- constants.ts # plugin id, catalog ids, surface kinds
32
- types.ts # platform-neutral shared types, if needed
33
- ```
34
-
35
- Use only the files a plugin actually needs. Large component families may live
36
- in subfolders such as `file-tree/`, `code-editor/`, or `empty-file-panel/`.
37
-
38
- ## Ownership Rules
39
-
40
- - `front/index.tsx` composes front outputs; large behavior belongs in focused files.
41
- - `server/index.ts` composes server outputs with `defineServerPlugin()`.
42
- - Shared files must stay platform-neutral. Do not import plugin `front/` or
43
- `server/` code from `shared/`.
44
- - Domain search belongs in `front/catalogs.ts`.
45
- - Domain open behavior belongs in `front/surfaceResolver.ts`.
46
- - Domain event names live in `shared/events.ts` when both layers need the
47
- contract; event names must be keyed by plugin id.
48
- - Plugin data clients/hooks live in `front/data/`, not package `front/data`.
49
- - Server prompts/tools/routes/provisioning live under `server/`, not mixed
50
- with client code.
51
- - Workspace core may host registries, providers, event transport, and bridge
52
- dispatch only. It must not hardcode plugin panel ids or plugin domain rules.
53
- - Catalog selection and plugin-owned routing should prefer `openSurface`.
54
- `openPanel` remains available for explicit app-level panel opens.
55
- - Executable agent tools should be server plugin contributions. The legacy
56
- front `agentTools` field remains for migration only.
57
-
58
- ## Composed Plugins
59
-
60
- Use `composePlugins()` when a front plugin is easier to build from smaller
61
- front fragments. The composed plugin flattens child panels, commands,
62
- catalogs, bindings, and outputs into one normal `WorkspaceFrontPlugin`.
63
-
64
- Default behavior adopts child ownership to the parent plugin id. Use
65
- `adoptOutputs: false` only when registry ownership must stay attached to the
66
- child fragment for diagnostics or selective unregister behavior.
67
-
68
- ```ts
69
- const macroPlugin = composePlugins({
70
- id: "boring-macro",
71
- label: "Macro",
72
- plugins: [macroPanelsPlugin, macroSurfacesPlugin, macroSeriesExplorerPlugin],
73
- })
12
+ ```bash
13
+ boring-ui scaffold-plugin <name>
14
+ boring-ui verify-plugin <name>
74
15
  ```
75
16
 
76
- Use `composeServerPlugins()` for the matching server side. It concatenates
77
- child tools, prompt text, pi package declarations, provisioning, and routes
78
- into one normal `WorkspaceServerPlugin`.
17
+ Default shape:
79
18
 
80
- ```ts
81
- const macroServerPlugin = composeServerPlugins({
82
- id: "boring-macro",
83
- label: "Macro",
84
- plugins: [macroToolsPlugin, macroRoutesPlugin],
85
- })
19
+ ```txt
20
+ .pi/extensions/<name>/
21
+ package.json # boring.front and/or pi.*; no boring.server by default
22
+ front/index.tsx # default-export definePlugin({ ... })
23
+ README.md
86
24
  ```
87
25
 
88
- ## Pi Package Adapters
26
+ Generated plugins are hot-reloaded with `/reload` for front/Pi resources. They
27
+ should stay route-free: no `server/index.ts`, no Fastify routes, and no dynamic
28
+ backend registration.
89
29
 
90
- Treat pi packages as implementation dependencies. The app-facing contract is a
91
- workspace adapter plugin that wraps the pi package and optionally adds front
92
- integration.
30
+ ## App/internal publishable package plugin
93
31
 
94
- Do not require pi packages to export Boring-specific adapters. The pi ecosystem
95
- already has its own shape, such as `package.json` `pi.extensions` entries and
96
- extension functions that call `pi.registerCommand(...)`. Workspace adapters
97
- should adapt to that shape.
98
-
99
- Declare native pi package dependencies on the server plugin:
100
-
101
- ```ts
102
- defineServerPlugin({
103
- id: "markdown-preview",
104
- piPackages: ["npm:pi-markdown-preview@0.9.7"],
105
- })
106
- ```
107
-
108
- Workspace passes these declarations to `@boring/agent` as in-memory Pi
109
- settings. This enables Pi's native package loader without mutating
110
- `.pi/settings.json`.
32
+ Use [`packages/cli/templates/plugin`](../../../packages/cli/templates/plugin/) as the reference
33
+ shape when building a trusted package composed by an app shell:
111
34
 
112
35
  ```txt
113
- src/plugins/markdownPreview/
114
- shared/
115
- constants.ts # workspace ids, surface kinds, command names
116
- server/
117
- index.ts # defineServerPlugin(), pi package dependency wrapper
118
- routes.ts # optional workspace-native render/preview routes
119
- front/
120
- index.tsx # defineFrontPlugin()
121
- panels.tsx # workspace-native preview panel
122
- surfaceResolver.ts # markdown-preview.open -> panel resolution
36
+ plugins/<name>/
37
+ package.json # boring.front + optional boring.server
38
+ src/front/index.ts # definePlugin({ ... })
39
+ src/server/index.ts # defineServerPlugin({ ... }) when needed
40
+ src/shared/* # browser-safe shared constants/types
41
+ tsup.config.ts
42
+ vitest.config.ts
123
43
  ```
124
44
 
125
- For example, a wrapper around `pi-markdown-preview` can depend on that package,
126
- read or invoke its pi extension behavior on the server side, and expose a
127
- workspace-native `markdown-preview.open` surface on the front side. The agent
128
- prompt should teach the model to use workspace `openSurface` when a Boring app
129
- is present, even if the underlying pi package also provides terminal/browser
130
- slash commands.
131
-
132
-
133
- ## Current Plugins
134
-
135
- - `packages/workspace/src/plugins/filesystemPlugin`
136
- - Data catalog package: `@hachej/boring-data-catalog` (`plugins/data-catalog/src`)
137
- - `apps/workspace-playground/src/plugins/playgroundDataCatalog`
138
- - Macro plugin example: `hachej/boring-macro` (`src/plugins/macro`)
139
-
140
- ## Invariants
141
-
142
- Run:
143
-
144
- ```sh
145
- pnpm --filter @boring/workspace run lint:plugin-invariants
146
- ```
45
+ App/internal plugins may expose boot-time server contributions such as routes,
46
+ agent tools, system prompts, provisioning, and Pi resources. Server changes
47
+ require restarting the workspace process.
147
48
 
148
- The scan rejects:
49
+ ## Import boundaries
149
50
 
150
- - `filePatterns`, `fileFallback`, `PanelRegistry.resolve`, and file-handler
151
- routing metadata in source.
152
- - `front/data` imports or path references in source.
153
- - `@boring/agent` imports from `workspace/src/shared/plugins`.
154
- - `front`/`server` imports from production `workspace/src/shared/plugins`.
155
- - Production plugin `front/`, `server/`, and `shared/` layers importing across
156
- the wrong layer boundary.
157
- - TypeScript source files directly under a plugin root instead of
158
- `front/`, `server/`, or `shared/`.
159
- - Plugin-domain imports from production `workspace/src/front/chrome`, `events`,
160
- and `hooks`.
161
- - Legacy plugin file names such as `catalog.ts`, `surfaceTargets.ts`,
162
- root-level `client.ts`, or root-level `server.ts`.
51
+ - Front plugin code imports from `@hachej/boring-workspace/plugin`.
52
+ - Trusted server plugin code imports from `@hachej/boring-workspace/server`.
53
+ - App shells use `@hachej/boring-workspace/app/front` and
54
+ `@hachej/boring-workspace/app/server`.
55
+ - Runtime/generated plugins should avoid broad host/workspace internals and use
56
+ documented primitives only.
@@ -0,0 +1,355 @@
1
+ # Plugin / Agent Layer
2
+
3
+ Normative spec for `@hachej/boring-workspace`'s current plugin + agent
4
+ layer. Code comments and tests cite section numbers in this file (for
5
+ example `PLUGIN_SYSTEM.md §4.5`), so keep headings stable when editing.
6
+
7
+ This document describes the implementation as it exists now. Historical
8
+ implementation plans live under `packages/workspace/docs/plans/archive/`.
9
+ For future generated/hosted runtime-plugin architecture, see the repo-level
10
+ `docs/runtime-plugin-v2-hot-reload-plan.md`.
11
+
12
+ ## Contents
13
+
14
+ 1. [Glossary](#1-glossary)
15
+ 2. [End-to-end behaviour](#2-end-to-end-behaviour)
16
+ 3. [Architecture](#3-architecture)
17
+ 4. [Public API](#4-public-api)
18
+ 5. [Key algorithms](#5-key-algorithms)
19
+ 6. [Gotchas](#6-gotchas)
20
+ 7. [Non-goals](#7-non-goals)
21
+ 8. [Risk register](#8-risk-register)
22
+
23
+ ---
24
+
25
+ ## 1. Glossary
26
+
27
+ | Term | Definition |
28
+ | --- | --- |
29
+ | **App/internal plugin** | Trusted package composed by the app at boot. May export `boring.server`, Fastify routes, agent tools, providers, catalogs, and domain APIs. Server changes require restart/redeploy. |
30
+ | **Runtime/generated plugin** | Workspace-local plugin under `.pi/extensions/<id>/`, usually produced by `boring-ui scaffold-plugin`. It is hot-loaded for front/Pi resources, but must not rely on dynamic backend routes. |
31
+ | **Boring plugin package** | Node package with `package.json#boring` and/or `package.json#pi`. App-default packages are declared in `package.json#boring.defaultPluginPackages` or passed to `createWorkspaceAgentServer`. |
32
+ | **Boring front factory** | Default export of `boring.front`: `(api: BoringFrontAPI) => void | Promise<void>`. Usually created with `definePlugin({ ... })`. |
33
+ | **Workspace server plugin** | Trusted boot-time server contribution returned by `defineServerPlugin({ ... })` or a compatible object. May include routes, tools, system prompt, Pi resources, and provisioning. |
34
+ | **Asset manager** | `BoringPluginAssetManager`. Scans plugin dirs, computes signatures, tracks revisions, emits `boring.plugin.{load,unload,error}` events, and backs `/api/v1/agent-plugins`. |
35
+ | **Revision** | Per-plugin monotonic integer. It bumps on signature changes and is appended to browser front imports for cache busting. |
36
+ | **Surface resolver** | Front contribution that maps a typed request such as `open-path` to a panel id/title/params. File opens should route through this path. |
37
+
38
+ ---
39
+
40
+ ## 2. End-to-end behaviour
41
+
42
+ ### App/internal package plugin
43
+
44
+ 1. App installs or references a plugin package.
45
+ 2. App declares it through `defaultPluginPackages`, `appPackageJsonPath`, or
46
+ explicit `plugins` passed to `createWorkspaceAgentServer`.
47
+ 3. Workspace resolves the package at boot.
48
+ 4. `boring.front` is exposed through the asset manager and front hot-load
49
+ path in dev.
50
+ 5. `boring.server` is imported and boot-composed once; routes and agent
51
+ tools are registered with the Fastify/agent process.
52
+ 6. Server changes require process restart. `/reload` may diagnose the drift,
53
+ but it does not rewire routes/tools in-place.
54
+
55
+ ### Runtime/generated plugin
56
+
57
+ 1. Agent/user runs the workspace-local CLI:
58
+
59
+ ```bash
60
+ boring-ui scaffold-plugin <name>
61
+ boring-ui verify-plugin <name>
62
+ ```
63
+
64
+ 2. The plugin lives under `.pi/extensions/<name>/`.
65
+ 3. `/reload` scans plugin manifests, refreshes Pi resources, and emits SSE
66
+ load/unload/error events.
67
+ 4. Browser dynamic-imports `boring.front` with revision + salt query params
68
+ and atomically replaces that plugin's registry entries.
69
+ 5. Previous working UI remains live when a front import/register fails.
70
+ 6. Generated plugins should omit `boring.server`; backend-like work should use
71
+ Pi extensions/tools today and future brokered sandbox tools/RPC later.
72
+
73
+ ---
74
+
75
+ ## 3. Architecture
76
+
77
+ ```
78
+ App package.json / createWorkspaceAgentServer options
79
+
80
+
81
+ resolve default plugin package dirs + explicit plugin entries
82
+
83
+ ├─ bootstrapServer(...) trusted boot-time server plugins
84
+ │ ├─ routes Fastify app.register at boot
85
+ │ ├─ agentTools passed to createAgentApp at boot
86
+ │ ├─ systemPromptAppend static prompt addendum
87
+ │ ├─ pi packages/skills/exts static Pi resources
88
+ │ └─ provisioning runtime workspace materialization
89
+
90
+ ├─ BoringPluginAssetManager manifest scan + signatures + SSE
91
+ │ ├─ /api/v1/agent-plugins
92
+ │ ├─ /api/v1/agent-plugins/events
93
+ │ └─ /api/v1/agent-plugins/:id/error
94
+
95
+ └─ createAgentApp(...)
96
+ ├─ beforeReload: asset scan + diagnostics + caller hook
97
+ ├─ systemPromptDynamic: plugin `pi.systemPrompt`
98
+ └─ pi.getHotReloadableResources: plugin skills/extensions/packages
99
+
100
+ Browser WorkspaceAgentFront
101
+
102
+ └─ useAgentPluginHotReload
103
+ ├─ EventSource to /api/v1/agent-plugins/events
104
+ ├─ dynamic import(frontUrl?v=<revision>&t=<salt>)
105
+ └─ registry.replaceByPluginId(...) per output kind
106
+ ```
107
+
108
+ Subpath intent:
109
+
110
+ | Subpath | Audience | Contents |
111
+ | --- | --- | --- |
112
+ | `/plugin` | Front plugin authors | `definePlugin`, front API/types, manifest validators, panel/surface contracts. |
113
+ | `/server` | Trusted server plugin authors/hosts | `defineServerPlugin`, server plugin types, asset manager helpers. |
114
+ | `/app/server` | App shells | `createWorkspaceAgentServer` and orchestration options. |
115
+ | `/app/front` | App shells | `WorkspaceAgentFront`. |
116
+ | `/shared` | Runtime-agnostic contracts | Shared types only. |
117
+
118
+ ---
119
+
120
+ ## 4. Public API
121
+
122
+ ### 4.1 Plugin `package.json`
123
+
124
+ ```jsonc
125
+ {
126
+ "name": "my-plugin",
127
+ "version": "0.1.0",
128
+ "boring": {
129
+ "label": "My Plugin",
130
+ "front": "front/index.tsx",
131
+ "server": "server/index.ts" // app/internal only; omit for generated plugins
132
+ },
133
+ "pi": {
134
+ "systemPrompt": "Short guidance for the agent.",
135
+ "extensions": ["agent/index.ts"],
136
+ "skills": ["skills/my-skill"],
137
+ "packages": ["."]
138
+ }
139
+ }
140
+ ```
141
+
142
+ Rules:
143
+
144
+ - `boring.front`, `boring.server`, `pi.extensions`, and `pi.skills` are safe
145
+ relative paths contained by the package root.
146
+ - `boring.server: true` is invalid. Use a string path or omit it.
147
+ - Plugin id is derived from `package.json#name`; `boring.id` is rejected.
148
+ - Runtime/generated plugins should include `boring.front` and/or `pi.*` and
149
+ omit `boring.server`.
150
+
151
+ ### 4.2 App `package.json`
152
+
153
+ ```jsonc
154
+ {
155
+ "boring": {
156
+ "defaultPluginPackages": [
157
+ "@hachej/boring-ask-user",
158
+ "./src/plugins/playgroundDataCatalog"
159
+ ]
160
+ }
161
+ }
162
+ ```
163
+
164
+ Relative entries resolve against the app package.json when
165
+ `appPackageJsonPath` is supplied. Explicit `defaultPluginPackages` passed to
166
+ `createWorkspaceAgentServer` are merged with manifest entries.
167
+
168
+ ### 4.3 Front authoring (`@hachej/boring-workspace/plugin`)
169
+
170
+ Use the declarative object form:
171
+
172
+ ```ts
173
+ import { definePlugin } from "@hachej/boring-workspace/plugin"
174
+
175
+ export default definePlugin({
176
+ id: "my-plugin",
177
+ label: "My Plugin",
178
+ panels: [
179
+ {
180
+ id: "my-plugin.panel",
181
+ label: "My Panel",
182
+ placement: "center",
183
+ component: MyPanel,
184
+ },
185
+ ],
186
+ commands: [
187
+ { id: "my-plugin.open", title: "Open My Plugin", panelId: "my-plugin.panel" },
188
+ ],
189
+ surfaceResolvers: [myResolver],
190
+ })
191
+ ```
192
+
193
+ The legacy positional form `definePlugin(id, factory, options?)` is not
194
+ supported. For advanced composition, use `setup(api) { ... }` inside the
195
+ config object.
196
+
197
+ ### 4.4 Server authoring (`@hachej/boring-workspace/server`)
198
+
199
+ Trusted app/internal packages may export a server plugin:
200
+
201
+ ```ts
202
+ import { defineServerPlugin } from "@hachej/boring-workspace/server"
203
+
204
+ export default defineServerPlugin({
205
+ id: "my-plugin",
206
+ systemPrompt: "Use My Plugin when ...",
207
+ agentTools: [tool],
208
+ routes: async (app) => {
209
+ app.get("/api/my-plugin/health", async () => ({ ok: true }))
210
+ },
211
+ })
212
+ ```
213
+
214
+ This is boot-time composition. Routes and tools are not hot-wired into a
215
+ running Fastify/agent process by `/reload`.
216
+
217
+ ### 4.5 Hot-reload coverage and partial-failure tolerance
218
+
219
+ | Surface | Runtime `.pi/extensions` | App/internal package plugin | Notes |
220
+ | --- | --- | --- | --- |
221
+ | `pi.systemPrompt` | `/reload`, next turn | `/reload` in dev when discovered; static in production if hot reload disabled | Appended by `systemPromptDynamic`. |
222
+ | `pi.extensions` | `/reload` through Pi session reload | `/reload` when discovered as package resources | File paths are re-read from manifest. |
223
+ | `pi.skills` / `pi.packages` | `/reload` through dynamic resource getter | `/reload` in dev; boot snapshot when `pluginHotReload=false` | `verify-plugin` checks declared local skill paths. |
224
+ | `boring.front` panels/commands/catalogs/surface resolvers | `/reload` + SSE + browser dynamic import | `/reload` in dev when front URL is served by the app/Vite | Previous version stays live on import/register failure. |
225
+ | `boring.server` routes/agentTools | Not supported for generated plugins | Boot-time only | `/reload` can warn `requiresRestart`. Restart process to apply. |
226
+ | Providers/bindings from hot-loaded front factories | Skipped | Static composition only | Dynamic provider tree mounting is intentionally not implemented yet. |
227
+
228
+ Partial-failure rule: a failed plugin scan/import/register must not abort the
229
+ whole reload. Healthy plugins still update; failing plugins emit diagnostics,
230
+ write/read `.error` state, and keep their previous live UI where possible.
231
+
232
+ ### 4.6 CLI authoring path
233
+
234
+ Generated plugin authoring uses the provisioned workspace-local CLI:
235
+
236
+ ```bash
237
+ boring-ui scaffold-plugin <name>
238
+ boring-ui verify-plugin <name>
239
+ ```
240
+
241
+ Do not teach agents to copy `packages/cli/templates/plugin` for generated runtime
242
+ plugins. That template is an app/internal publishable package example.
243
+
244
+ ### 4.7 `pluginHotReload`
245
+
246
+ `createWorkspaceAgentServer({ pluginHotReload })` controls runtime plugin
247
+ reload endpoints and dynamic Pi/package refresh.
248
+
249
+ - `true` (default): registers `/api/boring.reload`, keeps
250
+ `/api/v1/agent-plugins/events` active, and refreshes discovered plugin Pi
251
+ resources on `/api/v1/agent/reload`.
252
+ - `false`: static discovery/listing remains available, but package resources
253
+ are snapped once at boot and the developer reload endpoint is disabled.
254
+
255
+ ---
256
+
257
+ ## 5. Key algorithms
258
+
259
+ ### 5.1 Manifest scan
260
+
261
+ `scanBoringPlugins(pluginDirs)` discovers plugin package roots, validates
262
+ manifest shape, resolves contained entry paths, detects duplicate ids, and
263
+ returns `BoringServerPluginManifest[]` plus preflight diagnostics.
264
+
265
+ ### 5.2 Signatures and revisions
266
+
267
+ `BoringPluginAssetManager` hashes manifest fields, front/server file
268
+ signatures, relevant front/server/shared directories, and Pi resource paths.
269
+ When a signature changes, the plugin revision increments and a load/unload/error
270
+ event is emitted.
271
+
272
+ ### 5.3 Front import cache busting
273
+
274
+ Browser imports append both revision and salt:
275
+
276
+ ```ts
277
+ import(`${frontUrl}?v=${revision}&t=${Date.now()}`)
278
+ ```
279
+
280
+ The salt avoids stale Vite/browser module graph reuse across repeated reloads
281
+ or dev-server restarts.
282
+
283
+ ### 5.4 Atomic registry replacement
284
+
285
+ Front factories are first captured into an in-memory API. The captured panels,
286
+ commands, catalogs, and surface resolvers are then installed with
287
+ `replaceByPluginId` per registry. DockView and registry subscribers should not
288
+ see an intermediate empty state.
289
+
290
+ ### 5.5 Server drift warnings
291
+
292
+ When a plugin had a server entry in a previous revision and that server file
293
+ changes, the load event carries `requiresRestart: ["routes", "agentTools"]`.
294
+ The chat reload response also includes restart warnings so users know `/reload`
295
+ updated front/Pi resources but not boot-wired server code.
296
+
297
+ ### 5.6 Path containment
298
+
299
+ Manifest paths are lexically safe relative paths and are checked with realpath
300
+ containment so symlink escapes are rejected. Runtime workspace provisioning
301
+ template targets are also constrained to remain inside the workspace root.
302
+
303
+ ### 5.7 Output ownership and collision detection
304
+
305
+ Plugin-owned outputs carry `pluginId`. Hot reload uses that ownership to replace
306
+ only the current plugin's outputs. Cross-plugin id collisions are rejected with
307
+ `PLUGIN_OUTPUT_ID_COLLISION`; intra-plugin duplicate output ids are rejected
308
+ while capturing the front factory.
309
+
310
+ ---
311
+
312
+ ## 6. Gotchas
313
+
314
+ 1. `/reload` is the runtime plugin refresh boundary. Vite HMR should not
315
+ directly hot-update `.pi/extensions` modules into the host tree.
316
+ 2. Native `EventSource` cannot send Authorization headers. When bearer auth is
317
+ required and no token-query fallback exists, front plugin hot reload is
318
+ disabled rather than silently unauthenticated.
319
+ 3. Dynamic providers/bindings are not mounted for hot-loaded runtime plugins;
320
+ use static app composition for provider trees.
321
+ 4. `boring.server` in a runtime plugin may verify as a valid file path but it is
322
+ not dynamically registered by `/reload`.
323
+ 5. The asset manager is scan/hash/emit. Server route/tool import and
324
+ composition happen through app/server orchestration.
325
+ 6. Keep generated plugins route-free; use Pi extensions/tools and workspace file
326
+ APIs instead.
327
+ 7. Use surface resolvers for file visualizers. Do not hard-code extension logic
328
+ in the file tree.
329
+
330
+ ---
331
+
332
+ ## 7. Non-goals
333
+
334
+ - Hot-registering Fastify routes or static `agentTools` from generated plugins
335
+ into a live server process.
336
+ - Loading untrusted hosted plugin JavaScript directly into the host React tree.
337
+ - Replacing app/internal domain APIs (for example Macro routes) with runtime
338
+ plugin RPC for purity.
339
+ - Dynamic provider/binding trees for runtime hot-loaded plugins in this PR.
340
+ - Marketplace signing/provenance/permissions. Those belong to the next runtime
341
+ plugin architecture phase.
342
+
343
+ ---
344
+
345
+ ## 8. Risk register
346
+
347
+ | Risk | Current mitigation |
348
+ | --- | --- |
349
+ | Broken generated front import | Browser import/register error is surfaced; previous version remains live. |
350
+ | Stale server code after `/reload` | Restart warnings for server-path drift; docs state boot-time-only semantics. |
351
+ | Bad plugin blocks all reloads | Partial-failure tolerance; diagnostics per plugin. |
352
+ | Path escape via manifest entries | Safe relative path validation + realpath containment. |
353
+ | Path escape via runtime provisioning templates | Template targets are constrained to workspace-root descendants. |
354
+ | Duplicate output ids | Cross-plugin and intra-plugin collision checks. |
355
+ | Agent writes plugin into wrong cwd | Workspace-local `boring-ui` shim exports `BORING_AGENT_WORKSPACE_ROOT`; verifier prints scanned path. |
package/docs/README.md CHANGED
@@ -5,8 +5,13 @@ Older implementation plans live in `docs/plans/archive/`.
5
5
 
6
6
  ## Current References
7
7
 
8
+ - `PLUGIN_SYSTEM.md` - current normative spec for the plugin / agent layer:
9
+ package manifest fields, public API for front + server authoring,
10
+ hot-reload coverage table, prompt-location guidance, and key algorithms.
11
+ Code cites it as `Per PLUGIN_SYSTEM.md §X`.
12
+ - `PLUGIN_STRUCTURE.md` - quick layout guide for generated/runtime plugins vs
13
+ app/internal publishable package plugins.
8
14
  - `INTERFACES.md` - package boundaries and public abstractions.
9
- - `PLUGIN_STRUCTURE.md` - plugin folder shape and invariant scans.
10
15
  - `plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md` - latest plugin ownership plan.
11
16
  - `plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md` - UI bridge ownership decision.
12
17
  - `plans/archive/` - superseded implementation plans.
@@ -4,6 +4,7 @@ Active ownership records live in this folder.
4
4
 
5
5
  - `ASK_USER_QUESTIONS_PLUGIN_SPEC.md` - blocking ask-user tool + Questions workspace plugin spec.
6
6
  - `PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md` - pane/plugin to active agent chat action bridge spec.
7
+ - `HOT_RELOADABLE_AGENT_PLUGINS_PLAN.md` - current package-json plugin, reload, Pi, and front-factory architecture.
7
8
  - `GENERIC_EXPLORER_PLUGIN_PLAN.md` - generic explorer plugin shape and front/plugin ownership audit.
8
9
  - `MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md` - macro plugin audit for reusable explorer/surface/event helper extraction.
9
10
  - `PLUGIN_OUTPUTS_ISOLATION_PLAN.md` - plugin ownership and isolation plan.