@hachej/boring-agent 0.1.17 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{sandbox-handle-store-hK76cTjn.d.ts → agentPluginEvents-zyIvVjsA.d.ts} +28 -32
  2. package/dist/{chunk-F3CE5CNW.js → chunk-B5JECXMG.js} +5 -7
  3. package/dist/front/index.d.ts +23 -7
  4. package/dist/front/index.js +764 -421
  5. package/dist/front/styles.css +61 -0
  6. package/dist/{tool-ui-DSmWuqGe.d.ts → harness-DRrTn_5T.d.ts} +43 -24
  7. package/dist/server/index.d.ts +79 -10
  8. package/dist/server/index.js +251 -60
  9. package/dist/shared/index.d.ts +5 -3
  10. package/dist/shared/index.js +3 -1
  11. package/dist/tool-ui-DIFNGwYd.d.ts +20 -0
  12. package/docs/ACCESSIBILITY.md +55 -0
  13. package/docs/API.md +64 -0
  14. package/docs/CSP.md +40 -0
  15. package/docs/ERROR_CODES.md +54 -0
  16. package/docs/KNOWN_LIMITATIONS.md +100 -0
  17. package/docs/MIGRATION.md +50 -0
  18. package/docs/PERFORMANCE.md +68 -0
  19. package/docs/PLUGINS.md +108 -0
  20. package/docs/README.md +17 -0
  21. package/docs/RISKS-MULTI-TAB.md +46 -0
  22. package/docs/STYLING.md +71 -0
  23. package/docs/UI-SHADCN.md +56 -0
  24. package/docs/VERCEL_COSTS.md +103 -0
  25. package/docs/plans/AGENT_EVAL_FRAMEWORK.md +466 -0
  26. package/docs/plans/agent-package-spec.md +1777 -0
  27. package/docs/plans/harness-followup-capabilities.md +440 -0
  28. package/docs/plans/harness-tool-ui-capabilities.md +144 -0
  29. package/docs/plans/pi-followup-history-projection.md +229 -0
  30. package/docs/plans/pi-tools-migration.md +1061 -0
  31. package/docs/plans/reviews/pi-followup-history-codex-review.md +11 -0
  32. package/docs/plans/reviews/pi-followup-history-gpt-review.md +64 -0
  33. package/docs/plans/reviews/pi-followup-history-opus-review.md +43 -0
  34. package/docs/plans/reviews/pi-followup-history-xai-review.md +43 -0
  35. package/docs/plans/vercel-base-snapshot-template-plan.md +527 -0
  36. package/docs/plans/vercel-persistent-sandbox-adapter.md +553 -0
  37. package/docs/runtime.md +56 -0
  38. package/docs/tools.md +75 -0
  39. package/package.json +4 -3
@@ -9,9 +9,10 @@ import {
9
9
  import {
10
10
  DEFAULT_AGENT_RUNTIME_CAPABILITIES,
11
11
  PI_AGENT_RUNTIME_CAPABILITIES,
12
+ WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT,
12
13
  extractToolUiMetadata,
13
14
  isToolUiMetadata
14
- } from "../chunk-F3CE5CNW.js";
15
+ } from "../chunk-B5JECXMG.js";
15
16
 
16
17
  // src/shared/config-schema.ts
17
18
  import { z } from "zod";
@@ -77,6 +78,7 @@ export {
77
78
  ErrorLogFieldsSchema,
78
79
  PI_AGENT_RUNTIME_CAPABILITIES,
79
80
  RuntimeModeSchema,
81
+ WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT,
80
82
  extractToolUiMetadata,
81
83
  isToolUiMetadata,
82
84
  validateConfig,
@@ -0,0 +1,20 @@
1
+ interface ToolUiMetadata {
2
+ /** Renderer key registered by the app shell, e.g. `pi-subagent`. */
3
+ rendererId?: string;
4
+ /** Optional grouping label for future tool timelines/summaries. */
5
+ displayGroup?: string;
6
+ /** Optional icon hint. Frontends decide whether/how to honor it. */
7
+ icon?: string;
8
+ /** Renderer-specific structured data. */
9
+ details?: unknown;
10
+ }
11
+ declare function isToolUiMetadata(value: unknown): value is ToolUiMetadata;
12
+ /**
13
+ * Extract structured UI metadata from a ToolResult-like output.
14
+ *
15
+ * Shape:
16
+ * output.details.ui = { rendererId, displayGroup, icon, details }
17
+ */
18
+ declare function extractToolUiMetadata(output: unknown): ToolUiMetadata | undefined;
19
+
20
+ export { type ToolUiMetadata as T, extractToolUiMetadata as e, isToolUiMetadata as i };
@@ -0,0 +1,55 @@
1
+ # Accessibility Audit
2
+
3
+ This document tracks accessibility status for `@boring/agent` UI surfaces.
4
+
5
+ ## Scope (boring-ui-v2-cop)
6
+
7
+ - `packages/agent/src/front/ChatPanel.tsx`
8
+ - `packages/agent/src/front-shadcn/ChatPanel.tsx`
9
+ - `packages/agent/src/front-shadcn/primitives/*` (chat primitives)
10
+ - CI browser check for the default agent app (`packages/agent/app`)
11
+
12
+ ## Current Coverage
13
+
14
+ ### Landmarks and live regions
15
+
16
+ - ChatPanel roots now expose `role="region"` with
17
+ `aria-label="Agent assistant"` in both front variants.
18
+ - Message streams expose `role="log"` and `aria-live="polite"` as
19
+ `aria-label="Agent conversation"`.
20
+ - Error states are announced with `role="alert"` in both variants.
21
+
22
+ ### Interactive control names
23
+
24
+ - Icon-only conversation controls expose explicit names:
25
+ - `Scroll to latest message`
26
+ - `Download conversation`
27
+
28
+ ### Automated checks
29
+
30
+ - Unit checks:
31
+ - `src/front/__tests__/ChatPanel.test.tsx`
32
+ - `src/front-shadcn/__tests__/ChatPanel.test.tsx`
33
+ - Browser axe check:
34
+ - `packages/agent/e2e/a11y.spec.ts`
35
+ - Fails on any **serious** or **critical** violation inside
36
+ `[data-boring-agent]`.
37
+
38
+ This runs in CI automatically through the existing `pnpm e2e` job.
39
+
40
+ ## Known Gaps
41
+
42
+ 1. Storybook a11y pipeline is not present yet.
43
+ There is no `.storybook` config, no `@storybook/addon-a11y`, and no
44
+ story-level axe scan.
45
+ 2. Current automated axe gate targets the shipped chat surface only.
46
+ Workspace pane-level a11y checks (file tree, editor panes, dock tabs) are
47
+ outside this package and need dedicated coverage in `@boring/workspace`.
48
+ 3. Manual screen-reader validation (VoiceOver/NVDA) is still pending.
49
+
50
+ ## Next Increment
51
+
52
+ 1. Stand up Storybook for `@boring/agent` primitives.
53
+ 2. Enable addon-a11y and add story-level CI checks.
54
+ 3. Extend browser axe checks to `workspace-playground` once pane a11y owners
55
+ land stable selectors/landmarks.
package/docs/API.md ADDED
@@ -0,0 +1,64 @@
1
+ # API
2
+
3
+ This file documents the package API as it exists today in the v2 scaffold.
4
+ Roadmap-only APIs are explicitly marked.
5
+
6
+ ## Entry Points
7
+
8
+ `@boring/agent` currently maps to the frontend entry (`dist/front.js`).
9
+
10
+ Additional subpath exports:
11
+
12
+ - `@boring/agent/front`
13
+ - `@boring/agent/shared`
14
+ - `@boring/agent/server`
15
+
16
+ ## Current Runtime Surface
17
+
18
+ ### `@boring/agent/server`
19
+
20
+ Currently exported:
21
+
22
+ - `createDirectSandbox` from `src/server/sandbox/direct/createDirectSandbox.ts`
23
+
24
+ Notes:
25
+
26
+ - This is the only runtime mode implementation currently available.
27
+ - Local `bwrap` and remote `vercel-sandbox` adapters are planned.
28
+
29
+ ### `@boring/agent/shared`
30
+
31
+ Current contract files:
32
+
33
+ - `harness.ts` — `AgentHarness`, streaming event contracts.
34
+ - `tool.ts` — `AgentTool`, `ToolExecContext`, `ToolResult`.
35
+ - `workspace.ts` — platform-agnostic filesystem contract.
36
+ - `sandbox.ts` — command execution contract.
37
+ - `session.ts` — `SessionStore` and session models.
38
+ - `ui-bridge.ts` — `UiBridge`, `UiCommand`, `CommandResult`.
39
+ - `catalog.ts`, `file-search.ts`, `message.ts`, `sandbox-handle-store.ts`.
40
+
41
+ These files are the source of truth for TypeScript interfaces while higher-level
42
+ helpers are still being implemented.
43
+
44
+ ## Shipped High-Level API
45
+
46
+ - `createAgentApp(config)` — standalone Fastify factory. Zero dependency on `@boring/core`. Powers `npx @boring/agent`. See `packages/agent/src/server/createAgentApp.ts`.
47
+ - `ChatPanel` — React component exported from `@boring/agent/front` (and top-level barrel). See `packages/agent/src/front/index.ts:2`.
48
+ - `useAgentChat(...)` — React hook, same export surface.
49
+
50
+ ## Planned High-Level API (Not Implemented Yet)
51
+
52
+ - `registerAgentRoutes(app, opts)` — new Fastify plugin export for embedding into a core-built server. Lands in agent M4 alongside core's integration milestone. Paths are absolute (`/api/v1/agent/*`); no `prefix` option.
53
+
54
+ Track implementation progress in `docs/plans/agent-package-spec.md` and beads.
55
+
56
+ ## Deferred HTTP Git Surface (v1.x)
57
+
58
+ `/api/v1/git/*` routes are intentionally not part of agent v1.
59
+
60
+ - Reason: there is no first-party git UI consumer in agent/workspace v1, so
61
+ these routes would be dead code.
62
+ - Current behavior: use the `bash` tool for git commands.
63
+ - Activation point: when git UI returns (status/diff/badges), add the routes as
64
+ thin wrappers over sandbox git execution.
package/docs/CSP.md ADDED
@@ -0,0 +1,40 @@
1
+ # CSP Compatibility
2
+
3
+ `@boring/agent` is compatible with a strict CSP, with one required exception:
4
+ `style-src` must allow inline styles for editor/runtime integrations that do
5
+ not support nonces end-to-end.
6
+
7
+ ## Recommended Policy
8
+
9
+ ```http
10
+ Content-Security-Policy:
11
+ default-src 'self';
12
+ script-src 'self';
13
+ style-src 'self' 'unsafe-inline';
14
+ connect-src 'self';
15
+ img-src 'self' data: blob:;
16
+ font-src 'self';
17
+ ```
18
+
19
+ ## Notes
20
+
21
+ - `script-src` does **not** require `'unsafe-eval'`.
22
+ - No `eval()` or `new Function()` usage is expected in audited agent UI surfaces
23
+ (shadcn ChatPanel + example apps).
24
+ - Shared helper lives at `src/server/http/csp.ts`; example servers consume it
25
+ via `examples/csp.ts`.
26
+
27
+ ## Audited Scope (tv4)
28
+
29
+ - `src/front/ChatPanel.tsx`
30
+ - `examples/with-custom-tool/*`
31
+ - `apps/agent-playground/*`
32
+
33
+ ## Remaining Caveat
34
+
35
+ Even with this cleanup, downstream consumers that enable rich editor stacks
36
+ (for example CodeMirror/Tiptap integrations in workspace-oriented shells) should
37
+ keep `style-src 'unsafe-inline'` until upstream libraries expose nonce-safe
38
+ style injection across the full stack. The default non-shadcn primitive
39
+ stack (`src/front/primitives/*`) also still relies on inline style attributes,
40
+ so `style-src 'unsafe-inline'` remains required there as well.
@@ -0,0 +1,54 @@
1
+ # ERROR_CODES
2
+
3
+ Canonical registry for stable `@boring/agent` error codes.
4
+
5
+ All API failures must use the response envelope:
6
+
7
+ ```json
8
+ {
9
+ "error": {
10
+ "code": "PATH_ESCAPE",
11
+ "message": "Path '../secrets' escapes workspace root",
12
+ "details": {
13
+ "path": "../secrets",
14
+ "workspaceRoot": "/tmp/ws"
15
+ }
16
+ }
17
+ }
18
+ ```
19
+
20
+ ## Registry
21
+
22
+ | Code | When it fires | HTTP status | Suggested client action | Log level | Stability |
23
+ | --- | --- | --- | --- | --- | --- |
24
+ | `MISSING_API_KEY` | Required provider API key missing from runtime config | 500 | report-bug | error | stable (public API) |
25
+ | `INVALID_API_KEY` | Provider rejects API key as malformed/invalid | 401 | re-auth | warn | stable (public API) |
26
+ | `OIDC_REFRESH_FAILED` | OIDC refresh token exchange fails | 401 | re-auth | warn | stable (public API) |
27
+ | `VERCEL_AUTH_FAILED` | Vercel sandbox auth/token request fails | 401 | re-auth | warn | stable (public API) |
28
+ | `CONFIG_INVALID` | Runtime config fails schema validation | 500 | report-bug | error | stable (public API) |
29
+ | `PATH_ESCAPE` | Relative path escapes workspace root | 403 | user-fix | warn | stable (public API) |
30
+ | `PATH_ABSOLUTE` | Absolute path rejected where relative path is required | 400 | user-fix | warn | stable (public API) |
31
+ | `PATH_NULL_BYTE` | Path contains a null byte | 400 | user-fix | warn | stable (public API) |
32
+ | `PATH_SYMLINK_ESCAPE` | Realpath resolves outside workspace root | 403 | user-fix | warn | stable (public API) |
33
+ | `PATH_NOT_FOUND` | Read/stat/load targets missing path | 404 | user-fix | warn | stable (public API) |
34
+ | `PATH_NOT_WRITABLE` | Path parent missing or write denied | 403 | user-fix | warn | stable (public API) |
35
+ | `WORKSPACE_UNINITIALIZED` | Workspace adapter/store not initialized yet | 503 | retry | warn | stable (public API) |
36
+ | `BWRAP_UNAVAILABLE` | `bwrap` binary not found | 500 | report-bug | error | stable (public API) |
37
+ | `BWRAP_TIMEOUT` | Sandbox command exceeded timeout | 408 | retry | warn | stable (public API) |
38
+ | `OUTPUT_TRUNCATED` | Max output bytes reached; output was clipped | 200 | user-fix | warn | stable (public API) |
39
+ | `SANDBOX_NOT_READY` | Remote sandbox cold start / provisioning | 503 | retry | warn | stable (public API) |
40
+ | `SANDBOX_EXPIRED` | Remote sandbox TTL elapsed | 410 | retry | warn | stable (public API) |
41
+ | `VERCEL_API_ERROR` | Generic upstream Vercel SDK/API failure | 502 | retry | error | stable (public API) |
42
+ | `CIRCUIT_OPEN` | Circuit breaker open; request fast-failed | 503 | retry | warn | stable (public API) |
43
+ | `ABORTED` | Request cancelled via `AbortSignal` | 499 | retry | warn | stable (public API) |
44
+ | `SESSION_NOT_FOUND` | Session id does not exist | 404 | user-fix | warn | stable (public API) |
45
+ | `SESSION_LOCKED` | Session currently locked by concurrent writer | 409 | retry | warn | stable (public API) |
46
+ | `STREAM_BUFFER_EVICTED` | Resume cursor evicted from in-memory stream buffer | 410 | retry | warn | stable (public API) |
47
+ | `CURSOR_OUT_OF_RANGE` | Resume cursor invalid/out of range | 416 | user-fix | warn | stable (public API) |
48
+ | `BRIDGE_COMMAND_INVALID` | UI bridge command kind/params invalid | 400 | user-fix | warn | stable (public API) |
49
+ | `TOOL_NOT_FOUND` | Requested tool name not present in catalog | 404 | user-fix | warn | stable (public API) |
50
+ | `TOOL_INVALID_INPUT` | Tool input fails schema validation | 400 | user-fix | warn | stable (public API) |
51
+ | `TOOL_EXECUTION_ERROR` | Tool threw or returned execution failure | 500 | report-bug | error | stable (public API) |
52
+ | `PLUGIN_LOAD_FAILED` | Plugin failed to load/register | 500 | report-bug | error | stable (public API) |
53
+ | `PLUGIN_NAME_COLLISION` | Plugin name collides with existing tool/plugin | 409 | user-fix | warn | stable (public API) |
54
+ | `INTERNAL_ERROR` | Catch-all internal failure | 500 | report-bug | error | internal (may change) |
@@ -0,0 +1,100 @@
1
+ # Known Limitations
2
+
3
+ ## Abandoned Vercel sandboxes (xzr)
4
+
5
+ `@boring/agent` v1 intentionally does **not** implement a session-close release hook.
6
+ If a process crashes or a client disappears before cleanup, the sandbox may remain
7
+ alive until Vercel timeout/TTL.
8
+
9
+ ### Failure modes that can orphan a sandbox
10
+
11
+ - Backend crash or `kill -9` before cleanup path runs.
12
+ - Cross-process/session abort where the owning process never executes `stop()`.
13
+ - Browser tab close/network drop where `beforeunload` is unreliable.
14
+ - Multi-process deployments where one worker creates sandboxes and a different
15
+ worker handles later traffic.
16
+
17
+ ### Cost exposure (from `4vl` model)
18
+
19
+ See [VERCEL_COSTS.md](./VERCEL_COSTS.md) for full assumptions.
20
+
21
+ | Policy / behavior | Estimated cost per workspace per day |
22
+ |---|---:|
23
+ | Stop on session end | `$0.0822` |
24
+ | Idle-stop after 60 min | `$0.1246` |
25
+ | No explicit stop (5h timeout) | `$0.4638` |
26
+ | Pinned 24h workspace | `$2.0750` |
27
+
28
+ Implication: memory wall-clock billing dominates. Orphans are tolerable at low
29
+ volume but become expensive if idle lifetimes grow.
30
+
31
+ ### v1 monitoring + operations guidance
32
+
33
+ - Set Vercel spend/budget alerts on the team account (monthly threshold chosen
34
+ by owner; start low and tighten with observed traffic).
35
+ - Use `[sandbox]` logs for daily create/stop drift checks.
36
+ - Create log: `"[sandbox] created"` with `workspaceId`, `sandboxId`,
37
+ `estimatedAbandonedSessionCostUsd`.
38
+ - Stop log (orphan guard): `"[sandbox] stopped"` with
39
+ `reason: "orphan-guard-idle"`.
40
+ - Compare create vs stop counts in logs:
41
+
42
+ ```bash
43
+ grep -c "\\[sandbox\\] created" /var/log/boring-agent.log
44
+ grep -c "\\[sandbox\\] stopped" /var/log/boring-agent.log
45
+ ```
46
+
47
+ - Manual cleanup workflow:
48
+
49
+ ```bash
50
+ vercel sandbox list --team <team-id>
51
+ vercel sandbox stop <sandbox-id> --team <team-id>
52
+ ```
53
+
54
+ ### Trigger to build full mitigation
55
+
56
+ Implement release-on-close lifecycle when either trigger is hit:
57
+
58
+ 1. First user reports unexpected Vercel bill attributable to orphaned sandboxes.
59
+ 2. Monitoring shows `> 10` apparently abandoned sandboxes at peak.
60
+
61
+ ### Pre-designed mitigation path (deferred)
62
+
63
+ 1. Browser `beforeunload` best-effort call to release endpoint.
64
+ 2. Backend idle timer (`~10 min`) that calls `sandbox.stop()` when no traffic.
65
+ 3. Explicit CLI/server flag for forced idle-stop policy.
66
+
67
+ v1 status: **accepted risk**, documented and monitored.
68
+
69
+ ## GitHub Connect + `/api/v1/git/*` deferred to v1.x (nfx)
70
+
71
+ Git HTTP routes are intentionally not shipped in v1. Both `@boring/agent` v1
72
+ and `@boring/workspace` v1 dropped git UI consumers, so `/api/v1/git/*` would
73
+ be dead code today.
74
+
75
+ ### Current v1 behavior
76
+
77
+ - Git operations run through the existing `bash` tool (`git status`, `git add`,
78
+ `git commit`, etc.).
79
+ - No GitHub Connect token flow is wired into agent HTTP routes yet.
80
+
81
+ ### Why this is deferred
82
+
83
+ - No first-party UI consumer exists yet (status bar, diff pane, git badges).
84
+ - The design is non-trivial enough to avoid shipping unused backend surface.
85
+ - Deferring keeps v1 smaller while preserving a clear activation path.
86
+
87
+ ### Planned migration path once a consumer lands
88
+
89
+ 1. Add `/api/v1/git/*` thin wrappers over `sandbox.exec('git ...')` with output
90
+ parsing.
91
+ 2. Add GitHub Connect credential injection for remote mode
92
+ (`https://x-access-token:$TOKEN@github.com/...`).
93
+ 3. Emit `data-git-changed` SSE invalidation events after write operations.
94
+ 4. Seed git config (`user.name`, `user.email`) when a sandbox workspace is
95
+ created.
96
+
97
+ ### Trigger to implement
98
+
99
+ Ship when any UI consumer lands (agent v1.x git status/diff UI, or workspace
100
+ reintroducing git badges/panels).
@@ -0,0 +1,50 @@
1
+ # MIGRATION
2
+
3
+ Migration notes for moving from legacy monorepo-integrated agent flows to
4
+ `@boring/agent` in v2.
5
+
6
+ ## What Changes
7
+
8
+ v1-style app code typically mixed these concerns in one place:
9
+
10
+ - Chat UI
11
+ - Tool catalog wiring
12
+ - Filesystem behavior
13
+ - Sandbox/process execution
14
+
15
+ In v2, split responsibilities are intentional:
16
+
17
+ - `@boring/agent`: runtime contracts + tool/harness/sandbox layers.
18
+ - `@boring/workspace`: IDE-style layout package (frontend-only).
19
+ - App shell: final composition, mode selection, and product policy.
20
+
21
+ ## Practical Migration Steps
22
+
23
+ 1. Move chat/runtime logic into `@boring/agent` integration points.
24
+ 2. Keep editor/file-tree/layout code in `@boring/workspace` or your own UI.
25
+ 3. Route all filesystem and command execution through runtime mode adapters.
26
+ 4. Replace ad-hoc shared types with imports from `@boring/agent/shared`.
27
+ 5. Keep server-only code in server entry points; avoid server imports in
28
+ frontend/shared paths.
29
+
30
+ ## Runtime Mode Migration
31
+
32
+ Current scaffold status:
33
+
34
+ - `direct` mode is available.
35
+ - `local` (`bwrap`) and `vercel-sandbox` are planned.
36
+
37
+ If you are migrating today, target `direct` first and keep your integration
38
+ surface mode-agnostic so later adapter swaps are non-breaking.
39
+
40
+ ## Contract Hygiene Checklist
41
+
42
+ - No `node:*` imports in `src/shared/**`.
43
+ - No `Buffer` in shared contracts (`Uint8Array` only).
44
+ - Keep UI commands flowing through `UiBridge.postCommand`.
45
+ - Keep session/tool/workspace behavior aligned with `@boring/agent/shared`.
46
+
47
+ ## References
48
+
49
+ - Agent spec + migration design rationale: `docs/plans/agent-package-spec.md`
50
+ - README quickstart/context: `../README.md`
@@ -0,0 +1,68 @@
1
+ # Performance
2
+
3
+ ## Vercel Sandbox cold-start benchmark (2026-04-23)
4
+
5
+ > **Note:** The benchmark harness script has been removed from the repository. The results below are preserved for historical reference.
6
+
7
+ ### Method
8
+
9
+ - Region: `iad1` (Vercel default for this team/project).
10
+ - Runtime: `python3.13`.
11
+ - Iterations: `10` per scenario.
12
+ - Readiness metric: elapsed time from `Sandbox.create(...)` start to first successful `runCommand('echo hi')`.
13
+ - Scenarios:
14
+ - `source: empty`
15
+ - `source: tarball` (small npm tarball URL)
16
+ - `source: snapshot` (pre-baked snapshot)
17
+
18
+ Command used:
19
+
20
+ ```bash
21
+ pnpm --filter @boring/agent run bench:vercel-cold-start -- \
22
+ --iterations 10 \
23
+ --runtime python3.13 \
24
+ --snapshot-id <snapshot-id>
25
+ ```
26
+
27
+ ### Results (clean sample set)
28
+
29
+ The clean rollup uses `empty` + `tarball` from the full run and `snapshot`
30
+ from a dedicated snapshot-only rerun, because the first full run hit a Vercel
31
+ creation API throttle (`429`, 10-minute retry-after) on snapshot iteration 1.
32
+
33
+ #### Time to ready (`create` + first command)
34
+
35
+ | Scenario | p50 | p95 | p99 |
36
+ |---|---:|---:|---:|
37
+ | source: empty | 398 ms | 680 ms | 680 ms |
38
+ | source: tarball | 715 ms | 880 ms | 880 ms |
39
+ | source: snapshot | 1.81 s | 2.19 s | 2.19 s |
40
+
41
+ #### `Sandbox.create(...)` only
42
+
43
+ | Scenario | p50 | p95 | p99 |
44
+ |---|---:|---:|---:|
45
+ | source: empty | 216 ms | 499 ms | 499 ms |
46
+ | source: tarball | 542 ms | 696 ms | 696 ms |
47
+ | source: snapshot | 200 ms | 357 ms | 357 ms |
48
+
49
+ ### Conclusion
50
+
51
+ - Snapshot creation itself is faster than tarball creation (`create` p50: 200 ms
52
+ vs 542 ms), but end-to-end readiness is slower in this run (`ready` p50:
53
+ 1.81 s vs 715 ms), so the expected “snapshot is 10x faster” effect was not
54
+ observed.
55
+ - Against the UX budget rule for cold start (`p95 > 3s => async polling UI`):
56
+ measured cold-start p95 values are all `< 3s`, so a standard spinner is
57
+ sufficient for nominal startup latency.
58
+ - Independent of cold-start latency, the platform creation rate limit can add
59
+ multi-minute waits (`429` with `Retry-After: 600`), so the UI should still
60
+ handle long provisioning states gracefully when API throttling occurs.
61
+
62
+ ### Follow-up
63
+
64
+ - Re-run this benchmark on the recommended three windows (morning, afternoon,
65
+ evening UTC) to check diurnal variance and confirm whether snapshot readiness
66
+ remains slower than tarball readiness in steady state.
67
+ - If this pattern persists, revisit the M2 vercel-sandbox design assumption
68
+ that snapshot boot is the fastest production path for first-command latency.
@@ -0,0 +1,108 @@
1
+ # PLUGINS
2
+
3
+ Plugin and extension notes for `@boring/agent`.
4
+
5
+ ## Current Status
6
+
7
+ The full plugin runtime is not shipped yet in this scaffold.
8
+
9
+ Today:
10
+
11
+ - Tool contracts are defined in `src/shared/tool.ts`.
12
+ - Runtime catalog/harness integration is still under active implementation.
13
+
14
+ ## Planned Integration Model
15
+
16
+ The intended extension seam is "register additional tools into the catalog"
17
+ without modifying core runtime internals.
18
+
19
+ Each tool should provide:
20
+
21
+ - Stable `name`
22
+ - Clear `description`
23
+ - JSON-schema `parameters`
24
+ - Async `execute(params, ctx)` implementation
25
+
26
+ `ctx` is expected to carry:
27
+
28
+ - `abortSignal`
29
+ - `toolCallId`
30
+ - Optional progress callbacks (`onUpdate`)
31
+
32
+ ## Safety + Compatibility Rules
33
+
34
+ - Validate all plugin input against schema before execution.
35
+ - Return deterministic, serializable output structures for reliable rendering.
36
+ - Keep plugin-side filesystem and process behavior behind the selected runtime
37
+ mode adapter (do not bypass the agent runtime boundary).
38
+ - Avoid `node:*` imports in shared/frontend code paths.
39
+
40
+ ## Tool Name Collisions
41
+
42
+ Collision precedence is explicit: last-registered wins.
43
+
44
+ - Built-in catalog tools are registered first.
45
+ - App `extraTools` are registered next.
46
+ - Plugin tools are registered last.
47
+
48
+ If a plugin reuses an existing tool name (for example `bash`), the plugin tool
49
+ replaces the earlier one and a warning is logged:
50
+
51
+ `[catalog] Tool "bash" overridden by plugin <name>`
52
+
53
+ ### Plugin vs Plugin collisions
54
+
55
+ If two plugins register the same tool name:
56
+
57
+ - the later plugin registration wins
58
+ - a warning is logged for the override
59
+ - startup does **not** fail; no hard error is thrown
60
+
61
+ The rule is implemented in `mergeTools` and is applied uniformly whether the
62
+ previous tool came from built-ins, `extraTools`, or another plugin.
63
+
64
+ ## Discovery Sources (direct/local)
65
+
66
+ In `direct` and `local` mode, plugin discovery can load tools from:
67
+
68
+ - `~/.pi/agent/extensions/*.js|*.mjs` (global)
69
+ - `<workspace>/.pi/extensions/*.js|*.mjs` (local)
70
+ - `<workspace>/node_modules/pi-plugin-*` packages
71
+ - `<workspace>/.pi/extensions.json` `npm` entries (if installed in `node_modules`)
72
+
73
+ ## `vercel-sandbox` Mode Caveat
74
+
75
+ `createAgentApp` disables automatic plugin loading when runtime mode is
76
+ `vercel-sandbox`. This is intentional: plugin files are discovered via host
77
+ Node filesystem access, while sandbox workspace/exec are remote.
78
+
79
+ Practical effect:
80
+
81
+ - Plugin auto-discovery is enabled in `direct` and `local`.
82
+ - Plugin auto-discovery is disabled in `vercel-sandbox`.
83
+
84
+ ## Manual Packaging Pattern For `vercel-sandbox`
85
+
86
+ If you still need extension-like behavior in `vercel-sandbox`, package files
87
+ manually into the workspace image/template used to create the sandbox:
88
+
89
+ - include `.pi/extensions/*` in your source template
90
+ - ensure files exist at `/vercel/sandbox/.pi/extensions/` in the remote workspace
91
+ - load/register tools through your app-shell bootstrap (not package auto-discovery)
92
+
93
+ Use the same template-seeding concept as local provisioning (for example
94
+ `templatePath`/`BORING_AGENT_TEMPLATE_PATH` workflows), but apply it when you
95
+ build the sandbox source/snapshot consumed by Vercel.
96
+
97
+ ## Environment Differences
98
+
99
+ When porting plugins from host modes to `vercel-sandbox`, expect differences:
100
+
101
+ - available binaries and OS tools can differ from host machine
102
+ - root/path assumptions differ (`/vercel/sandbox` workspace root)
103
+ - process/network policy can differ from local development
104
+
105
+ ## References
106
+
107
+ - Canonical design: `docs/plans/agent-package-spec.md`
108
+ - Minimal integration sketch: `examples/with-custom-tool/README.md`
package/docs/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @boring/agent Docs
2
+
3
+ This folder contains user-facing package docs for `@boring/agent`.
4
+
5
+ ## Start Here
6
+
7
+ - [API](./API.md) — current export surface and contract status.
8
+ - [STYLING](./STYLING.md) — CSS-variable theming contract and guardrails.
9
+ - [PLUGINS](./PLUGINS.md) — extension model and runtime-mode caveats.
10
+ - [MIGRATION](./MIGRATION.md) — migration guidance for v1-style integrations.
11
+
12
+ ## Scope
13
+
14
+ These docs track the implementation status of the v2 scaffold and call out
15
+ roadmap-only APIs explicitly to avoid over-promising.
16
+
17
+ For canonical design context, see `./plans/agent-package-spec.md`.
@@ -0,0 +1,46 @@
1
+ # Multi-Tab Concurrency Risk
2
+
3
+ ## Scope
4
+
5
+ This note documents current `@boring/agent` behavior when two browser tabs
6
+ point at the same workspace and edit the same file.
7
+
8
+ ## Observed Behavior (v1)
9
+
10
+ - There is no workspace-level lock and no per-file lock.
11
+ - `POST /api/v1/files` performs an unconditional write.
12
+ - There is no optimistic concurrency precondition (no ETag/version check,
13
+ no `If-Match` handling).
14
+ - Result: writes are silent last-write-wins. A stale tab can overwrite a newer
15
+ edit without a conflict response.
16
+
17
+ This is visible in route code:
18
+ - [file.ts](../src/server/http/routes/file.ts) calls `workspace.writeFile(...)`
19
+ directly.
20
+ - [createNodeWorkspace.ts](../src/server/workspace/createNodeWorkspace.ts)
21
+ calls Node `writeFile(...)` without revision guards.
22
+
23
+ Behavior is exercised by:
24
+ - [file.test.ts](../src/server/http/routes/__tests__/file.test.ts)
25
+ `multi-tab stale write overwrites newer content (last write wins)`.
26
+
27
+ ## Session Semantics
28
+
29
+ - Tabs are not treated as exclusive owners of a workspace.
30
+ - Session identity is driven by `sessionId`; multiple tabs can operate against
31
+ the same workspace concurrently.
32
+
33
+ ## Known User Impact
34
+
35
+ - Lost-update risk for simultaneous edits on the same path.
36
+ - No explicit error or merge prompt when overwrite happens.
37
+
38
+ ## Future Mitigation Seam
39
+
40
+ Introduce optimistic write preconditions:
41
+
42
+ 1. `GET /api/v1/files` returns a version token (for example ETag/content hash +
43
+ mtime).
44
+ 2. `POST /api/v1/files` accepts `If-Match` and rejects mismatched versions with
45
+ `409 Conflict`.
46
+ 3. Frontend handles `409` with refresh/retry UX instead of silent overwrite.