@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.
- package/README.md +36 -34
- package/dist/{FileTree-Dvaud3jU.js → FileTree-DHVB9rpk.js} +15 -15
- package/dist/{MarkdownEditor-sLkqTXDj.js → MarkdownEditor-L1KDH0bM.js} +1 -1
- package/dist/{WorkspaceLoadingState-zLzh1tGc.js → WorkspaceLoadingState-DYDxUYnx.js} +114 -110
- package/dist/WorkspaceProvider-CDPaAO5u.js +5971 -0
- package/dist/app-front.d.ts +94 -107
- package/dist/app-front.js +243 -233
- package/dist/app-server.d.ts +130 -15
- package/dist/app-server.js +1569 -304
- package/dist/{bootstrapServer-BreQ9QBc.d.ts → createInMemoryBridge-BDxDzihm.d.ts} +11 -26
- package/dist/manifest-CyNNdfYz.d.ts +58 -0
- package/dist/plugin.d.ts +199 -0
- package/dist/plugin.js +300 -0
- package/dist/server.d.ts +239 -4
- package/dist/server.js +901 -78
- package/dist/shared.d.ts +4 -112
- package/dist/surface-COYagY2m.d.ts +111 -0
- package/dist/testing.d.ts +19 -1
- package/dist/testing.js +2 -2
- package/dist/{agent-tool-DEtfQPVB.d.ts → ui-bridge-Gfh1MMgl.d.ts} +30 -30
- package/dist/workspace.css +36 -0
- package/dist/workspace.d.ts +165 -120
- package/dist/workspace.js +330 -377
- package/docs/INTERFACES.md +9 -9
- package/docs/PLUGIN_STRUCTURE.md +39 -145
- package/docs/PLUGIN_SYSTEM.md +355 -0
- package/docs/README.md +6 -1
- package/docs/plans/README.md +1 -0
- package/docs/plans/archive/HOT_RELOADABLE_AGENT_PLUGINS_PLAN.md +218 -0
- package/docs/plans/archive/RELOAD_PLUGGABILITY_PLAN.md +174 -0
- package/docs/plans/archive/UNIFIED_PLUGIN_SYSTEM_PLAN.md +769 -0
- package/package.json +11 -5
- package/dist/CommandPalette-CJHuTJlD.js +0 -5716
- package/docs/bridge.md +0 -135
- package/docs/panels.md +0 -102
- package/docs/plugins.md +0 -158
- /package/docs/plans/{MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md → archive/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md} +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Hot-Reloadable Boring Plugins Plan
|
|
2
|
+
|
|
3
|
+
Last updated: 2026-05-12
|
|
4
|
+
Status: current architecture snapshot for PR #18
|
|
5
|
+
|
|
6
|
+
This file is the single active plan for the hot-reloadable plugin/agent layer.
|
|
7
|
+
Older split plans for agent docs, plugin-agent layering, and derivation path A
|
|
8
|
+
were consolidated here so implementation guidance matches the code now in the
|
|
9
|
+
branch.
|
|
10
|
+
|
|
11
|
+
## Goals
|
|
12
|
+
|
|
13
|
+
- `/reload` reloads both Pi agent assets and Boring workspace plugin assets.
|
|
14
|
+
- Plugin metadata is package-shaped and easy to author.
|
|
15
|
+
- UI registration is runtime code, not JSON-driven wiring.
|
|
16
|
+
- Pi-specific runtime/reload behavior stays behind the Pi harness adapter.
|
|
17
|
+
- The agent harness remains pluggable for non-Pi runtimes.
|
|
18
|
+
- Bad plugin metadata is observable and recoverable: no process crash, stable
|
|
19
|
+
error events, and `.error` files.
|
|
20
|
+
|
|
21
|
+
## Package model
|
|
22
|
+
|
|
23
|
+
A Boring plugin is a package/directory with `package.json` metadata and optional
|
|
24
|
+
runtime entrypoints:
|
|
25
|
+
|
|
26
|
+
```txt
|
|
27
|
+
plugin/
|
|
28
|
+
package.json
|
|
29
|
+
front/index.tsx # browser-only BoringFrontFactory
|
|
30
|
+
agent/index.ts # optional Pi ExtensionFactory
|
|
31
|
+
agent/skills/ # optional Pi skills
|
|
32
|
+
server/index.ts # optional trusted workspace/server routes
|
|
33
|
+
shared/ # optional platform-neutral types/constants
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `package.json#boring`
|
|
37
|
+
|
|
38
|
+
`boring` is workspace/UI discovery metadata only:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"name": "example-plugin",
|
|
43
|
+
"boring": {
|
|
44
|
+
"label": "Example Plugin",
|
|
45
|
+
"front": "front/index.tsx",
|
|
46
|
+
"server": "server/index.ts",
|
|
47
|
+
"derivesFrom": "data-catalog"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Allowed runtime semantics:
|
|
53
|
+
|
|
54
|
+
- `id` / derived package name selects the stable plugin id.
|
|
55
|
+
- `label` is display metadata.
|
|
56
|
+
- `front` points at the front factory entrypoint.
|
|
57
|
+
- `server` points at trusted Node routes/helpers, or `false` to opt out.
|
|
58
|
+
- `derivesFrom` is discovery metadata for templates/catalogs.
|
|
59
|
+
|
|
60
|
+
Not allowed in `boring`: panels, commands, left tabs, surface resolvers, agent
|
|
61
|
+
tools, skills, extensions, packages, system prompts. Those old JSON registration
|
|
62
|
+
arrays were intentionally removed.
|
|
63
|
+
|
|
64
|
+
### `package.json#pi`
|
|
65
|
+
|
|
66
|
+
`pi` owns agent/Pi contributions:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"pi": {
|
|
71
|
+
"extensions": ["agent/index.ts"],
|
|
72
|
+
"skills": ["agent/skills"],
|
|
73
|
+
"packages": [{ "source": "file:.", "extensions": ["agent/index.ts"] }],
|
|
74
|
+
"systemPrompt": "Use this plugin's tools when working with example data."
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`extensions`, `skills`, `packages`, and `systemPrompt` are consumed by the Pi
|
|
80
|
+
adapter. Workspace code only discovers and forwards them; it does not implement
|
|
81
|
+
Pi loading directly.
|
|
82
|
+
|
|
83
|
+
## Runtime registration
|
|
84
|
+
|
|
85
|
+
### Front/UI
|
|
86
|
+
|
|
87
|
+
`BoringFrontFactory` is the single runtime UI registration source:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import type { BoringFrontFactory } from "@hachej/boring-workspace/plugin"
|
|
91
|
+
|
|
92
|
+
const plugin: BoringFrontFactory = (api) => {
|
|
93
|
+
api.registerPanel({ id: "example.panel", title: "Example", component: ExamplePane })
|
|
94
|
+
api.registerCommand({ id: "example.open", title: "Open Example", run: () => {} })
|
|
95
|
+
api.registerLeftTab({ id: "example.left", title: "Example", component: ExampleLeft })
|
|
96
|
+
api.registerSurfaceResolver({ id: "example.surface", resolve: () => ({ component: "example.panel" }) })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default plugin
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Front plugin code is browser code. It must not contribute executable agent tools.
|
|
103
|
+
Hot-loaded panels are ordinary React function components: hooks such as
|
|
104
|
+
`useState`, `useEffect`, and `useMemo` are supported. The host Vite dev server
|
|
105
|
+
must resolve `react`, `react-dom`, `react/jsx-runtime`, and
|
|
106
|
+
`react/jsx-dev-runtime` to the same singleton modules used by the workspace
|
|
107
|
+
shell; duplicate React copies are a host configuration bug, not a plugin author
|
|
108
|
+
contract. The legacy front-side `agentTools` / `agent-tool` output path is
|
|
109
|
+
removed.
|
|
110
|
+
|
|
111
|
+
### Server/workspace
|
|
112
|
+
|
|
113
|
+
`server/index.ts` is trusted host-process code for workspace routes or support
|
|
114
|
+
helpers. Hot-reloadable package plugins should put new tool capabilities in
|
|
115
|
+
`pi.extensions` via `agent/index.ts`. Programmatic host/server plugin APIs may
|
|
116
|
+
still adapt legacy `extraTools` during migration, but package metadata does not
|
|
117
|
+
carry front-side or JSON-declared tools.
|
|
118
|
+
|
|
119
|
+
### Agent/Pi
|
|
120
|
+
|
|
121
|
+
`agent/index.ts` exports native Pi extension factories. The Pi harness adapter
|
|
122
|
+
owns conversion into `DefaultResourceLoader`, dynamic package metadata refresh,
|
|
123
|
+
skill loading, and `piSession.reload()`.
|
|
124
|
+
|
|
125
|
+
## Reload flow
|
|
126
|
+
|
|
127
|
+
1. User sends `/reload` in chat.
|
|
128
|
+
2. Front slash-command calls `POST /api/v1/agent/reload`.
|
|
129
|
+
3. Generic agent route calls the workspace-provided `beforeReload` hook.
|
|
130
|
+
4. Workspace composition refreshes package.json `pi` inputs and reloads
|
|
131
|
+
`BoringPluginAssetManager`.
|
|
132
|
+
5. Manager emits `boring.plugin.load`, `boring.plugin.unload`, or
|
|
133
|
+
`boring.plugin.error` SSE events.
|
|
134
|
+
6. Generic agent route calls the configured harness `reloadSession` method.
|
|
135
|
+
7. Pi harness implementation reloads Pi extension/skill/package resources for
|
|
136
|
+
the session.
|
|
137
|
+
8. Front plugin runtime hot-swaps successful `front` modules and keeps the last
|
|
138
|
+
good UI alive on malformed/error events.
|
|
139
|
+
|
|
140
|
+
The generic agent package exposes only the harness seam; Pi-specific knobs live
|
|
141
|
+
under `pi` options.
|
|
142
|
+
|
|
143
|
+
## Validation and safety
|
|
144
|
+
|
|
145
|
+
Plugin discovery/preflight must:
|
|
146
|
+
|
|
147
|
+
- reject invalid ids and duplicate effective plugin ids;
|
|
148
|
+
- reject `.` paths, absolute paths, backslash paths, null-byte paths, and
|
|
149
|
+
traversal (`../`) paths;
|
|
150
|
+
- validate explicit `front`, `server`, `pi.extensions`, `pi.skills`, and nested
|
|
151
|
+
`pi.packages[*]` resource filters;
|
|
152
|
+
- perform realpath containment checks for existing paths;
|
|
153
|
+
- check the nearest existing ancestor for missing paths under symlinked parents;
|
|
154
|
+
- allow empty collection directories such as `.pi/extensions`;
|
|
155
|
+
- report explicit plugin dirs without `package.json` as `MISSING_PACKAGE_JSON`;
|
|
156
|
+
- surface invalid JSON/metadata through preflight errors rather than crashing
|
|
157
|
+
startup.
|
|
158
|
+
|
|
159
|
+
Error reporting:
|
|
160
|
+
|
|
161
|
+
- `BoringPluginAssetManager.load()` returns errors.
|
|
162
|
+
- SSE emits `boring.plugin.error`.
|
|
163
|
+
- `.error` files are written under the configured error root.
|
|
164
|
+
- If a plugin id cannot be safely derived, use a stable `preflight-<hash>` id.
|
|
165
|
+
|
|
166
|
+
## Agent docs for plugin creation
|
|
167
|
+
|
|
168
|
+
The agent should learn the plugin API from workspace-owned Markdown docs:
|
|
169
|
+
|
|
170
|
+
- `packages/workspace/src/server/docs/plugins.md`
|
|
171
|
+
- `packages/workspace/src/server/docs/panels.md`
|
|
172
|
+
- `packages/workspace/src/server/docs/bridge.md`
|
|
173
|
+
|
|
174
|
+
`buildBoringSystemPrompt()` embeds those docs into the agent prompt when the
|
|
175
|
+
workspace app has strong filesystem capability. This replaces the older separate
|
|
176
|
+
agent-doc-embedding plan.
|
|
177
|
+
|
|
178
|
+
## Current asset-serving caveat
|
|
179
|
+
|
|
180
|
+
Hot-loaded front entries currently use Vite-style `/@fs/<absolute-path>` module
|
|
181
|
+
URLs. `WorkspaceProvider` gates that path behind `frontPluginHotReload="vite"`,
|
|
182
|
+
which defaults on only in dev, because Vite transforms TypeScript/TSX and React
|
|
183
|
+
imports for the browser. Vite hosts that enable this mode must alias/dedupe the
|
|
184
|
+
React family imports to the host app singleton so hook-based plugin panels run
|
|
185
|
+
under the same dispatcher as the workspace shell.
|
|
186
|
+
|
|
187
|
+
A production Fastify-only host needs a workspace-owned authenticated module
|
|
188
|
+
asset endpoint/bundler before front plugin hot-loading can work without Vite.
|
|
189
|
+
Until that endpoint exists, document front plugin hot-reload as development /
|
|
190
|
+
workspace-dev-server scope.
|
|
191
|
+
|
|
192
|
+
## Public API boundaries
|
|
193
|
+
|
|
194
|
+
- `@hachej/boring-workspace/plugin` exports front authoring helpers,
|
|
195
|
+
`BoringFrontFactory`, and package metadata types.
|
|
196
|
+
- Public barrels export `BoringPluginPackageJson`, not the server runtime
|
|
197
|
+
manifest shape.
|
|
198
|
+
- Server-only runtime manifests stay under `server/agentPlugins`.
|
|
199
|
+
- Shared/browser code must not import `@hachej/boring-agent` values.
|
|
200
|
+
- `UiBridge.postCommand` remains the single command dispatch source.
|
|
201
|
+
|
|
202
|
+
## Done in PR #18
|
|
203
|
+
|
|
204
|
+
- Generic `/api/v1/agent/reload` route and `/reload` slash command.
|
|
205
|
+
- Pluggable `AgentHarnessFactory` and Pi-scoped adapter options.
|
|
206
|
+
- Workspace plugin scanner, asset manager, routes, and SSE front reload client.
|
|
207
|
+
- `package.json#pi` / `package.json#boring` split.
|
|
208
|
+
- `BoringFrontFactory` as the only runtime UI registration source.
|
|
209
|
+
- Removal of obsolete front-side `agentTools` registration path.
|
|
210
|
+
- Realpath-aware path validation and observable preflight errors.
|
|
211
|
+
- Consolidated plan docs into this current architecture plan.
|
|
212
|
+
|
|
213
|
+
## Not in scope for PR #18
|
|
214
|
+
|
|
215
|
+
- `apps/boring-macro-v2`.
|
|
216
|
+
- Password-reset smoke tests.
|
|
217
|
+
- Production Fastify asset bundling for front plugin modules.
|
|
218
|
+
- Cloud/multi-tenant plugin provisioning.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Reload pluggability — small, harness-agnostic seams
|
|
2
|
+
|
|
3
|
+
Status: proposal, builds on PR #18.
|
|
4
|
+
Scope: cut workspace ↔ Pi coupling in the reload path. Keep Pi as the default
|
|
5
|
+
harness; let a future custom harness slot in without forking the workspace.
|
|
6
|
+
|
|
7
|
+
## Two-track reload, no callback between tracks
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
POST /api/v1/agent/reload
|
|
11
|
+
├─ Track A — workspace owns it
|
|
12
|
+
│ BoringPluginAssetManager.load() → SSE → front hot-swap
|
|
13
|
+
│ (errors → 422 with details; preserves last-good UI)
|
|
14
|
+
└─ Track B — harness owns it (optional)
|
|
15
|
+
harness.reloadSession?(sessionId)
|
|
16
|
+
Pi today; custom harness later; missing method = skip.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The two tracks do not call into each other. The workspace stops registering Pi
|
|
20
|
+
extensions and stops mutating arrays that Pi later reads.
|
|
21
|
+
|
|
22
|
+
## Single new seam: dynamic prompt provider
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// packages/agent/src/shared/harness.ts
|
|
26
|
+
interface AgentHarnessFactoryInput {
|
|
27
|
+
// ...existing
|
|
28
|
+
/**
|
|
29
|
+
* Optional source of additional system prompt content. Harness reads it
|
|
30
|
+
* each time it builds/rebuilds a session prompt. Returning `undefined`
|
|
31
|
+
* means "nothing to add right now". Workspace plugin layer supplies it;
|
|
32
|
+
* harness decides when to call it.
|
|
33
|
+
*/
|
|
34
|
+
systemPromptDynamic?: () => string | undefined | Promise<string | undefined>
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Workspace passes `systemPromptDynamic: () => aggregatePluginPrompts(boringAssetManager)`.
|
|
39
|
+
- Pi adapter, internally, registers a `before_agent_start` extension that calls
|
|
40
|
+
the provider and appends to `event.systemPrompt`. Pi's native
|
|
41
|
+
`reloadSession` already re-fires `before_agent_start`, so /reload picks up
|
|
42
|
+
fresh plugin prompts with no extra lifecycle plumbing.
|
|
43
|
+
- Other harnesses call the provider during their own session-init flow, or
|
|
44
|
+
ignore it.
|
|
45
|
+
|
|
46
|
+
## Audit — other leaky abstractions in today's reload path
|
|
47
|
+
|
|
48
|
+
### 1. Workspace mutates Pi-owned arrays in place (high)
|
|
49
|
+
|
|
50
|
+
`createWorkspaceAgentServer.ts → syncPackageJsonPiOptions()` calls
|
|
51
|
+
`piAdditionalSkillPaths.splice(...)`, `piPackages.splice(...)`,
|
|
52
|
+
`piExtensionPaths.splice(...)` to mutate the *same* arrays that were passed
|
|
53
|
+
into `pi: { ... }` on `createAgentApp`. This relies on Pi re-reading those
|
|
54
|
+
arrays inside `reloadSession`. Two bad properties:
|
|
55
|
+
|
|
56
|
+
- It's invisible coupling — neither side declares the contract.
|
|
57
|
+
- A non-Pi harness has no reason to read those arrays again, so the mutation
|
|
58
|
+
is silently meaningless.
|
|
59
|
+
|
|
60
|
+
**Fix:** replace the snapshot arrays with a single getter:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// On PiHarnessOptions:
|
|
64
|
+
interface PiHarnessOptions {
|
|
65
|
+
// ...existing
|
|
66
|
+
getResources?: () => {
|
|
67
|
+
additionalSkillPaths?: string[]
|
|
68
|
+
packages?: WorkspacePiPackageSource[]
|
|
69
|
+
extensionPaths?: string[]
|
|
70
|
+
extensionFactories?: ExtensionFactory[]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Pi's resource-loader rebuild reads from `getResources()` each time. Workspace
|
|
76
|
+
provides one getter that merges static + package.json-discovered values. No
|
|
77
|
+
splices. No shared array references.
|
|
78
|
+
|
|
79
|
+
This is Pi-internal and doesn't change the workspace's public surface much,
|
|
80
|
+
but it removes the most surprising piece of coupling in the file.
|
|
81
|
+
|
|
82
|
+
### 2. Pi-shaped helpers live in the workspace package (medium)
|
|
83
|
+
|
|
84
|
+
`createBoringPiPackageSource(workspaceRoot)` builds `{ source, skills:
|
|
85
|
+
["skills/boring-plugin-authoring"] }` — a Pi `WorkspacePiPackageSource` shape.
|
|
86
|
+
It lives in `createWorkspaceAgentServer.ts` and gets prepended to `piPackages`.
|
|
87
|
+
|
|
88
|
+
A non-Pi harness has no use for this shape and shouldn't have to pretend it
|
|
89
|
+
does. **Fix:** move the helper into the Pi-default composition path:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// pseudo
|
|
93
|
+
const harnessFactory = opts.harnessFactory ?? composePiHarnessFactory({
|
|
94
|
+
workspaceRoot,
|
|
95
|
+
pi: opts.pi,
|
|
96
|
+
bundledSkillPackage: createBoringPiPackageSource(workspaceRoot),
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Workspace stops constructing Pi-shaped values when a non-default harness is
|
|
101
|
+
in use.
|
|
102
|
+
|
|
103
|
+
### 3. Pi's bundled skill package is provisioned unconditionally (medium)
|
|
104
|
+
|
|
105
|
+
`createBoringPiPackageProvisioningContribution()` materializes
|
|
106
|
+
`@hachej/boring-pi` into the child workspace's `node_modules` regardless of
|
|
107
|
+
which harness is active. It should only run when the Pi adapter is the
|
|
108
|
+
selected harness.
|
|
109
|
+
|
|
110
|
+
**Fix:** make provisioning contributions a *harness adapter* responsibility
|
|
111
|
+
too:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
interface AgentHarnessFactory {
|
|
115
|
+
contributeProvisioning?(workspaceRoot: string): WorkspaceProvisioningContribution[]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`composePiHarnessFactory` returns the Pi-bundled provisioning entry from
|
|
120
|
+
`contributeProvisioning`. Workspace collects them generically. Custom
|
|
121
|
+
harnesses contribute their own asset packages, or nothing.
|
|
122
|
+
|
|
123
|
+
### 4. `/reload` response shape is harness-shaped (low)
|
|
124
|
+
|
|
125
|
+
`reloadRoutes` returns `{ ok, sessionId, reloaded }`. `reloaded: boolean` is
|
|
126
|
+
fine for Pi but doesn't carry boring-plugin reload outcomes (loaded count,
|
|
127
|
+
errors-but-still-usable, last-good UI kept), so the front always renders the
|
|
128
|
+
same "Agent plugins reloaded." message regardless of what really happened.
|
|
129
|
+
|
|
130
|
+
**Fix:** widen the response without breaking the existing field:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
{
|
|
134
|
+
ok: true,
|
|
135
|
+
sessionId,
|
|
136
|
+
reloaded, // keep — harness reload result
|
|
137
|
+
boring?: { // new — workspace track
|
|
138
|
+
loaded: number,
|
|
139
|
+
errors: { id: string; message: string }[],
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Front `/reload` handler uses `boring.errors` to surface compile failures
|
|
145
|
+
without forcing a 422 (today's `throw` model conflates "reload broken"
|
|
146
|
+
with "some plugins broken").
|
|
147
|
+
|
|
148
|
+
### 5. Naming creep — `pi` in the workspace public API (defer)
|
|
149
|
+
|
|
150
|
+
`WorkspaceAgentServerOptions.pi`, `WorkspacePiPackageSource`,
|
|
151
|
+
`compactPiPackages` are exported by `@hachej/boring-workspace`. Hard to fix
|
|
152
|
+
without a breaking change for plugin authors. Leave for the day a second
|
|
153
|
+
harness lands; rename then under one umbrella (`harness?: { pi?: ..., ...}`)
|
|
154
|
+
with one release of aliases.
|
|
155
|
+
|
|
156
|
+
## Tradeoff acknowledged once
|
|
157
|
+
|
|
158
|
+
Track A and Track B don't share a callback. If a custom harness wants
|
|
159
|
+
plugin `systemPrompt`s to refresh inside a running session, it implements
|
|
160
|
+
the `systemPromptDynamic` getter call into its own session-init flow. Pi
|
|
161
|
+
already does this for free via `before_agent_start`. Other harnesses opt
|
|
162
|
+
in or accept static prompts.
|
|
163
|
+
|
|
164
|
+
## Execution order
|
|
165
|
+
|
|
166
|
+
| # | Change | Risk | Notes |
|
|
167
|
+
|---|--------|------|-------|
|
|
168
|
+
| 1 | Add `systemPromptDynamic` on `AgentHarnessFactoryInput`. Pi adapter consumes it via an internal `before_agent_start` extension. Delete `packages/workspace/src/server/agentPlugins/boringPiExtension.ts` and its test. Workspace passes the getter. | Low — same runtime behavior, code moves. | Step 1 keeps every existing test green. |
|
|
169
|
+
| 2 | Replace `syncPackageJsonPiOptions` array splices with a single `getResources()` getter on `PiHarnessOptions`. Pi reads it on every session rebuild. Workspace stops mutating shared arrays. | Low | Drops 30+ lines of mutation glue. |
|
|
170
|
+
| 3 | Move `createBoringPiPackageSource` and Pi-bundled provisioning into a `composePiHarnessFactory` helper. Workspace constructs them only on the Pi-default path. | Low | Custom harness path now allocates zero Pi-shaped data. |
|
|
171
|
+
| 4 | Widen `/api/v1/agent/reload` response with optional `boring` block. Update front `/reload` handler to surface compile errors without 422. | Low | Strictly additive on the wire. |
|
|
172
|
+
| 5 (later) | Rename workspace public types to drop `pi` prefix; ship `harness: { pi?: ... }` namespacing with deprecated aliases. | Medium | Wait until a second harness justifies the churn. |
|
|
173
|
+
|
|
174
|
+
Steps 1–4 are independent commits. Each can land on its own.
|