@hachej/boring-workspace 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/CodeEditor-DQqOn4xz.js +266 -0
- package/dist/CommandPalette-aM61U-b0.js +5229 -0
- package/dist/FileTree-DRq_bfue.js +245 -0
- package/dist/MarkdownEditor-DjiHxnRv.js +349 -0
- package/dist/WorkspaceLoadingState-By0dZoPD.js +568 -0
- package/dist/agent-tool-NvxKfist.d.ts +28 -0
- package/dist/app-front.d.ts +485 -0
- package/dist/app-front.js +452 -0
- package/dist/app-server.d.ts +53 -0
- package/dist/app-server.js +769 -0
- package/dist/bootstrapServer-BRUqUpVW.d.ts +66 -0
- package/dist/boring-workspace.css +1 -0
- package/dist/charts.d.ts +114 -0
- package/dist/charts.js +143 -0
- package/dist/events.d.ts +178 -0
- package/dist/events.js +88 -0
- package/dist/explorer-DtLUnuah.d.ts +129 -0
- package/dist/panel-DnvDNQac.js +6 -0
- package/dist/server.d.ts +84 -0
- package/dist/server.js +811 -0
- package/dist/shared.d.ts +113 -0
- package/dist/shared.js +11 -0
- package/dist/testing-e2e.d.ts +68 -0
- package/dist/testing-e2e.js +45 -0
- package/dist/testing.d.ts +464 -0
- package/dist/testing.js +10984 -0
- package/dist/utils-B6yFEsav.js +8 -0
- package/dist/workspace.css +5780 -0
- package/dist/workspace.d.ts +2119 -0
- package/dist/workspace.js +1884 -0
- package/docs/INTERFACES.md +58 -0
- package/docs/PLUGIN_STRUCTURE.md +162 -0
- package/docs/README.md +19 -0
- package/docs/bridge.md +135 -0
- package/docs/panels.md +102 -0
- package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +455 -0
- package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +962 -0
- package/docs/plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md +301 -0
- package/docs/plans/README.md +9 -0
- package/docs/plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md +303 -0
- package/docs/plans/archive/CODE_OWNERSHIP_CLEANUP_PLAN.md +387 -0
- package/docs/plans/archive/COMMAND_PALETTE_REGISTRY.md +814 -0
- package/docs/plans/archive/DECLARATIVE_LAYOUT_MIGRATION.md +277 -0
- package/docs/plans/archive/PLUGIN_MODEL.md +3674 -0
- package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +307 -0
- package/docs/plans/archive/UNIFIED_EVENT_BUS.md +647 -0
- package/docs/plans/archive/WORKSPACE_V2_PLAN.md +2489 -0
- package/docs/plugins.md +158 -0
- package/package.json +164 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Plugin outputs and workspace isolation plan
|
|
2
|
+
|
|
3
|
+
**Status:** implemented migration plan
|
|
4
|
+
**Owners:** workspace
|
|
5
|
+
**Last updated:** 2026-04-30
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Make plugins the owners of user-facing workspace capabilities, while
|
|
10
|
+
keeping `@boring/workspace` as the host substrate.
|
|
11
|
+
|
|
12
|
+
The immediate correction is that a left workbench tab should not be a
|
|
13
|
+
hardcoded workbench behavior or an implicit `PanelConfig.placement`.
|
|
14
|
+
It should be an explicit plugin output. The same rule applies to file
|
|
15
|
+
search, file hooks, file open behavior, and file routing: those belong to
|
|
16
|
+
the filesystem plugin, with the workspace only providing generic
|
|
17
|
+
registries, hosts, event transport, and UI manipulation contracts.
|
|
18
|
+
|
|
19
|
+
## Problems Addressed
|
|
20
|
+
|
|
21
|
+
1. **Left tabs are too implicit.** Plugins currently express a workbench
|
|
22
|
+
left tab as a normal panel with `placement: "left-tab"`. That makes
|
|
23
|
+
the host infer semantics from a layout hint instead of consuming an
|
|
24
|
+
intentional plugin output.
|
|
25
|
+
|
|
26
|
+
2. **The left pane knew filesystem details.**
|
|
27
|
+
`WorkbenchLeftPane` imported and rendered the filesystem file tree
|
|
28
|
+
directly. It also had built-in handling for the Data tab. That meant
|
|
29
|
+
`excludeDefaults` and plugin composition could not fully own the visible
|
|
30
|
+
left-tab set.
|
|
31
|
+
|
|
32
|
+
3. **Filesystem data lived in `front/`.** `front/data` contained
|
|
33
|
+
filesystem-specific client methods, hooks, event invalidation, and SSE
|
|
34
|
+
subscription logic. Those are not generic workspace frontend
|
|
35
|
+
primitives; they are filesystem plugin runtime.
|
|
36
|
+
|
|
37
|
+
4. **Event domains were not plugin-owned.** The event bus is generic
|
|
38
|
+
workspace substrate, but event names such as `file:changed`
|
|
39
|
+
make filesystem behavior look like workspace core behavior. Plugin
|
|
40
|
+
events must be keyed by their owning plugin id, for example
|
|
41
|
+
`filesystem:file.changed`.
|
|
42
|
+
|
|
43
|
+
5. **Artifact routing hardcoded file plugin behavior.** The artifact
|
|
44
|
+
surface knew about `code-editor`, `markdown-editor`,
|
|
45
|
+
`csv-viewer`, and `empty-file-panel` fallback behavior. File type
|
|
46
|
+
routing should be contributed by whichever plugin handles files.
|
|
47
|
+
|
|
48
|
+
6. **Workspace imported agent types.** The workspace shared/plugin
|
|
49
|
+
layer imports `@boring/agent` types and validation. That violates the
|
|
50
|
+
package invariant that the app shell wires agent and workspace
|
|
51
|
+
together.
|
|
52
|
+
|
|
53
|
+
## Target contract
|
|
54
|
+
|
|
55
|
+
Introduce a first-class plugin output model. The exact names can be
|
|
56
|
+
adjusted during implementation, but the shape should be discriminated and
|
|
57
|
+
explicit:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
type PluginOutput =
|
|
61
|
+
| LeftTabOutput
|
|
62
|
+
| CenterPanelOutput
|
|
63
|
+
| CatalogOutput
|
|
64
|
+
| CommandOutput
|
|
65
|
+
| BindingOutput
|
|
66
|
+
| SurfaceResolverOutput;
|
|
67
|
+
|
|
68
|
+
interface LeftTabOutput {
|
|
69
|
+
type: "left-tab";
|
|
70
|
+
id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
icon?: ReactNode;
|
|
73
|
+
component: ComponentType<LeftTabProps>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface LeftTabProps {
|
|
77
|
+
query: string;
|
|
78
|
+
rootDir?: string;
|
|
79
|
+
bridge: WorkspaceBridge;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The host may keep existing `panels`, `commands`, `catalogs`, and
|
|
84
|
+
`bindings` arrays as temporary compatibility sugar, but plugin bootstrap
|
|
85
|
+
should normalize everything into explicit outputs before registering
|
|
86
|
+
with the lower-level registries.
|
|
87
|
+
|
|
88
|
+
## Ownership boundaries
|
|
89
|
+
|
|
90
|
+
Workspace core owns:
|
|
91
|
+
|
|
92
|
+
- plugin bootstrap and validation
|
|
93
|
+
- command, catalog, panel, output, and surface-resolver registries
|
|
94
|
+
- `DockviewShell` and generic panel hosting
|
|
95
|
+
- generic left-tab host chrome
|
|
96
|
+
- command palette shell
|
|
97
|
+
- event bus transport and workspace-owned event contracts
|
|
98
|
+
- generic data explorer primitives, if they remain domain-neutral
|
|
99
|
+
- superseded for explorer panes by `GENERIC_EXPLORER_PLUGIN_PLAN.md`: generic explorer becomes a workspace-owned feature plugin once it owns pane/output contracts
|
|
100
|
+
- the generic resolver dispatch loop: given an open request, ask registered
|
|
101
|
+
resolvers in precedence order and open the returned panel config
|
|
102
|
+
|
|
103
|
+
Filesystem plugin owns:
|
|
104
|
+
|
|
105
|
+
- Files left-tab output
|
|
106
|
+
- Files catalog output
|
|
107
|
+
- filesystem client and React hooks
|
|
108
|
+
- file event namespace and constants (`filesystem:file.*`)
|
|
109
|
+
- file event stream and cache invalidation binding
|
|
110
|
+
- file editor panels and fallback panels
|
|
111
|
+
- the filesystem surface resolver: path/file requests, extension or glob
|
|
112
|
+
matching, mapping paths to `code-editor` / `markdown-editor` / `csv-viewer`
|
|
113
|
+
/ fallback panels, and open-file behavior through the workspace UI command
|
|
114
|
+
contract
|
|
115
|
+
|
|
116
|
+
Static data / domain plugins own:
|
|
117
|
+
|
|
118
|
+
- their own left-tab outputs
|
|
119
|
+
- their own catalogs
|
|
120
|
+
- their own surface resolvers for their resource types, e.g. series,
|
|
121
|
+
datasets, SQL results, images, notebooks, dashboards, or app-specific
|
|
122
|
+
artifacts
|
|
123
|
+
- their own data source adapters
|
|
124
|
+
|
|
125
|
+
The app shell owns:
|
|
126
|
+
|
|
127
|
+
- the actual chat panel dependency
|
|
128
|
+
- agent server composition
|
|
129
|
+
- app-specific plugins and runtime dependencies
|
|
130
|
+
|
|
131
|
+
## Implementation plan
|
|
132
|
+
|
|
133
|
+
### Phase 1: Add explicit outputs
|
|
134
|
+
|
|
135
|
+
- Add `PluginOutput` types under `packages/workspace/src/shared/plugins/`.
|
|
136
|
+
- Add validation for output IDs, output kinds, and renderable components.
|
|
137
|
+
- Update plugin bootstrap to normalize legacy contribution arrays into
|
|
138
|
+
outputs, then fan outputs into existing registries.
|
|
139
|
+
- Keep existing public fields for this pass so callers do not break
|
|
140
|
+
while the internals move.
|
|
141
|
+
|
|
142
|
+
Acceptance:
|
|
143
|
+
|
|
144
|
+
- Existing plugins still register.
|
|
145
|
+
- Tests cover duplicate output IDs and invalid output shapes.
|
|
146
|
+
|
|
147
|
+
### Phase 2: Make the left pane a generic output host
|
|
148
|
+
|
|
149
|
+
- Replace `WorkbenchLeftPane` filesystem/Data special cases with a
|
|
150
|
+
generic host for `left-tab` outputs.
|
|
151
|
+
- Pass shared `LeftTabProps` into each tab: `query`, `rootDir`, and the
|
|
152
|
+
workspace bridge/UI contract.
|
|
153
|
+
- Move the Files tab declaration into `filesystemPlugin` as a
|
|
154
|
+
`left-tab` output.
|
|
155
|
+
- Move the Data tab declaration into data catalog plugin outputs.
|
|
156
|
+
|
|
157
|
+
Acceptance:
|
|
158
|
+
|
|
159
|
+
- No direct import from `front/chrome/workbench-left` to
|
|
160
|
+
`plugins/filesystemPlugin`.
|
|
161
|
+
- Files tab disappears when the filesystem default plugin is excluded.
|
|
162
|
+
- Data tab appears only when a data plugin contributes it.
|
|
163
|
+
|
|
164
|
+
### Phase 3: Move filesystem hooks into the filesystem plugin
|
|
165
|
+
|
|
166
|
+
- Move the implementation of filesystem client, hooks, event stream, and
|
|
167
|
+
invalidation into `plugins/filesystemPlugin/front/data`.
|
|
168
|
+
- Delete `front/data`; do **not** keep compatibility re-export wrappers.
|
|
169
|
+
Filesystem data APIs are plugin-owned and must be imported from the
|
|
170
|
+
filesystem plugin package surface.
|
|
171
|
+
- Update filesystem plugin internals and all first-party consumers to import
|
|
172
|
+
from `plugins/filesystemPlugin/front/data`, not from `front/data`.
|
|
173
|
+
- Keep generic React Query provider behavior in workspace core only if it
|
|
174
|
+
is still domain-neutral after the move; otherwise let the filesystem plugin
|
|
175
|
+
own its provider/binding.
|
|
176
|
+
|
|
177
|
+
Acceptance:
|
|
178
|
+
|
|
179
|
+
- No tracked files remain under `packages/workspace/src/front/data/`.
|
|
180
|
+
- No first-party code imports `front/data`.
|
|
181
|
+
- Filesystem data APIs remain available through the filesystem plugin public
|
|
182
|
+
surface, not through `front/data` compatibility wrappers.
|
|
183
|
+
- File tree, file search, write, move, create directory, delete, and
|
|
184
|
+
invalidation tests still pass.
|
|
185
|
+
|
|
186
|
+
### Phase 4: Make plugin events composable and plugin-keyed
|
|
187
|
+
|
|
188
|
+
- Split the event map into workspace-core events plus an augmentable
|
|
189
|
+
plugin event map.
|
|
190
|
+
- Key workspace-owned events with the workspace id:
|
|
191
|
+
`workspace:ui.command`, `workspace:editor.save.start`, and
|
|
192
|
+
`workspace:editor.save.end`.
|
|
193
|
+
- Let the filesystem plugin contribute typed event constants and payloads:
|
|
194
|
+
`filesystem:file.changed`, `filesystem:file.created`,
|
|
195
|
+
`filesystem:file.moved`, and `filesystem:file.deleted`.
|
|
196
|
+
- Replace bare event strings in emitters/subscribers with the owning
|
|
197
|
+
contract constants.
|
|
198
|
+
|
|
199
|
+
Acceptance:
|
|
200
|
+
|
|
201
|
+
- No production code emits or subscribes to bare `file:*` or `ui:command`
|
|
202
|
+
names.
|
|
203
|
+
- The generic event bus remains in `front/events`.
|
|
204
|
+
- Filesystem event names are defined by the filesystem plugin.
|
|
205
|
+
- Existing file invalidation, open-file, dock rename/delete, and UI command
|
|
206
|
+
tests still pass after migrating event names.
|
|
207
|
+
|
|
208
|
+
### Phase 5: Move artifact routing out of artifact surface
|
|
209
|
+
|
|
210
|
+
Do **not** add a filesystem-specific `FileHandlerOutput` to the shared plugin
|
|
211
|
+
model. That would make the shared plugin contract know about paths, files,
|
|
212
|
+
globs, and extensions. Instead, add a generic `SurfaceResolverOutput`:
|
|
213
|
+
shared workspace knows only that a plugin can resolve an open request into an
|
|
214
|
+
`OpenPanelConfig`; each plugin owns the request kinds and mapping rules for
|
|
215
|
+
its domain.
|
|
216
|
+
|
|
217
|
+
Example shape:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
interface SurfaceOpenRequest {
|
|
221
|
+
kind: string;
|
|
222
|
+
target: string;
|
|
223
|
+
meta?: Record<string, unknown>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface SurfacePanelResolution {
|
|
227
|
+
component: string;
|
|
228
|
+
id?: string;
|
|
229
|
+
title?: string;
|
|
230
|
+
params?: Record<string, unknown>;
|
|
231
|
+
score?: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface SurfaceResolverOutput {
|
|
235
|
+
type: "surface-resolver";
|
|
236
|
+
resolver: {
|
|
237
|
+
id: string;
|
|
238
|
+
resolve(request: SurfaceOpenRequest): SurfacePanelResolution | undefined;
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Mapping ownership:
|
|
244
|
+
|
|
245
|
+
- Workspace core owns the resolver registry and dispatch loop only:
|
|
246
|
+
collect resolver results and pick the best score.
|
|
247
|
+
- Filesystem plugin owns `workspace.open.path` requests and maps file paths to
|
|
248
|
+
its panels (`code-editor`, `markdown-editor`, `csv-viewer`, fallback, etc.).
|
|
249
|
+
- Data catalog plugin owns `data-catalog.open-row` requests and maps rows to
|
|
250
|
+
its visualization panel. Catalog search remains a separate catalog output.
|
|
251
|
+
- Data/domain plugins own their own resource requests, for example
|
|
252
|
+
`{ type: "series", id: "GDP" } -> chart panel` or
|
|
253
|
+
`{ type: "sql-result", id: "q1" } -> table panel`.
|
|
254
|
+
- The app shell owns plugin order. Later plugins should be able to override
|
|
255
|
+
earlier/default resolvers, so an app plugin can replace filesystem's CSV
|
|
256
|
+
mapping or add domain-specific routing without changing workspace core.
|
|
257
|
+
|
|
258
|
+
Make artifact surface ask the surface resolver registry for an open-panel
|
|
259
|
+
config instead of hardcoding filesystem panel IDs or file extensions.
|
|
260
|
+
|
|
261
|
+
Acceptance:
|
|
262
|
+
|
|
263
|
+
- Artifact surface has no hardcoded filesystem panel IDs, extension maps, or
|
|
264
|
+
fallback panel IDs.
|
|
265
|
+
- Shared plugin types do not mention file/path/glob-specific handler fields.
|
|
266
|
+
- Filesystem plugin contributes the resolver for file/path requests.
|
|
267
|
+
- Excluding filesystem defaults removes filesystem file routing.
|
|
268
|
+
- A host/plugin can override routing by registering a later resolver.
|
|
269
|
+
|
|
270
|
+
### Phase 6: Remove workspace-to-agent imports
|
|
271
|
+
|
|
272
|
+
- Replace workspace shared usage of `@boring/agent` types with local
|
|
273
|
+
structural contracts or app-shell-provided adapters.
|
|
274
|
+
- Move agent-tool validation out of client-facing workspace shared code.
|
|
275
|
+
- Keep server-side composition in app-shell/server entry points only.
|
|
276
|
+
|
|
277
|
+
Acceptance:
|
|
278
|
+
|
|
279
|
+
- `rg '@boring/agent' packages/workspace/src` returns only allowed
|
|
280
|
+
app-shell/server adapter locations, or zero if the wrapper is moved out.
|
|
281
|
+
- Typecheck catches no regressions.
|
|
282
|
+
|
|
283
|
+
## Test plan
|
|
284
|
+
|
|
285
|
+
- `pnpm --filter @boring/workspace typecheck`
|
|
286
|
+
- Workspace plugin tests for output validation and bootstrap fan-out.
|
|
287
|
+
- `WorkspaceProvider` tests for default plugin inclusion/exclusion.
|
|
288
|
+
- `WorkbenchLeftPane` tests for generic left-tab hosting.
|
|
289
|
+
- Filesystem plugin tests for Files tab, catalog search, and open-file
|
|
290
|
+
command dispatch.
|
|
291
|
+
- Artifact surface tests for surface resolver resolution and host override.
|
|
292
|
+
- Browser smoke: command palette Files search opens the selected file in
|
|
293
|
+
the workbench with Enter.
|
|
294
|
+
|
|
295
|
+
## Non-goals for this pass
|
|
296
|
+
|
|
297
|
+
- No npm plugin loading changes.
|
|
298
|
+
- No plugin dependency graph.
|
|
299
|
+
- No route-as-plugin-output support.
|
|
300
|
+
- No deletion of compatibility wrapper files until the migration is
|
|
301
|
+
proven and explicitly approved.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Workspace Plans
|
|
2
|
+
|
|
3
|
+
Active ownership records live in this folder.
|
|
4
|
+
|
|
5
|
+
- `GENERIC_EXPLORER_PLUGIN_PLAN.md` - generic explorer plugin shape and front/plugin ownership audit.
|
|
6
|
+
- `MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md` - macro plugin audit for reusable explorer/surface/event helper extraction.
|
|
7
|
+
- `PLUGIN_OUTPUTS_ISOLATION_PLAN.md` - plugin ownership and isolation plan.
|
|
8
|
+
- `UI_BRIDGE_OWNERSHIP_REFACTOR.md` - UI bridge ownership decision.
|
|
9
|
+
- `archive/` - superseded implementation plans kept for history.
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# UI bridge ownership refactor — move UI tools out of `@boring/agent`
|
|
2
|
+
|
|
3
|
+
**Status:** implemented; kept as ownership record
|
|
4
|
+
**Owners:** workspace, agent
|
|
5
|
+
**Last updated:** 2026-05-01
|
|
6
|
+
|
|
7
|
+
> Naming note, 2026-05-01: this ownership record predates the workspace app
|
|
8
|
+
> split. Current public server composition is `createWorkspaceAgentServer` from
|
|
9
|
+
> `@boring/workspace/app/server`; older `createWorkspaceAgentApp` references
|
|
10
|
+
> below describe the same boundary before the front/server naming cleanup.
|
|
11
|
+
>
|
|
12
|
+
> Current shared contracts live in `packages/workspace/src/shared/ui-bridge.ts`.
|
|
13
|
+
> Current server wiring lives in
|
|
14
|
+
> `packages/workspace/src/app/server/createWorkspaceAgentServer.ts` and
|
|
15
|
+
> `packages/workspace/src/server/ui-control`.
|
|
16
|
+
|
|
17
|
+
## Problem
|
|
18
|
+
|
|
19
|
+
`@boring/agent` currently owns four things that are conceptually workspace concerns:
|
|
20
|
+
|
|
21
|
+
1. **`UiBridge` interface + `UiState` / `UiCommand` types** in `src/shared/ui-bridge.ts`. The discriminated union in `UiCommand` (`openFile` / `openPanel` / `closePanel` / `navigateToLine` / `expandToFile` / `showNotification`) describes operations on a workspace, not on an LLM harness.
|
|
22
|
+
2. **`createInMemoryBridge()`** in `src/server/ui-bridge/createInMemoryBridge.ts`. The bridge is the message queue between "frontend pushed UI state" and "agent dispatched UI command" — both endpoints of the bridge are workspace concerns.
|
|
23
|
+
3. **`uiRoutes` plugin** in `src/server/http/routes/ui.ts`. Serves `/api/v1/ui/*` (PUT state, POST commands, SSE drain). Today this is registered inside `createAgentApp`.
|
|
24
|
+
4. **`createGetUiStateTool` / `createExecUiTool`** in `src/server/catalog/standardCatalog.ts`. The `standardCatalog` factory takes a `uiBridge?: UiBridge` parameter and conditionally appends the two tools. `createAgentApp` constructs an in-memory bridge and passes it.
|
|
25
|
+
|
|
26
|
+
Symptoms:
|
|
27
|
+
|
|
28
|
+
- Standalone `@boring/agent` (CLI mode, no workspace) ships UI tools and HTTP routes the harness can never fulfill — wasted bundle, misleading capability surface for non-workspace consumers.
|
|
29
|
+
- `standardCatalog`'s `uiBridge?` branch is the only place where the catalog has a knob; everything else is fixed. Asymmetric.
|
|
30
|
+
- The agent package's "shared" types are mixed: `AgentTool` (truly generic) sits next to `UiCommand` (workspace-specific). Future readers can't tell which is which.
|
|
31
|
+
- Adding a workspace-specific tool today (e.g., `query_data_catalog`, `focus_chart`) means either bolting it into agent's `standardCatalog` or threading it through `extraTools` from the app shell. Workspace doesn't currently have a clean place to define server-side tool factories.
|
|
32
|
+
|
|
33
|
+
## Goal
|
|
34
|
+
|
|
35
|
+
`@boring/agent` becomes a pure tool harness. It knows nothing about UI bridges, UI state shapes, or workspace-specific commands. It exposes:
|
|
36
|
+
|
|
37
|
+
- A generic `AgentTool` interface
|
|
38
|
+
- `createAgentApp(opts)` — boots Fastify with the LLM loop, chat persistence, file/bash/edit/read/write/find/grep tools, and `/api/v1/agent/*` routes
|
|
39
|
+
- An `extraTools?: AgentTool[]` option as the only seam for hosts to add tools
|
|
40
|
+
- Returns the `FastifyInstance` so the host can register additional plugins
|
|
41
|
+
|
|
42
|
+
`@boring/workspace` owns everything UI-bridge-related:
|
|
43
|
+
|
|
44
|
+
- The `UiBridge` interface, `UiCommand` discriminated union, `UiState` shape (in a new `@boring/workspace/shared` subpath — needed on both client and server, so it lives outside both bundles).
|
|
45
|
+
- `createInMemoryBridge()` impl.
|
|
46
|
+
- `uiRoutes` Fastify plugin.
|
|
47
|
+
- `createGetUiStateTool` / `createExecUiTool` factories.
|
|
48
|
+
- A convenience wrapper `createWorkspaceAgentApp(opts)` that builds the bridge, builds tools that close over it, registers `uiRoutes`, and delegates everything else to `createAgentApp`.
|
|
49
|
+
|
|
50
|
+
App shells get one import:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createWorkspaceAgentApp } from "@boring/workspace/server"
|
|
54
|
+
|
|
55
|
+
const app = await createWorkspaceAgentApp({
|
|
56
|
+
workspaceRoot: process.cwd(),
|
|
57
|
+
mode: "local",
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Standalone agent users (CLI, non-workspace embedders) keep using `createAgentApp` directly and get zero UI surface — smaller bundle, honest contract.
|
|
62
|
+
|
|
63
|
+
## Dependency direction (verify no cycle)
|
|
64
|
+
|
|
65
|
+
After refactor:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
app shell
|
|
69
|
+
└─→ @boring/workspace/server
|
|
70
|
+
└─→ @boring/agent/server (createAgentApp, FastifyInstance type)
|
|
71
|
+
└─→ @boring/agent/shared (AgentTool interface, ToolResult — only the truly generic tool types stay here)
|
|
72
|
+
└─→ @boring/workspace/shared (UiBridge / UiCommand / UiState)
|
|
73
|
+
@boring/workspace/front
|
|
74
|
+
└─→ @boring/workspace/shared (for UiCommand type)
|
|
75
|
+
└─→ @boring/agent/ui-shadcn (ChatPanel — unchanged)
|
|
76
|
+
@boring/agent (no edges into workspace, never)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`@boring/workspace/server` imports from `@boring/agent`. The reverse never happens.
|
|
80
|
+
|
|
81
|
+
`@boring/boring-macro` (existing) imports `AgentTool` + `ToolResult` from `@boring/agent/shared` — those are pure tool types and stay in agent. No change.
|
|
82
|
+
|
|
83
|
+
`@boring/agent/front-shadcn/ChatPanel.tsx` currently has a `bridge?: UiBridge` prop that is **unused in the implementation**. The prop is destructured at the top of `ChatPanel` but never threaded into any child. It's dead-code that gives the impression UiBridge is a ChatPanel concern. **Drop the prop entirely** as part of this PR — removes the last surface in agent that references UiBridge.
|
|
84
|
+
|
|
85
|
+
`@boring/core` does not import `UiBridge` / `UiCommand` / `UiState` from anywhere (verified via repo-wide grep). After refactor, core depends only on `@boring/workspace/front` (existing) and never transitively pulls fastify, since workspace's front bundle is split from its server bundle (see Bundling section).
|
|
86
|
+
|
|
87
|
+
## Scope
|
|
88
|
+
|
|
89
|
+
### In scope
|
|
90
|
+
|
|
91
|
+
- File moves listed below.
|
|
92
|
+
- `standardCatalog` API change — drop `uiBridge?` from both the parameter destructure AND the `ToolCatalog` interface definition (per Gemini: "remove from the interface, not just the function").
|
|
93
|
+
- `createAgentApp` — drop `uiRoutes` registration, drop the `createInMemoryBridge()` call. Keep `extraTools`.
|
|
94
|
+
- `@boring/agent`'s `front-shadcn/ChatPanel` — drop the unused `bridge?: UiBridge` prop and the corresponding type import.
|
|
95
|
+
- Workspace package: new `./shared` and `./server` exports, new `tsconfig.server.json` and `tsconfig.front.json`, new `fastify` dependency.
|
|
96
|
+
- App shell migration: `apps/workspace-playground/vite.config.ts` switches from `createAgentApp` to `createWorkspaceAgentApp`.
|
|
97
|
+
- All affected tests move with their code; one new agent-side regression test asserts UI tools are NOT in the standalone catalog.
|
|
98
|
+
|
|
99
|
+
### Out of scope
|
|
100
|
+
|
|
101
|
+
- Adding new tools or new command kinds.
|
|
102
|
+
- Changing the wire protocol (SSE format, PUT body shape).
|
|
103
|
+
- The deferred Gemini-review items from earlier round: Last-Event-ID seq replay, multi-tab session keying, promise-based `whenReady` to replace double-RAF.
|
|
104
|
+
- Touching `@boring/core` or any core integration.
|
|
105
|
+
|
|
106
|
+
## File moves and edits
|
|
107
|
+
|
|
108
|
+
### Files moved out of `@boring/agent`
|
|
109
|
+
|
|
110
|
+
| From | To |
|
|
111
|
+
|------|----|
|
|
112
|
+
| `packages/agent/src/shared/ui-bridge.ts` | `packages/workspace/src/shared/ui-bridge.ts` |
|
|
113
|
+
| `packages/agent/src/server/ui-bridge/createInMemoryBridge.ts` | `packages/workspace/src/server/ui-bridge/createInMemoryBridge.ts` |
|
|
114
|
+
| `packages/agent/src/server/ui-bridge/__tests__/createInMemoryBridge.test.ts` | `packages/workspace/src/server/ui-bridge/__tests__/createInMemoryBridge.test.ts` |
|
|
115
|
+
| `packages/agent/src/server/http/routes/ui.ts` | `packages/workspace/src/server/http/uiRoutes.ts` |
|
|
116
|
+
| The `createGetUiStateTool` + `createExecUiTool` factories from `packages/agent/src/server/catalog/standardCatalog.ts` (lines ~19-90) | new `packages/workspace/src/server/uiTools.ts` |
|
|
117
|
+
| `packages/agent/src/server/catalog/tools/__tests__/uiTools.test.ts` | `packages/workspace/src/server/__tests__/uiTools.test.ts` |
|
|
118
|
+
|
|
119
|
+
### Files edited in `@boring/agent`
|
|
120
|
+
|
|
121
|
+
- `src/server/catalog/standardCatalog.ts` — drop `uiBridge?` from the destructured `ToolCatalog` deps AND from the `ToolCatalog` interface definition. Drop the `if (uiBridge) {...}` branch. The catalog signature shrinks; no other behavioural change.
|
|
122
|
+
- `src/server/createAgentApp.ts` — delete:
|
|
123
|
+
- `import { uiRoutes } from './http/routes/ui'`
|
|
124
|
+
- `import { createInMemoryBridge } from './ui-bridge/createInMemoryBridge'`
|
|
125
|
+
- `const uiBridge = createInMemoryBridge()` line
|
|
126
|
+
- `await app.register(uiRoutes, { bridge: uiBridge })` line
|
|
127
|
+
- The `uiBridge` arg to `standardCatalog({ ...runtimeBundle, uiBridge })` — becomes `standardCatalog(runtimeBundle)`.
|
|
128
|
+
- `src/front-shadcn/ChatPanel.tsx` — drop `bridge?: UiBridge` from `ChatPanelProps`, drop the destructure, drop the `import type { UiBridge }` line.
|
|
129
|
+
- `src/server/__tests__/createAgentApp.test.ts` — three tests added in commit `01bf41f` (`createAgentApp registers get_ui_state and exec_ui in the catalog`, `PUT /api/v1/ui/state is round-tripped by GET`, `exec_ui-style POST /api/v1/ui/commands enqueues for drain`) **move to workspace** — they now assert against `createWorkspaceAgentApp`. Add **one new test in agent** asserting the standalone agent's catalog does NOT include UI tools AND `/api/v1/ui/state` returns 404 — regression test pinning the new contract.
|
|
130
|
+
- `src/index.ts` / `src/server/index.ts` / `src/shared/index.ts` — remove any re-exports of `UiBridge`, `UiCommand`, `UiState`, `createInMemoryBridge`.
|
|
131
|
+
|
|
132
|
+
### Files added in `@boring/workspace`
|
|
133
|
+
|
|
134
|
+
- `packages/workspace/src/shared/index.ts` — re-export `UiBridge`, `UiCommand`, `UiState`, `CommandResult` types from the moved `ui-bridge.ts`. **Strict isolation rule (build-enforced — see Bundling section): zero imports from `../server/**` or `../components/**` or `../front/**`.**
|
|
135
|
+
- `packages/workspace/src/server/index.ts` — public server-side surface:
|
|
136
|
+
```ts
|
|
137
|
+
export { createWorkspaceAgentApp } from "./createWorkspaceAgentApp"
|
|
138
|
+
export { createInMemoryBridge } from "./ui-bridge/createInMemoryBridge"
|
|
139
|
+
export { uiRoutes } from "./http/uiRoutes"
|
|
140
|
+
export { createGetUiStateTool, createExecUiTool, createWorkspaceUiTools } from "./uiTools"
|
|
141
|
+
export type * from "../shared"
|
|
142
|
+
```
|
|
143
|
+
- `packages/workspace/src/server/createWorkspaceAgentApp.ts` — the wrapper:
|
|
144
|
+
```ts
|
|
145
|
+
import { createAgentApp, type CreateAgentAppOptions } from "@boring/agent/server"
|
|
146
|
+
import type { FastifyInstance } from "fastify"
|
|
147
|
+
import { createInMemoryBridge } from "./ui-bridge/createInMemoryBridge"
|
|
148
|
+
import { createWorkspaceUiTools } from "./uiTools"
|
|
149
|
+
import { uiRoutes } from "./http/uiRoutes"
|
|
150
|
+
|
|
151
|
+
export async function createWorkspaceAgentApp(
|
|
152
|
+
opts: CreateAgentAppOptions = {},
|
|
153
|
+
): Promise<FastifyInstance> {
|
|
154
|
+
const bridge = createInMemoryBridge()
|
|
155
|
+
const tools = createWorkspaceUiTools(bridge)
|
|
156
|
+
const app = await createAgentApp({
|
|
157
|
+
...opts,
|
|
158
|
+
extraTools: [...(opts.extraTools ?? []), ...tools],
|
|
159
|
+
})
|
|
160
|
+
await app.register(uiRoutes, { bridge })
|
|
161
|
+
return app
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
- `packages/workspace/src/server/uiTools.ts` — the moved tool factories plus a `createWorkspaceUiTools(bridge)` convenience that returns both as an `AgentTool[]`.
|
|
165
|
+
|
|
166
|
+
### Bundling — explicit plan (per Gemini, "verify during implementation" was insufficient)
|
|
167
|
+
|
|
168
|
+
Workspace today is a single browser-targeted bundle. After refactor it must produce three separate output trees:
|
|
169
|
+
|
|
170
|
+
| Bundle | Source | Targets | Allowed runtime |
|
|
171
|
+
|--------|--------|---------|-----------------|
|
|
172
|
+
| `dist/index.js` (front) | `src/components`, `src/dock`, plugin-owned frontend data such as `src/plugins/filesystemPlugin/front/data`, etc. | browser | DOM, React, Tailwind. NO node built-ins, NO fastify. |
|
|
173
|
+
| `dist/server/index.js` | `src/server` | node | Fastify, node http/streams. NO React, NO DOM. |
|
|
174
|
+
| `dist/shared/index.js` | `src/shared` | both | Pure types only, no runtime imports. |
|
|
175
|
+
|
|
176
|
+
Concrete changes:
|
|
177
|
+
|
|
178
|
+
1. **`packages/workspace/package.json`**:
|
|
179
|
+
- Add `"fastify"` and `"zod"` (uiRoutes uses zod) as direct `dependencies`. Currently agent ships these and we'd transitively pull through; making them direct removes the implicit assumption that agent's deps are stable.
|
|
180
|
+
- Add `"@boring/agent": "workspace:*"` already exists in deps (verified). No change.
|
|
181
|
+
- `"exports"` map updated:
|
|
182
|
+
```jsonc
|
|
183
|
+
{
|
|
184
|
+
".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
|
|
185
|
+
"./globals.css": "./dist/globals.css",
|
|
186
|
+
"./ui-shadcn": { "import": "./dist/ui-shadcn/index.js", "types": "./dist/ui-shadcn/index.d.ts" },
|
|
187
|
+
"./shared": { "import": "./dist/shared/index.js", "types": "./dist/shared/index.d.ts" },
|
|
188
|
+
"./server": { "import": "./dist/server/index.js", "types": "./dist/server/index.d.ts" }
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
- `"files"` array includes `"dist"` already; no change needed.
|
|
192
|
+
- Add `"sideEffects": false` if not present so client bundles tree-shake cleanly.
|
|
193
|
+
|
|
194
|
+
2. **TypeScript split**: replace single `tsconfig.json` with three:
|
|
195
|
+
- `tsconfig.front.json` — `lib: ["DOM","DOM.Iterable","ES2022"]`, includes `src/components/**`, `src/shared/**`, excludes `src/server/**`.
|
|
196
|
+
- `tsconfig.server.json` — `lib: ["ES2022"]`, includes `src/server/**`, `src/shared/**`. Adds `@types/node` to `types`. Excludes `src/components/**`.
|
|
197
|
+
- `tsconfig.json` — root project-references config that points to both. CI typecheck runs both.
|
|
198
|
+
- **Why**: prevents DOM types leaking into server code (silent `window` access at compile time) and node types leaking into front code (silent `process.env` access). Both are real bugs that strict tsconfig-split catches at typecheck time.
|
|
199
|
+
|
|
200
|
+
3. **Build pipeline**: workspace currently uses Vite for the bundle. The server output should use a separate config or a separate tsup step. **Open question**: prefer Vite (consistent tooling) or tsup (simpler for pure-Node ESM). Lean: tsup for the server bundle — it's what `@boring/agent` already uses and matches the node-target ergonomics.
|
|
201
|
+
|
|
202
|
+
4. **Build-time isolation check** (per Gemini, prevents `shared` accidentally importing from `server`):
|
|
203
|
+
- Add a small script `scripts/assert-bundle-isolation.mjs` that:
|
|
204
|
+
- Parses `dist/shared/index.js` AST and asserts no `import` / `require` references `fastify`, `@fastify/*`, `node:*`.
|
|
205
|
+
- Parses `dist/index.js` (front) AST and asserts no `import` references `fastify`, `@fastify/*`, `node:fs`, `node:http`, `@boring/workspace/server`.
|
|
206
|
+
- Wire as a `postbuild` step in `workspace/package.json`. Fails the build (and CI) on a regression. Implementation is ~30 lines using `acorn` or `es-module-lexer` (both are common dev deps).
|
|
207
|
+
|
|
208
|
+
5. **CI check** that imports `@boring/workspace` (front) in a fresh Node process and asserts `require.cache` (or ESM equivalent — `import.meta.resolve` introspection) doesn't contain `fastify`. Catches the dynamic-import-loophole that AST analysis alone can miss.
|
|
209
|
+
|
|
210
|
+
### Test coverage additions (per Gemini)
|
|
211
|
+
|
|
212
|
+
In addition to the test migration table:
|
|
213
|
+
|
|
214
|
+
- **`extraTools` merge test** in workspace: pass an arbitrary host tool `{ name: 'host_tool', ... }` to `createWorkspaceAgentApp({ extraTools: [hostTool] })`, hit `/api/v1/agent/catalog`, assert the response contains BOTH `host_tool` AND `get_ui_state` AND `exec_ui`. Pins that the wrapper merges rather than overwrites.
|
|
215
|
+
- **Bundle isolation test** in workspace's CI: a scripted check (see Bundling §4) plus a runtime test (Bundling §5).
|
|
216
|
+
- **Regression test in agent** (per the file-edits list above) — `createAgentApp` standalone must NOT include UI tools and `/api/v1/ui/state` must 404.
|
|
217
|
+
|
|
218
|
+
## API surface — before / after
|
|
219
|
+
|
|
220
|
+
### Before
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
// agent
|
|
224
|
+
import { createAgentApp } from "@boring/agent/server"
|
|
225
|
+
import type { UiCommand, UiState, UiBridge } from "@boring/agent/shared"
|
|
226
|
+
|
|
227
|
+
const app = await createAgentApp({ workspaceRoot, mode: "local" })
|
|
228
|
+
// app exposes /api/v1/agent/*, /api/v1/ui/*, includes get_ui_state + exec_ui in catalog
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### After
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// agent (standalone — no UI surface)
|
|
235
|
+
import { createAgentApp } from "@boring/agent/server"
|
|
236
|
+
|
|
237
|
+
const app = await createAgentApp({ workspaceRoot, mode: "local" })
|
|
238
|
+
// app exposes /api/v1/agent/* only, catalog has bash/read/write/edit/etc. — no UI tools
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// agent + workspace
|
|
243
|
+
import { createWorkspaceAgentApp } from "@boring/workspace/server"
|
|
244
|
+
import type { UiCommand, UiState } from "@boring/workspace/shared"
|
|
245
|
+
|
|
246
|
+
const app = await createWorkspaceAgentApp({ workspaceRoot, mode: "local" })
|
|
247
|
+
// app exposes /api/v1/agent/* AND /api/v1/ui/*, catalog includes UI tools
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
App shell migration is mechanical: rename the import + function call.
|
|
251
|
+
|
|
252
|
+
## Migration plan — ATOMIC single PR (revised per Gemini)
|
|
253
|
+
|
|
254
|
+
Step-by-step migration is unsafe: in a multi-commit version, the intermediate state would have BOTH `createAgentApp` registering `uiRoutes` internally AND `createWorkspaceAgentApp` registering it again, producing a `FST_ERR_DUP_ROUTE` on boot. **Land everything in one PR.**
|
|
255
|
+
|
|
256
|
+
Implementation order within the PR (driven by what compiles at each step):
|
|
257
|
+
|
|
258
|
+
1. Create `packages/workspace/src/shared/` and copy `ui-bridge.ts` types in. Update workspace's `tsconfig.json` → `tsconfig.front.json` + `tsconfig.server.json` + root project references. Add `fastify` + `zod` to workspace `package.json`.
|
|
259
|
+
2. Create `packages/workspace/src/server/` with `createInMemoryBridge.ts`, `uiTools.ts`, `http/uiRoutes.ts`, `createWorkspaceAgentApp.ts`, `index.ts`. Update workspace `package.json` `exports` to expose `./shared` and `./server`.
|
|
260
|
+
3. In agent: drop `bridge?: UiBridge` prop from ChatPanel, drop UI bridge import. Drop `uiBridge?` from `standardCatalog` interface and impl. In `createAgentApp`, remove the `createInMemoryBridge()` + `app.register(uiRoutes)` lines and the imports that supported them. Delete `src/shared/ui-bridge.ts`, `src/server/ui-bridge/`, `src/server/http/routes/ui.ts`, the UI tool factories, the related tests.
|
|
261
|
+
4. In agent's `createAgentApp.test.ts`: replace the three UI bridge tests with the regression test (catalog has no UI tools, `/api/v1/ui/state` is 404).
|
|
262
|
+
5. Add the new tests in workspace: round-trip / queue-drain against `createWorkspaceAgentApp`, plus the `extraTools` merge test.
|
|
263
|
+
6. Migrate `apps/workspace-playground/vite.config.ts` to use `createWorkspaceAgentApp`.
|
|
264
|
+
7. Wire bundle isolation check (`scripts/assert-bundle-isolation.mjs` + postbuild hook + CI runtime check).
|
|
265
|
+
8. Documentation pass — update `archive/WORKSPACE_V2_PLAN.md` and `agent/docs/API.md`.
|
|
266
|
+
|
|
267
|
+
CI must be green at the END of the PR. Intermediate commits within the PR may be red — the agent-only step (3) breaks until the workspace side (1, 2) is in place. That's acceptable for a single PR landing as one merge.
|
|
268
|
+
|
|
269
|
+
## Risks and unknowns (revised)
|
|
270
|
+
|
|
271
|
+
1. **Bundle separation** — must produce three bundles cleanly (front, server, shared). Caught via tsconfig split + AST check + runtime test. Closed under the Bundling section above.
|
|
272
|
+
|
|
273
|
+
2. **Fastify peer/direct dep duplication** — workspace will declare `fastify` directly; agent already declares it. pnpm will hoist a single instance, so route collisions don't happen at the module-identity level. Verified during implementation that `pnpm why fastify` in `apps/workspace-playground` resolves to one instance.
|
|
274
|
+
|
|
275
|
+
3. **`uiBridge` removal from `ToolCatalog` interface** — this is a breaking type change for any direct consumer of `ToolCatalog`. Inside the monorepo only `standardCatalog` consumes it. External consumers don't exist yet (pre-1.0). Caught via grep during step 3.
|
|
276
|
+
|
|
277
|
+
4. **In-flight session migration** — none. The bridge is in-memory and ephemeral. Across an agent server restart all state is lost regardless. Refactor doesn't change that.
|
|
278
|
+
|
|
279
|
+
5. **`@boring/boring-macro`** — imports `AgentTool` + `ToolResult` from `@boring/agent/shared`. Those symbols stay in agent; macro is unaffected.
|
|
280
|
+
|
|
281
|
+
## Open questions (resolved per Gemini review)
|
|
282
|
+
|
|
283
|
+
| Q | Resolution |
|
|
284
|
+
|---|------------|
|
|
285
|
+
| 1. Multi-commit migration vs single PR? | Single PR — multi-commit hits `FST_ERR_DUP_ROUTE` in intermediate state. |
|
|
286
|
+
| 2. Split `UiBridge` interface from impl? | Yes — interface in `workspace/shared`, impl in `workspace/server`. Mirrors agent's existing convention. |
|
|
287
|
+
| 3. `toolFactories` mechanism on `createAgentApp`? | No — workspace tools close over their bridge in the wrapper. Defer until a real second use case emerges. |
|
|
288
|
+
| 4. Core dep on UI types? | Verified clean — core has zero imports of UI bridge surface. |
|
|
289
|
+
| 5. `createWorkspaceAgentApp` naming? | Keep — precisely describes "agent app + workspace UI surface". |
|
|
290
|
+
|
|
291
|
+
## Done definition
|
|
292
|
+
|
|
293
|
+
- [ ] All file moves complete (table above).
|
|
294
|
+
- [ ] `pnpm --filter @boring/agent test` green; agent test suite includes the new "no UI tools, no /api/v1/ui/* route" regression test.
|
|
295
|
+
- [ ] `pnpm --filter @boring/workspace test` green; tests for `createWorkspaceAgentApp`, `createInMemoryBridge`, UI tool factories, AND the `extraTools` merge test all live here.
|
|
296
|
+
- [ ] `apps/workspace-playground/vite.config.ts` uses `createWorkspaceAgentApp`. End-to-end smoke (PUT /ui/state → get_ui_state via tool → expected payload) passes manually.
|
|
297
|
+
- [ ] `pnpm --filter @boring/workspace build` produces `dist/index.js` (front, no fastify), `dist/server/index.js` (server, no React), `dist/shared/index.js` (no runtime deps).
|
|
298
|
+
- [ ] `scripts/assert-bundle-isolation.mjs` passes as a postbuild step.
|
|
299
|
+
- [ ] CI runtime test confirms importing `@boring/workspace` in node does not pull `fastify` into the module graph.
|
|
300
|
+
- [ ] No remaining `import ... from "@boring/agent/shared"` for `UiBridge` / `UiCommand` / `UiState` anywhere in the repo.
|
|
301
|
+
- [ ] `@boring/agent`'s `ChatPanel` no longer references `UiBridge`.
|
|
302
|
+
- [ ] `archive/WORKSPACE_V2_PLAN.md` and `agent/docs/API.md` reflect the new shape.
|
|
303
|
+
- [ ] Single PR (multiple commits within it OK as long as the final tree is green).
|