@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.
- package/dist/{sandbox-handle-store-hK76cTjn.d.ts → agentPluginEvents-zyIvVjsA.d.ts} +28 -32
- package/dist/{chunk-F3CE5CNW.js → chunk-B5JECXMG.js} +5 -7
- package/dist/front/index.d.ts +23 -7
- package/dist/front/index.js +764 -421
- package/dist/front/styles.css +61 -0
- package/dist/{tool-ui-DSmWuqGe.d.ts → harness-DRrTn_5T.d.ts} +43 -24
- package/dist/server/index.d.ts +79 -10
- package/dist/server/index.js +251 -60
- package/dist/shared/index.d.ts +5 -3
- package/dist/shared/index.js +3 -1
- package/dist/tool-ui-DIFNGwYd.d.ts +20 -0
- package/docs/ACCESSIBILITY.md +55 -0
- package/docs/API.md +64 -0
- package/docs/CSP.md +40 -0
- package/docs/ERROR_CODES.md +54 -0
- package/docs/KNOWN_LIMITATIONS.md +100 -0
- package/docs/MIGRATION.md +50 -0
- package/docs/PERFORMANCE.md +68 -0
- package/docs/PLUGINS.md +108 -0
- package/docs/README.md +17 -0
- package/docs/RISKS-MULTI-TAB.md +46 -0
- package/docs/STYLING.md +71 -0
- package/docs/UI-SHADCN.md +56 -0
- package/docs/VERCEL_COSTS.md +103 -0
- package/docs/plans/AGENT_EVAL_FRAMEWORK.md +466 -0
- package/docs/plans/agent-package-spec.md +1777 -0
- package/docs/plans/harness-followup-capabilities.md +440 -0
- package/docs/plans/harness-tool-ui-capabilities.md +144 -0
- package/docs/plans/pi-followup-history-projection.md +229 -0
- package/docs/plans/pi-tools-migration.md +1061 -0
- package/docs/plans/reviews/pi-followup-history-codex-review.md +11 -0
- package/docs/plans/reviews/pi-followup-history-gpt-review.md +64 -0
- package/docs/plans/reviews/pi-followup-history-opus-review.md +43 -0
- package/docs/plans/reviews/pi-followup-history-xai-review.md +43 -0
- package/docs/plans/vercel-base-snapshot-template-plan.md +527 -0
- package/docs/plans/vercel-persistent-sandbox-adapter.md +553 -0
- package/docs/runtime.md +56 -0
- package/docs/tools.md +75 -0
- package/package.json +4 -3
package/dist/shared/index.js
CHANGED
|
@@ -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-
|
|
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.
|
package/docs/PLUGINS.md
ADDED
|
@@ -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.
|