@hachej/boring-agent 0.1.111 → 0.2.1

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 (156) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +262 -51
  3. package/dist/agents.d.ts +75 -0
  4. package/dist/agents.js +91 -0
  5. package/dist/approval.d.ts +71 -0
  6. package/dist/approval.js +126 -0
  7. package/dist/artifacts.d.ts +53 -0
  8. package/dist/artifacts.js +129 -0
  9. package/dist/ask-user.d.ts +40 -0
  10. package/dist/ask-user.js +134 -0
  11. package/dist/attachment.d.ts +5 -0
  12. package/dist/attachment.js +4 -0
  13. package/dist/browser-task.d.ts +32 -0
  14. package/dist/browser-task.js +119 -0
  15. package/dist/chat-transport.d.ts +80 -0
  16. package/dist/chat-transport.js +306 -0
  17. package/dist/contracts.d.ts +40 -0
  18. package/dist/contracts.js +1 -0
  19. package/dist/conversations.d.ts +136 -0
  20. package/dist/conversations.js +366 -0
  21. package/dist/definitions.d.ts +37 -0
  22. package/dist/definitions.js +99 -0
  23. package/dist/delivery-input.d.ts +35 -0
  24. package/dist/delivery-input.js +56 -0
  25. package/dist/delivery.d.ts +33 -0
  26. package/dist/delivery.js +154 -0
  27. package/dist/durable-wait.d.ts +23 -0
  28. package/dist/durable-wait.js +79 -0
  29. package/dist/file-convert.d.ts +46 -0
  30. package/dist/file-convert.js +159 -0
  31. package/dist/file-guard.d.ts +50 -0
  32. package/dist/file-guard.js +177 -0
  33. package/dist/file-types.d.ts +17 -0
  34. package/dist/file-types.js +95 -0
  35. package/dist/gateway-provider.d.ts +24 -0
  36. package/dist/gateway-provider.js +42 -0
  37. package/dist/git.d.ts +34 -0
  38. package/dist/git.js +79 -0
  39. package/dist/harness-pool.d.ts +38 -0
  40. package/dist/harness-pool.js +134 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +1 -0
  43. package/dist/json-schema-tool.d.ts +25 -0
  44. package/dist/json-schema-tool.js +19 -0
  45. package/dist/memory/optchat-core.d.ts +118 -0
  46. package/dist/memory/optchat-core.js +317 -0
  47. package/dist/memory/optchat.d.ts +156 -0
  48. package/dist/memory/optchat.js +432 -0
  49. package/dist/mentions.d.ts +56 -0
  50. package/dist/mentions.js +140 -0
  51. package/dist/metering.d.ts +183 -0
  52. package/dist/metering.js +443 -0
  53. package/dist/model-gateway.d.ts +86 -0
  54. package/dist/model-gateway.js +241 -0
  55. package/dist/native-version.d.ts +9 -0
  56. package/dist/native-version.js +7 -0
  57. package/dist/native.d.ts +4 -0
  58. package/dist/native.js +1 -0
  59. package/dist/presentation.d.ts +17 -0
  60. package/dist/presentation.js +39 -0
  61. package/dist/projection-format.d.ts +68 -0
  62. package/dist/projection-format.js +118 -0
  63. package/dist/projection-window.d.ts +9 -0
  64. package/dist/projection-window.js +93 -0
  65. package/dist/projection.d.ts +22 -0
  66. package/dist/projection.js +153 -0
  67. package/dist/question-response.d.ts +20 -0
  68. package/dist/question-response.js +75 -0
  69. package/dist/questions.d.ts +74 -0
  70. package/dist/questions.js +199 -0
  71. package/dist/self-evolving.d.ts +55 -0
  72. package/dist/self-evolving.js +239 -0
  73. package/dist/skills.d.ts +15 -0
  74. package/dist/skills.js +51 -0
  75. package/dist/tool-evidence.d.ts +14 -0
  76. package/dist/tool-evidence.js +54 -0
  77. package/dist/validation.d.ts +38 -0
  78. package/dist/validation.js +68 -0
  79. package/dist/workspaces.d.ts +107 -0
  80. package/dist/workspaces.js +212 -0
  81. package/package.json +145 -150
  82. package/dist/DebugDrawer-JNA43SVR.js +0 -8
  83. package/dist/admin-DORVQfLn.d.ts +0 -340
  84. package/dist/chunk-2BGLZZ7T.js +0 -10
  85. package/dist/chunk-42D6KAQV.js +0 -2882
  86. package/dist/chunk-4XOW57KJ.js +0 -343
  87. package/dist/chunk-72KT6VWV.js +0 -1338
  88. package/dist/chunk-7XAOVFSN.js +0 -28
  89. package/dist/chunk-KJIEMSQP.js +0 -198
  90. package/dist/chunk-KN72XWP7.js +0 -394
  91. package/dist/chunk-N6THTH7P.js +0 -110
  92. package/dist/chunk-V3H5A3BR.js +0 -22501
  93. package/dist/chunk-YGRQ4Q5P.js +0 -20
  94. package/dist/chunk-YTUZFKB3.js +0 -78
  95. package/dist/core/index.d.ts +0 -28
  96. package/dist/core/index.js +0 -617
  97. package/dist/createHarness-DOLOHA62.js +0 -18
  98. package/dist/eval/index.d.ts +0 -247
  99. package/dist/eval/index.js +0 -541
  100. package/dist/file-search-BUgMSKZE.d.ts +0 -289
  101. package/dist/front/artifacts.d.ts +0 -34
  102. package/dist/front/artifacts.js +0 -21
  103. package/dist/front/index.d.ts +0 -1082
  104. package/dist/front/index.js +0 -12912
  105. package/dist/front/styles.css +0 -5380
  106. package/dist/harness-qHlMJhZo.d.ts +0 -276
  107. package/dist/middleware-CaAEXpi5.d.ts +0 -115
  108. package/dist/piChatEvent-DpE0lIb9.d.ts +0 -450
  109. package/dist/piChatSessionService-CBct-hmz.d.ts +0 -86
  110. package/dist/sandbox-BMhut3kh.d.ts +0 -317
  111. package/dist/server/agent-host/testing/compositionRouteProof.d.ts +0 -8
  112. package/dist/server/agent-host/testing/compositionRouteProof.js +0 -78
  113. package/dist/server/agent-host/testing/gatewayConformance.d.ts +0 -43
  114. package/dist/server/agent-host/testing/gatewayConformance.js +0 -605
  115. package/dist/server/index.d.ts +0 -2278
  116. package/dist/server/index.js +0 -274
  117. package/dist/server/pi-session-readability.d.ts +0 -12
  118. package/dist/server/pi-session-readability.js +0 -23
  119. package/dist/server/worker/index.d.ts +0 -68
  120. package/dist/server/worker/index.js +0 -377
  121. package/dist/share-entry-CxS6UJ_L.d.ts +0 -330
  122. package/dist/shared/index.d.ts +0 -2111
  123. package/dist/shared/index.js +0 -262
  124. package/dist/types-DcWoQMn8.d.ts +0 -1377
  125. package/docs/ACCESSIBILITY.md +0 -52
  126. package/docs/AGENT_GATEWAY_V0.md +0 -212
  127. package/docs/AGENT_PACKAGES.md +0 -90
  128. package/docs/API.md +0 -123
  129. package/docs/AUTHORING.md +0 -68
  130. package/docs/CSP.md +0 -41
  131. package/docs/ERROR_CODES.md +0 -229
  132. package/docs/KNOWN_LIMITATIONS.md +0 -100
  133. package/docs/MIGRATION.md +0 -50
  134. package/docs/PERFORMANCE.md +0 -68
  135. package/docs/PLUGINS.md +0 -138
  136. package/docs/README.md +0 -98
  137. package/docs/RISKS-MULTI-TAB.md +0 -46
  138. package/docs/SESSION_IDENTITY.md +0 -91
  139. package/docs/STYLING.md +0 -73
  140. package/docs/UI-SHADCN.md +0 -56
  141. package/docs/VERCEL_COSTS.md +0 -103
  142. package/docs/plans/archive/AGENT_EVAL_FRAMEWORK.md +0 -466
  143. package/docs/plans/archive/agent-package-spec.md +0 -1777
  144. package/docs/plans/archive/harness-followup-capabilities.md +0 -440
  145. package/docs/plans/archive/harness-tool-ui-capabilities.md +0 -144
  146. package/docs/plans/archive/pi-followup-history-projection.md +0 -229
  147. package/docs/plans/archive/pi-tools-migration.md +0 -1061
  148. package/docs/plans/archive/reviews/pi-followup-history-codex-review.md +0 -11
  149. package/docs/plans/archive/reviews/pi-followup-history-gpt-review.md +0 -64
  150. package/docs/plans/archive/reviews/pi-followup-history-opus-review.md +0 -43
  151. package/docs/plans/archive/reviews/pi-followup-history-xai-review.md +0 -43
  152. package/docs/plans/archive/vercel-base-snapshot-template-plan.md +0 -527
  153. package/docs/plans/archive/vercel-persistent-sandbox-adapter.md +0 -553
  154. package/docs/runtime-provisioning.md +0 -157
  155. package/docs/runtime.md +0 -296
  156. package/docs/tools.md +0 -102
@@ -1,553 +0,0 @@
1
- # Vercel Persistent Sandbox Adapter Plan
2
-
3
- Status: draft
4
- Date: 2026-04-29
5
- Owner package: `@boring/agent`
6
- Consumer app: `apps/full-app`
7
-
8
- ## Summary
9
-
10
- Move full-app workspaces from Vercel Sandbox stable session IDs to Vercel
11
- Sandbox beta persistent names.
12
-
13
- The product invariant stays:
14
-
15
- > One Boring workspace maps to one durable sandbox identity.
16
-
17
- With Vercel stable `@vercel/sandbox@1.10.0`, the durable thing we stored was a
18
- session-like `sandboxId`. When that sandbox stopped and there was no snapshot,
19
- the filesystem was not recoverable. With Vercel beta persistent sandboxes, the
20
- durable thing is the sandbox `name`; sessions are compute instances that can be
21
- stopped and resumed from persisted state.
22
-
23
- This is not a drop-in package bump. Beta removes the old `sandbox.fs.readdir`
24
- style API and exposes persistent sandbox methods that resume sessions under
25
- the hood. We need a new adapter path that speaks the beta API cleanly.
26
-
27
- ## Validated Facts
28
-
29
- Versions checked:
30
-
31
- - Current repo dependency: `@vercel/sandbox@1.10.0`
32
- - Current beta: `@vercel/sandbox@2.0.0-beta.14`
33
-
34
- Official docs say:
35
-
36
- - Stable `Sandbox.get({ sandboxId })` reconnects to an active sandbox.
37
- - Stable snapshots recreate by starting a new sandbox from a snapshot.
38
- - Beta persistent sandboxes use `Sandbox.create({ name })` and
39
- `Sandbox.get({ name })`.
40
- - Beta persistence is enabled by default and can resume a stopped sandbox by
41
- creating a new session from persisted filesystem state.
42
-
43
- Live validation performed against beta:
44
-
45
- 1. Created named sandbox with `persistent: true`.
46
- 2. Wrote `probe.txt`.
47
- 3. Stopped the sandbox with `blocking: true`.
48
- 4. Fetched with `Sandbox.get({ name, resume: false })`; status was `stopped`.
49
- 5. Called `readFileToBuffer({ path: "probe.txt" })`.
50
- 6. SDK resumed the sandbox and returned the file content.
51
- 7. Session list became `["stopped", "running"]`.
52
- 8. Test sandbox was deleted.
53
-
54
- Conclusion: beta file operations do resume a stopped persistent sandbox. We do
55
- not need a noop wake command before reads if the adapter uses beta `Sandbox`
56
- methods. Command-backed operations also resume through `runCommand`.
57
-
58
- ## Goals
59
-
60
- - Make `apps/full-app` use persistent Vercel sandboxes by default once the
61
- adapter is proven.
62
- - Keep `@boring/agent` standalone and core-free.
63
- - Preserve the existing stable Vercel adapter for standalone users until beta
64
- is GA or explicitly adopted.
65
- - Store durable sandbox identity as a sandbox name, not a session id.
66
- - Keep session id as metadata only.
67
- - Make stopped sandbox recovery automatic when Vercel persistence is enabled.
68
- - Keep no-snapshot data-loss protection for the stable adapter.
69
- - Keep path validation inside the workspace adapter before any command or file
70
- operation crosses into the remote sandbox.
71
- - Keep full-app wiring simple: it injects stores and chooses mode/channel; it
72
- does not own sandbox lifecycle behavior.
73
-
74
- ## Non-Goals
75
-
76
- - Do not delete existing Vercel sandboxes or snapshots.
77
- - Do not migrate old stopped stable sandboxes that have no snapshot. Their
78
- filesystem data is already unrecoverable.
79
- - Do not remove manual snapshot support. Keep it as fallback and for stable.
80
- - Do not make `@boring/core` import `@boring/agent`.
81
- - Do not hand-roll shell escaping for file operations. Use validated paths plus
82
- argv-based Node scripts or beta SDK file methods.
83
-
84
- ## Proposed User-Facing Policy
85
-
86
- Default full-app policy:
87
-
88
- - `1 workspace = 1 persistent Vercel sandbox name`
89
- - Name format is deterministic from workspace id and deployment namespace.
90
- - Current session id can change over time.
91
- - Stopping a sandbox is allowed; the next file or command operation resumes it.
92
- - Workspace reset is the explicit destructive action that creates a new durable
93
- sandbox name or deletes the existing one.
94
-
95
- Standalone agent policy:
96
-
97
- - `vercel-sandbox` remains stable SDK behavior by default.
98
- - Add opt-in persistent beta channel for users who want named persistence.
99
-
100
- ## Package Strategy
101
-
102
- Add the beta SDK as an npm alias instead of replacing stable immediately:
103
-
104
- ```json
105
- {
106
- "dependencies": {
107
- "@vercel/sandbox": "^1.10.0",
108
- "@vercel/sandbox-beta": "npm:@vercel/sandbox@2.0.0-beta.14"
109
- }
110
- }
111
- ```
112
-
113
- Rationale:
114
-
115
- - Stable adapter can continue importing `@vercel/sandbox`.
116
- - Persistent adapter imports `@vercel/sandbox-beta`.
117
- - Type surfaces can differ without conditional imports or unsafe casts.
118
- - We can remove the alias after beta reaches GA.
119
-
120
- ## Runtime Selection
121
-
122
- Prefer a channel flag over exposing beta as the only mode:
123
-
124
- ```text
125
- BORING_AGENT_MODE=vercel-sandbox
126
- BORING_AGENT_VERCEL_SANDBOX_CHANNEL=stable | persistent-beta
127
- ```
128
-
129
- Defaults:
130
-
131
- - Agent package standalone: `stable`
132
- - Full app: `persistent-beta`
133
-
134
- Alternative:
135
-
136
- - Add a new runtime mode id, `vercel-sandbox-persistent`.
137
-
138
- Recommendation:
139
-
140
- - Use the channel flag internally first. The mode remains conceptually Vercel
141
- sandbox; the channel selects provider API generation.
142
-
143
- ## Store Contract
144
-
145
- Current `SandboxHandleRecord` assumes `sandboxId` is durable. That is true for
146
- stable only. Add a discriminated union.
147
-
148
- Target shape:
149
-
150
- ```ts
151
- export type SandboxHandleRecord =
152
- | {
153
- kind: "vercel-session"
154
- workspaceId: string
155
- sandboxId: string
156
- snapshotId?: string
157
- createdAt: string
158
- lastUsedAt: string
159
- }
160
- | {
161
- kind: "vercel-persistent"
162
- workspaceId: string
163
- sandboxName: string
164
- currentSessionId?: string
165
- currentSnapshotId?: string
166
- status?: string
167
- persistent: true
168
- createdAt: string
169
- lastUsedAt: string
170
- lastSeenAt?: string
171
- }
172
- ```
173
-
174
- Compatibility rules:
175
-
176
- - Existing records with no `kind` are read as `vercel-session`.
177
- - Stable resolver only accepts `vercel-session`.
178
- - Persistent resolver only accepts `vercel-persistent`.
179
- - If channel changes for a workspace, return a clear configuration error and
180
- require explicit reset/migration.
181
-
182
- ## Core DB Changes
183
-
184
- Core owns the DB-backed store, so add fields to `WorkspaceRuntime`.
185
-
186
- Recommended columns:
187
-
188
- - `sandbox_handle_kind text null`
189
- - `sandbox_name text null`
190
- - `sandbox_id text null`
191
- - `sandbox_current_session_id text null`
192
- - `sandbox_status text null`
193
- - `sandbox_persistent boolean null`
194
- - `sandbox_sdk_channel text null`
195
- - `sandbox_snapshot_id text null`
196
- - `sandbox_created_at timestamptz null`
197
- - `sandbox_last_seen_at timestamptz null`
198
- - `sandbox_last_used_at timestamptz null`
199
-
200
- Mapping:
201
-
202
- - Stable:
203
- - `sandbox_handle_kind = "vercel-session"`
204
- - `sandbox_id = "sbx_..."`
205
- - `sandbox_snapshot_id = "snap_..." | null`
206
- - Persistent beta:
207
- - `sandbox_handle_kind = "vercel-persistent"`
208
- - `sandbox_name = "boring-<namespace>-<workspace-hash>"`
209
- - `sandbox_current_session_id = "sbx_..." | null`
210
- - `sandbox_snapshot_id = current Vercel snapshot id | null`
211
- - `sandbox_persistent = true`
212
- - `sandbox_sdk_channel = "persistent-beta"`
213
-
214
- Name generation:
215
-
216
- - Add `BORING_AGENT_VERCEL_SANDBOX_NAME_PREFIX`.
217
- - Default prefix: `boring`.
218
- - Include deployment namespace, default `dev`.
219
- - Hash workspace id to avoid provider naming constraints.
220
- - Example: `boring-dev-ws-6f8d4a9c2b0e`.
221
-
222
- The exact name validator must be backed by either Vercel docs or live API
223
- errors. Until documented, keep names lowercase alphanumeric plus hyphens.
224
-
225
- ## Agent Architecture
226
-
227
- Add persistent beta files under the existing Vercel sandbox area:
228
-
229
- ```text
230
- packages/agent/src/server/sandbox/vercel-sandbox/
231
- persistentClient.ts
232
- resolvePersistentSandboxHandle.ts
233
- createPersistentVercelSandboxWorkspace.ts
234
- createPersistentVercelSandboxExec.ts
235
- persistentFileOps.ts
236
- ```
237
-
238
- Keep stable files in place:
239
-
240
- ```text
241
- resolveSandboxHandle.ts
242
- createVercelSandboxWorkspace.ts
243
- createVercelSandboxExec.ts
244
- periodicSnapshot.ts
245
- ```
246
-
247
- `createVercelSandboxModeAdapter()` becomes a small dispatcher:
248
-
249
- 1. Resolve auth.
250
- 2. Read `BORING_AGENT_VERCEL_SANDBOX_CHANNEL`.
251
- 3. If `stable`, use current stable adapter path.
252
- 4. If `persistent-beta`, use new persistent path.
253
-
254
- Longer-term cleanup:
255
-
256
- - Extract common auth, timeout, template packaging, logging, and dirty tracking
257
- helpers after both paths are tested.
258
-
259
- ## Persistent Resolver
260
-
261
- New resolver behavior:
262
-
263
- 1. Normalize workspace id.
264
- 2. Compute deterministic sandbox name.
265
- 3. Load store record.
266
- 4. If record exists:
267
- - verify `kind === "vercel-persistent"`
268
- - prefer stored `sandboxName`
269
- - call `Sandbox.get({ name })`
270
- 5. If not found:
271
- - call `Sandbox.create({ name, persistent: true, ...template/source })`
272
- 6. If create conflicts because another process won:
273
- - call `Sandbox.get({ name })`
274
- 7. Persist metadata:
275
- - `sandboxName`
276
- - `currentSessionId`
277
- - `currentSnapshotId`
278
- - `status`
279
- - timestamps
280
-
281
- Use `Sandbox.get({ name })` with default `resume: true` for normal route
282
- resolution. Use `resume: false` only for diagnostics or non-mutating status
283
- checks where waking the sandbox would be surprising.
284
-
285
- ## Workspace Adapter
286
-
287
- Beta SDK has no `sandbox.fs.readdir`. Implement the `Workspace` interface with
288
- a mix of beta methods and command-backed Node scripts.
289
-
290
- Path rules:
291
-
292
- - Accept only user relative paths.
293
- - Use existing `validatePath` logic.
294
- - Convert validated paths to sandbox-relative paths under `/vercel/sandbox`.
295
- - Never concatenate user paths into shell command strings.
296
-
297
- Operation mapping:
298
-
299
- | Workspace method | Persistent implementation |
300
- |---|---|
301
- | `readFile` | `sandbox.readFileToBuffer({ path, cwd })` |
302
- | `writeFile` | `sandbox.writeFiles([{ path, content }])` |
303
- | `mkdir` | `sandbox.mkDir(path)` for simple mkdir; Node script for recursive if needed |
304
- | `readdir` | `runCommand(node, ["-e", script, "--", path])` returning JSON |
305
- | `stat` | `runCommand(node, ["-e", script, "--", path])` returning JSON |
306
- | `unlink` | Node script with `fs.rm(path, { recursive: false })` |
307
- | `rename` | Node script with `fs.rename(from, to)` |
308
-
309
- Node script requirements:
310
-
311
- - Lives in source as a string constant or small packaged helper.
312
- - Receives paths through argv, not interpolated source.
313
- - Emits a single JSON object.
314
- - Maps common errors to stable Boring error codes.
315
- - Has unit tests for path traversal, missing path, and bad JSON.
316
-
317
- Cache:
318
-
319
- - Keep existing short metadata caches for `readdir` and `stat`.
320
- - Invalidate on write, unlink, mkdir, rename, and any sandbox command.
321
- - Since beta can resume and create a new session, cache keys must not depend on
322
- session id unless we intentionally want session-local caches.
323
-
324
- ## Exec Adapter
325
-
326
- Beta `sandbox.runCommand()` is compatible enough for the current exec shape.
327
- Create a beta-specific exec wrapper anyway so types do not depend on stable
328
- `Sandbox`.
329
-
330
- Behavior:
331
-
332
- - Run `sh -c` exactly as the stable exec adapter does for agent commands.
333
- - Preserve timeout, heartbeat, output cap, stdout/stderr streaming.
334
- - Invalidate workspace metadata cache after command completion.
335
- - Persist latest session metadata after resume if available.
336
-
337
- Risk:
338
-
339
- - `sh -c` is acceptable for agent-authored commands, but file operations must
340
- not use shell strings.
341
-
342
- ## Snapshot Policy After Persistent Beta
343
-
344
- Persistent beta changes the primary policy:
345
-
346
- - Automatic Vercel persistence is primary.
347
- - Manual dirty periodic snapshots are fallback only.
348
-
349
- For stable:
350
-
351
- - Keep current dirty snapshot scheduler.
352
- - Keep `SANDBOX_EXPIRED` if no snapshot exists.
353
-
354
- For persistent beta:
355
-
356
- - Do not run periodic snapshot scheduler by default because `snapshot()` stops
357
- the current session.
358
- - Store `currentSnapshotId` if Vercel reports one.
359
- - Offer explicit "checkpoint now" later if product needs it.
360
- - On app shutdown, do not force snapshot; let Vercel persistence handle stop.
361
-
362
- ## Full-App Wiring
363
-
364
- Full-app should only configure and inject:
365
-
366
- - `WorkspaceRuntimeSandboxHandleStore`
367
- - `BORING_AGENT_MODE=vercel-sandbox`
368
- - `BORING_AGENT_VERCEL_SANDBOX_CHANNEL=persistent-beta`
369
- - credentials and timeout/prefix env vars
370
-
371
- Full-app should not:
372
-
373
- - import Vercel SDK directly
374
- - know how resume works
375
- - mutate sandbox handles outside the store implementation
376
-
377
- ## Migration Strategy
378
-
379
- No silent migration from stable stopped sandboxes.
380
-
381
- For active stable workspaces:
382
-
383
- 1. Leave current stable record intact.
384
- 2. When full-app switches to persistent beta, create a new persistent sandbox
385
- name for the workspace.
386
- 3. Seed from template or current workspace source of truth if available.
387
- 4. If old stable sandbox is running and user asks to migrate files, add a
388
- separate explicit migration command later.
389
-
390
- For stopped stable workspaces with no snapshot:
391
-
392
- - Return clear status: old sandbox expired and had no recoverable snapshot.
393
- - Create new persistent sandbox only after explicit reset or provisioning path.
394
-
395
- For development local state:
396
-
397
- - Allow `BORING_AGENT_VERCEL_SANDBOX_CHANNEL=stable` to keep using current
398
- DB rows during rollout.
399
-
400
- ## Rollout Phases
401
-
402
- ### Phase 1 - Types, Store, Config
403
-
404
- - Add beta dependency alias.
405
- - Extend config schema with:
406
- - `BORING_AGENT_VERCEL_SANDBOX_CHANNEL`
407
- - `BORING_AGENT_VERCEL_SANDBOX_NAME_PREFIX`
408
- - `BORING_AGENT_VERCEL_SANDBOX_NAMESPACE`
409
- - Add discriminated sandbox handle type.
410
- - Update `FileHandleStore`.
411
- - Update `WorkspaceRuntimeSandboxHandleStore`.
412
- - Add DB migration and local store support.
413
- - Add type tests.
414
-
415
- Acceptance:
416
-
417
- - Existing stable tests pass.
418
- - Old JSON records without `kind` still load as stable records.
419
- - Core store round-trips stable and persistent records.
420
-
421
- ### Phase 2 - Persistent Resolver
422
-
423
- - Add beta client wrapper.
424
- - Add deterministic name generator.
425
- - Add `resolvePersistentSandboxHandle()`.
426
- - Add race-safe get/create/get behavior.
427
- - Add tests for stopped status, missing record, conflict, and wrong kind.
428
-
429
- Acceptance:
430
-
431
- - Resolver never stores session id as durable identity.
432
- - `Sandbox.get({ name })` path resumes by default.
433
- - Wrong channel for existing record gives a stable explicit error.
434
-
435
- ### Phase 3 - Persistent Workspace And Exec
436
-
437
- - Add persistent workspace adapter.
438
- - Add command-backed `readdir`, `stat`, `unlink`, `rename`.
439
- - Add persistent exec wrapper.
440
- - Generalize metadata invalidation to support both stable and beta sandbox
441
- object types.
442
- - Wire persistent path into runtime mode dispatcher.
443
-
444
- Acceptance:
445
-
446
- - File tree loads after a sandbox was stopped.
447
- - Read/write/mkdir/rename/delete work after stop/resume.
448
- - Agent command execution resumes stopped sandbox.
449
- - No file operation interpolates user path into shell source.
450
-
451
- ### Phase 4 - Full-App Default And Smoke
452
-
453
- - Set full-app env defaults to persistent beta.
454
- - Add README/env example docs.
455
- - Add an opt-in live smoke script for beta persistence:
456
- - create named sandbox
457
- - write file
458
- - stop
459
- - read file
460
- - list sessions
461
- - delete test sandbox
462
- - Run local full-app against Vercel beta channel.
463
-
464
- Acceptance:
465
-
466
- - `/api/v1/tree` works for a new workspace.
467
- - Stop sandbox in Vercel, reload file tree, it resumes and still sees files.
468
- - DB row stores sandbox name and latest session id.
469
- - Stable channel still works in package tests.
470
-
471
- ### Phase 5 - Cleanup After Beta GA
472
-
473
- - Revisit dependency alias.
474
- - If Vercel stable catches up, remove stable-specific snapshot recreation where
475
- no longer needed.
476
- - Decide whether to rename channel from `persistent-beta` to `persistent`.
477
- - Keep backwards compatibility for old stable records until an explicit cleanup
478
- task removes it.
479
-
480
- ## Test Matrix
481
-
482
- Unit tests:
483
-
484
- - Config channel parsing.
485
- - Name generation is deterministic and provider-safe.
486
- - Store contract discriminates stable vs persistent.
487
- - Persistent resolver get/create/conflict.
488
- - Persistent workspace path validation.
489
- - Persistent `readdir` and `stat` JSON parsing.
490
- - Persistent mutations invalidate caches and mark dirty.
491
- - Persistent exec streams stdout/stderr and handles timeout.
492
-
493
- Integration tests with mocked beta SDK:
494
-
495
- - Stopped sandbox resumes on `readFileToBuffer`.
496
- - Stopped sandbox resumes on `runCommand`.
497
- - Session id changes after resume and store is updated.
498
- - Wrong channel existing record returns stable error.
499
-
500
- Opt-in live smoke:
501
-
502
- - Requires Vercel token/team/project env.
503
- - Creates a unique sandbox name.
504
- - Deletes it at the end.
505
- - Must not run in default CI.
506
-
507
- Full-app smoke:
508
-
509
- - Sign up/login.
510
- - Create workspace.
511
- - Fetch file tree.
512
- - Write file.
513
- - Stop sandbox externally or via test helper.
514
- - Fetch file tree again.
515
- - Assert file remains.
516
-
517
- ## Risks
518
-
519
- - Beta API can change. Mitigation: isolate imports behind
520
- `persistentClient.ts` and use package alias.
521
- - Provider naming constraints are not clearly documented. Mitigation: hashed
522
- lowercase names, live smoke test, explicit error if rejected.
523
- - `snapshot()` stops sessions. Mitigation: disable periodic snapshot scheduler
524
- by default in persistent beta.
525
- - Filetree via `runCommand` may be slower than old `fs.readdir`. Mitigation:
526
- cache metadata and use one Node script that returns exactly what the UI needs.
527
- - Race on first workspace open. Mitigation: create conflict falls back to get.
528
- - Channel switch can strand old stable records. Mitigation: explicit error and
529
- reset path, no silent replacement.
530
-
531
- ## Open Questions
532
-
533
- - Should full-app default to `persistent-beta` immediately, or behind an env
534
- flag until a live smoke passes on the deployment host?
535
- - Do we want a user-visible "restart sandbox" action, or should resume remain
536
- invisible?
537
- - Should workspace reset delete the persistent sandbox remotely, or only detach
538
- it from the DB row? Deletion is destructive and needs explicit UX.
539
- - Should we expose session history/snapshots in workspace settings later?
540
- - Do we seed persistent sandboxes from tarball template only, or also support
541
- importing from a running stable sandbox as a separate migration action?
542
-
543
- ## Done Criteria
544
-
545
- - Full-app uses persistent Vercel sandbox names by default.
546
- - One workspace can survive sandbox stop and reload file tree without losing
547
- files.
548
- - DB shows durable sandbox name plus current session id.
549
- - No silent empty sandbox creation on stable expired handles.
550
- - Stable `vercel-sandbox` package behavior remains tested.
551
- - `pnpm --filter @boring/agent typecheck` passes.
552
- - Relevant agent/core/full-app tests pass.
553
- - Live smoke script passes against Vercel beta and cleans up after itself.
@@ -1,157 +0,0 @@
1
- # Workspace-local runtime provisioning
2
-
3
- Boring UI provisions runtime resources inside the selected workspace. Opening a
4
- workspace through the app shell or `npx @hachej/boring-ui-cli` is intended to be
5
- zero setup: generated skills, package CLIs, SDKs, and starter files appear under
6
- that workspace before the agent runtime is ready.
7
-
8
- ## Generated layout
9
-
10
- All generated state lives under:
11
-
12
- ```text
13
- $BORING_AGENT_WORKSPACE_ROOT/.boring-agent/
14
- ├── .gitignore
15
- ├── skills/ # generated mirror of plugin skills
16
- ├── node/ # npm prefix for runtime Node CLIs
17
- ├── venv/ # Python virtualenv for runtime Python CLIs/SDKs
18
- ├── sdk/ # staged local SDK/package sources (sdk/uv = workspace-local uv)
19
- ├── cache/ # npm/uv/pip caches
20
- └── tmp/ # staged venvs, tarballs, temp files
21
- ```
22
-
23
- Provisioning skip state is tracked via ownership markers and fingerprints stored
24
- alongside managed directories (see `runtimeLayout.ts`), not a separate
25
- `fingerprints/` directory.
26
-
27
- `.boring-agent` is generated and disposable. Do not hand-edit it; delete it to
28
- force a full local reprovision. The directory writes its own `.gitignore` and
29
- should not be committed.
30
-
31
- User-authored skills still belong in `.agents/skills`. Plugin-provided skills
32
- are copied into `.boring-agent/skills/<plugin-id>/<skill-name>` so normal
33
- workspace reads can inspect them. Provisioning never overwrites `.agents/skills`.
34
-
35
- ## When provisioning runs
36
-
37
- Provisioning is synchronous and idempotent:
38
-
39
- - workspace/app load runs provisioning before declaring the agent runtime ready;
40
- - CLI project mode provisions the selected project workspace;
41
- - CLI workspaces/global mode provisions the saved selected workspace, not the
42
- registry directory or home directory;
43
- - `POST /api/v1/agent/reload` reruns provisioning, recopies edited skills,
44
- reseeds missing files, and refreshes prompt/resources;
45
- - runtime installs are skipped when fingerprints and expected outputs still
46
- match.
47
-
48
- Set `provisionWorkspace: false` to opt out. In that mode Boring UI does not write
49
- `.boring-agent` and no package source copies or installs run.
50
-
51
- There is no provisioning job/status/doctor endpoint in this first pass. Failures
52
- surface synchronously with stable `PROVISIONING_*` error codes and structured
53
- logs. Concurrent first-load/reload calls are not locked yet; avoid intentionally
54
- starting multiple provisioning runs for the same workspace at once.
55
-
56
- ## Package authoring shape
57
-
58
- Package metadata uses the existing public namespaces only:
59
-
60
- ```jsonc
61
- {
62
- "name": "@example/macro",
63
- "pi": {
64
- "skills": ["src/server/workspace-template/.agents/skills/macro-transform"],
65
- "systemPrompt": "Use bm for macro transforms.",
66
- "packages": ["npm:pi-web-access"],
67
- "extensions": []
68
- },
69
- "boring": {
70
- "front": "src/front/index.tsx",
71
- "server": "src/server/index.ts"
72
- }
73
- }
74
- ```
75
-
76
- - `pi.skills` are mirrored into `.boring-agent/skills`.
77
- - `pi.systemPrompt`, `pi.packages`, `pi.extensions`, and prompts remain Pi
78
- resources and flow through the Pi loader/prompt builder.
79
- - `boring.front` discovers UI contributions.
80
- - `boring.server` discovers trusted server code.
81
-
82
- There is no third public manifest namespace for provisioning. Future
83
- workspace-installed plugin declarations are deferred and are not first-pass
84
- behavior.
85
-
86
- ## Trusted server provisioning
87
-
88
- Trusted `boring.server` code can declare runtime provisioning for SDKs,
89
- templates, and environment variables:
90
-
91
- ```ts
92
- import { defineServerPlugin } from "@hachej/boring-workspace/server"
93
-
94
- export default defineServerPlugin({
95
- id: "boring-macro",
96
- provisioning: {
97
- python: [{
98
- id: "macro-sdk",
99
- packageName: "boring-macro-sdk",
100
- projectFile: "src/server/sdk/pyproject.toml",
101
- expectedBins: ["bm"],
102
- env: { BORING_MACRO_API_URL: "http://localhost:3000/api/macro" },
103
- }],
104
- templateDirs: [{
105
- id: "macro-template",
106
- path: "src/server/workspace-template",
107
- }],
108
- },
109
- routes(app) {
110
- // Macro HTTP/API integration.
111
- },
112
- agentTools: [
113
- // Only trusted server-stateful tools that need DB/API clients/auth.
114
- ],
115
- })
116
- ```
117
-
118
- After provisioning, a macro-style workspace has:
119
-
120
- ```text
121
- .boring-agent/skills/boring-macro/macro-transform/SKILL.md
122
- .boring-agent/venv/bin/bm
123
- .deck/intro.md # seeded only if missing
124
- transforms/custom/.gitkeep # seeded only if missing
125
- ```
126
-
127
- The harness PATH includes `.boring-agent/venv/bin`, and runtime command env
128
- includes `BORING_MACRO_API_URL`. Existing user files are preserved; templates
129
- copy only missing files.
130
-
131
- Regular portable package/runtime plugins should prefer `pi.extensions` or future
132
- portable sandbox-tool declarations instead of `boring.server.agentTools`.
133
-
134
- ## Runtime-mode consistency
135
-
136
- `direct`, `local`/bwrap, and `vercel-sandbox` use the same provisioning engine and
137
- same runtime layout. Mode adapters own transport details:
138
-
139
- - direct/local can use local package paths directly;
140
- - Vercel-like adapters turn path-invisible local packages into workspace-visible
141
- artifact sources;
142
- - adapters choose cache roots and enforce path-safety for workspace file writes.
143
-
144
- The shared provisioning engine never calls Vercel APIs, `npm pack`, upload APIs,
145
- or cloud credentials directly.
146
-
147
- ## Verification commands
148
-
149
- Useful smoke/unit checks:
150
-
151
- ```bash
152
- pnpm --filter @hachej/boring-ui-cli exec vitest run src/__tests__/runtimeProvisioning.test.ts
153
- pnpm --filter @hachej/boring-workspace exec vitest run src/app/server/__tests__/macroRuntimeProvisioning.test.ts
154
- pnpm --filter @hachej/boring-workspace exec vitest run src/server/__tests__/createWorkspaceAgentServer.test.ts --testNamePattern "runtime provisioning reload"
155
- pnpm --filter @hachej/boring-sandbox exec vitest run src/providers/vercel-sandbox/__tests__/provisioningAdapter.test.ts
156
- pnpm --filter @hachej/boring-agent exec vitest run src/server/workspace/provisioning/__tests__
157
- ```