@hachej/boring-agent 0.1.111 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +171 -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,229 +0,0 @@
1
- # ERROR_CODES
2
-
3
- Canonical registry for stable `@hachej/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
- | `UNAUTHORIZED` | Request reached a protected path (e.g. credit metering) without an authenticated user | 401 | re-auth | warn | stable (public API) |
25
- | `MISSING_API_KEY` | Required provider API key missing from runtime config | 500 | report-bug | error | stable (public API) |
26
- | `INVALID_API_KEY` | Provider rejects API key as malformed/invalid | 401 | re-auth | warn | stable (public API) |
27
- | `OIDC_REFRESH_FAILED` | OIDC refresh token exchange fails | 401 | re-auth | warn | stable (public API) |
28
- | `VERCEL_AUTH_FAILED` | Vercel sandbox auth/token request fails | 401 | re-auth | warn | stable (public API) |
29
- | `BLAXEL_AUTH_FAILED` | Blaxel sandbox auth/workspace request fails | 401 | re-auth | warn | stable (public API) |
30
- | `CONFIG_INVALID` | Runtime config fails schema validation | 500 | report-bug | error | stable (public API) |
31
- | `PATH_ESCAPE` | Relative path escapes workspace root | 403 | user-fix | warn | stable (public API) |
32
- | `PATH_ABSOLUTE` | Absolute path rejected where relative path is required | 400 | user-fix | warn | stable (public API) |
33
- | `PATH_NULL_BYTE` | Path contains a null byte | 400 | user-fix | warn | stable (public API) |
34
- | `PATH_SYMLINK_ESCAPE` | Realpath resolves outside workspace root | 403 | user-fix | warn | stable (public API) |
35
- | `PATH_NOT_FOUND` | Read/stat/load targets missing path | 404 | user-fix | warn | stable (public API) |
36
- | `PATH_NOT_WRITABLE` | Path parent missing or write denied | 403 | user-fix | warn | stable (public API) |
37
- | `WORKSPACE_UNINITIALIZED` | Workspace adapter/store not initialized yet | 503 | retry | warn | stable (public API) |
38
- | `WORKSPACE_NOT_READY` | Workspace substrate (`workspace-fs`, `sandbox-exec`, or `ui-bridge`) is still preparing | 503 | retry | warn | stable (public API) |
39
- | `AGENT_HOST_SCOPE_VIOLATION` | Presented workspace selector conflicts with the trusted dedicated-host request scope | 421 | user-fix | warn | stable (public API) |
40
- | `AGENT_RUNTIME_NOT_READY` | Selected workspace runtime dependencies (`runtime-dependencies` or `runtime:<name>`, e.g. `runtime:python`/`runtime:node`) are still preparing | 503 | retry | warn | stable (public API) |
41
- | `AGENT_BINDING_DISPOSED` | A caller retained an agent binding after its host retired it | 410 | resolve a fresh binding | warn | stable (trusted API) |
42
- | `AGENT_CONTROL_RECEIPT_INVALID` | The existing agent runtime returned a malformed interrupt/stop receipt through the trusted dispatcher | 500 | report-bug | error | stable (trusted API) |
43
- | `AGENT_FLEET_PLUGIN_UNKNOWN` | Agent fleet configuration references a plugin that the app did not load | 500 | fix app configuration | error | stable (trusted API) |
44
- | `AGENT_FLEET_CONFIG_BINDING_UNKNOWN` | Agent fleet configuration passes a plugin config key outside its declared Agent config contract | 500 | fix app configuration | error | stable (trusted API) |
45
- | `AGENT_FLEET_MODEL_POLICY_UNCOMPILED` | Agent fleet configuration declares a model policy that the app fleet compiler did not resolve | 500 | configure app fleet compiler | error | stable (trusted API) |
46
- | `AGENT_FLEET_SEAT_PERSONA_INVALID` | `loadConfiguredAgentFleet` excluded a fleet.yaml seat because its persona package failed to materialize or compose | 500 | fix persona package/spec | error | stable (trusted API) |
47
- | `AGENT_FLEET_SEAT_SKILL_DIGEST_MISMATCH` | `loadConfiguredAgentFleet` excluded a fleet.yaml seat because a pinned skill's canonical content was unavailable or its digest drifted | 500 | run `pnpm write:skill-digests` or fix the skill file | error | stable (trusted API) |
48
- | `AGENT_FLEET_SEAT_INSTRUCTIONS_PATH_UNPUBLISHABLE` | `GET /api/v1/agents/:agentTypeId/describe` withheld the link to a seat's persona `instructions.md` for THIS request. Usually because the workspace root serving the request does not contain the personas tree (the fleet is composed from a repository root while the request is served from somewhere else); also when the persona canonically resolves outside that root through a symlink, when the source no longer exists, or when the seat name is not a safe path segment. NOT an HTTP error: the describe call still returns 200 — the code is only logged as a warning while that link is omitted from the response | 200 (log-only) | expected whenever a request is served from a workspace the personas are outside of — the seat runs normally, only the Agent-details instruction link is withheld. Serve the request from a workspace that contains the personas tree to get the link. If the message names an unsafe composed path, rename the seat directory to `[A-Za-z0-9][A-Za-z0-9._-]*` | warn | stable (trusted API) |
49
- | `AGENT_FLEET_CONFIG_FILE_INVALID` | `loadConfiguredAgentFleet`'s fleet.yaml could not be read, parsed, or validated (whole-fleet failure, not per-seat) | 500 | fix `.agents/factory/fleet.yaml` | error | stable (trusted API) |
50
- | `AGENT_DEFINITION_ID_CONFLICT` | Multiple discovered plugin packages claim the same agent definition id, so every claimant is excluded | 500 | remove or rename conflicting definitions | error | stable (trusted API) |
51
- | `AGENT_DEFINITION_UNSEATED` | A discovered agent package is not named by the fleet roster and remains inert | 200 | seat it in class-B fleet config if activation is intended | info | stable (trusted API) |
52
- | `RUNTIME_PROVISIONING_FAILED` | Agent runtime dependency provisioning failed before Level 3 runtime dependencies became ready | 503 | retry/report | error | stable (public API) |
53
- | `RUNTIME_PROVISIONING_LOCKED` | Agent runtime provisioning is locked by another reconciler | 423 | retry | warn | stable (public API) |
54
- | `BWRAP_UNAVAILABLE` | `bwrap` binary not found | 500 | report-bug | error | stable (public API) |
55
- | `BWRAP_TIMEOUT` | Sandbox command exceeded timeout | 408 | retry | warn | stable (public API) |
56
- | `OUTPUT_TRUNCATED` | Max output bytes reached; output was clipped | 200 | user-fix | warn | stable (public API) |
57
- | `SANDBOX_NOT_READY` | Remote sandbox cold start / provisioning | 503 | retry | warn | stable (public API) |
58
- | `SANDBOX_EXPIRED` | Remote sandbox TTL elapsed | 410 | retry | warn | stable (public API) |
59
- | `VERCEL_API_ERROR` | Generic upstream Vercel SDK/API failure | 502 | retry | error | stable (public API) |
60
- | `BLAXEL_API_ERROR` | Generic upstream Blaxel SDK/API failure | 502 | retry | error | stable (public API) |
61
- | `BLAXEL_CONFIG_DRIFT` | Existing Blaxel sandbox or Volume differs from requested immutable configuration | 409 | operator action | error | stable (public API) |
62
- | `BLAXEL_RUNTIME_UNQUALIFIED` | Blaxel runtime image lacks required workspace tools | 503 | replace image | error | stable (public API) |
63
- | `BLAXEL_VOLUME_BUSY` | Durable Blaxel Volume remains attached to another sandbox | 409 | retry | warn | stable (public API) |
64
- | `REMOTE_WORKER_CONFIG_INVALID` | Static remote-worker fleet configuration is invalid or incomplete | 500 | report-bug | error | stable (trusted API) |
65
- | `REMOTE_WORKER_PROTOCOL_MISMATCH` | Worker protocol or provider-contract version differs from the configured V1 cohort | 502 | operator-fix | error | stable (trusted API) |
66
- | `REMOTE_WORKER_UNAUTHENTICATED` | Worker rejected the per-box capability or authenticated receipt | 401 | operator-fix | warn | stable (trusted API) |
67
- | `REMOTE_WORKER_UNAVAILABLE` | The statically owning worker cannot serve the request; no fallback is attempted | 503 | retry | warn | stable (public API) |
68
- | `REMOTE_WORKER_UNQUALIFIED` | Worker qualification or artifact facts do not match static placement config | 503 | operator-fix | error | stable (trusted API) |
69
- | `REMOTE_WORKER_REQUEST_INVALID` | A strict remote-worker request schema rejected the request | 400 | report-bug | warn | stable (trusted API) |
70
- | `REMOTE_WORKER_RESPONSE_INVALID` | A strict remote-worker response schema rejected the worker response | 502 | operator-fix | error | stable (trusted API) |
71
- | `REMOTE_WORKER_CAPABILITY_EXPIRED` | A short-lived worker capability expired or exceeded its maximum lifetime | 401 | retry | warn | stable (trusted API) |
72
- | `REMOTE_WORKER_CAPABILITY_REPLAY` | A single-use worker capability nonce was presented more than once | 409 | report-security | warn | stable (trusted API) |
73
- | `REMOTE_WORKER_CAPABILITY_NONCE_STORE_EXHAUSTED` | Worker cannot accept another capability without violating its bounded nonce history | 503 | retry | warn | stable (trusted API) |
74
- | `REMOTE_WORKER_AUTHORIZED_WORKSPACE_REQUIRED` | Remote provider creation lacked an authenticated workspace identity | 403 | report-bug | warn | stable (trusted API) |
75
- | `REMOTE_WORKER_BINDING_RECEIPT_INVALID` | Create returned an unauthenticated or mismatched sandbox/workspace binding receipt | 502 | operator-fix | error | stable (trusted API) |
76
- | `REMOTE_WORKER_SANDBOX_WORKSPACE_MISMATCH` | Authorized workspace capability does not match the immutable sandbox lease binding | 404 | report-security | warn | stable (trusted API) |
77
- | `REMOTE_WORKER_SANDBOX_NOT_FOUND` | Addressed remote sandbox lease does not exist | 404 | reacquire | warn | stable (trusted API) |
78
- | `REMOTE_WORKER_SANDBOX_EXPIRED` | Addressed remote sandbox lease has expired | 410 | reacquire | warn | stable (trusted API) |
79
- | `REMOTE_WORKER_SANDBOX_DISPOSED` | Addressed remote sandbox lease was already disposed | 410 | reacquire | warn | stable (trusted API) |
80
- | `REMOTE_WORKER_CREATE_CONCURRENCY_EXHAUSTED` | Worker create concurrency is exhausted | 429 | retry | warn | stable (trusted API) |
81
- | `REMOTE_WORKER_EXEC_CONCURRENCY_EXHAUSTED` | Worker execution concurrency is exhausted | 429 | retry | warn | stable (trusted API) |
82
- | `REMOTE_WORKER_IDEMPOTENCY_CONFLICT` | Reused lease/invocation id has a different request digest | 409 | report-bug | warn | stable (trusted API) |
83
- | `REMOTE_WORKER_EXEC_IN_PROGRESS` | Duplicate invocation is still executing | 409 | retry | warn | stable (trusted API) |
84
- | `REMOTE_WORKER_SECRET_INVOCATION_NOT_REPLAYABLE` | Completed secret-bearing invocation cannot replay cached output | 409 | start-new-request | warn | stable (trusted API) |
85
- | `REMOTE_WORKER_SECRET_REFERENCE_REJECTED` | Invocation secret metadata is invalid, cross-workspace, or classified as a model credential | 403 | report-bug | warn | stable (trusted API) |
86
- | `REMOTE_WORKER_EXEC_ABORTED` | Invocation was aborted after bounded in-sandbox cleanup | 499 | retry | warn | stable (trusted API) |
87
- | `REMOTE_WORKER_OUTPUT_LIMIT` | Invocation output exceeded its configured combined byte ceiling | 413 | reduce-output | warn | stable (trusted API) |
88
- | `REMOTE_WORKER_PATH_UNSAFE` | Workspace operation failed dirfd-relative path confinement | 400 | report-bug | warn | stable (trusted API) |
89
- | `REMOTE_WORKER_ALREADY_EXISTS` | An atomic exclusive-create workspace operation found an existing path | 409 | choose-replace-or-skip | info | stable (trusted API) |
90
- | `REMOTE_WORKER_PATH_PRIMITIVE_UNAVAILABLE` | Required dirfd/openat2 containment primitive is unavailable | 503 | operator-fix | error | stable (trusted API) |
91
- | `REMOTE_WORKER_QUOTA_EXCEEDED` | Fixed workspace byte or inode quota was exceeded | 507 | free-space | warn | stable (trusted API) |
92
- | `REMOTE_WORKER_OUTCOME_UNKNOWN` | Worker loss left an effectful invocation outcome unknown; no automatic replay is safe | 502 | inspect-before-retry | error | stable (public API) |
93
- | `REMOTE_WORKER_INCOMPLETE_CLEANUP` | Provider could not prove remote lease teardown after bounded retries | 502 | operator-fix | error | stable (trusted API) |
94
- | `REMOTE_WORKER_DOCKER_COMMAND_FAILED` | Worker runtime command failed without exposing infrastructure stderr | 502 | operator-fix | error | stable (trusted API) |
95
- | `REMOTE_WORKER_TIMEOUT` | Remote worker request exceeded its client-side timeout before a response arrived | 504 | retry | warn | stable (public API) |
96
- | `REMOTE_WORKER_STREAM_CLOSED` | Remote worker filesystem event stream closed unexpectedly | 502 | retry | warn | stable (public API) |
97
- | `CIRCUIT_OPEN` | Circuit breaker open; request fast-failed | 503 | retry | warn | stable (public API) |
98
- | `ABORTED` | Request cancelled via `AbortSignal` | 499 | retry | warn | stable (public API) |
99
- | `PAYMENT_REQUIRED` | Billing/metering sink rejected the run (e.g. credits exhausted) | 402 | user-fix | warn | stable (public API) |
100
- | `MODEL_BUDGET_EXCEEDED` | Governance model budget for this user/model is exhausted | 402 | user-fix | warn | stable (public API) |
101
- | `MODEL_CONTEXT_WINDOW_EXCEEDED` | Provider rejects a model request because its conversation context is too large | 413 | compact/retry or start a smaller chat | warn | stable (public API) |
102
- | `METERING_UNSUPPORTED_COMMAND` | Slash-command execution is disabled because metering cannot yet reserve/settle that path | 409 | user-fix | warn | stable (public API) |
103
- | `SESSION_NOT_FOUND` | Session id does not exist | 404 | user-fix | warn | stable (public API) |
104
- | `SESSION_LOCKED` | Session currently locked by concurrent writer | 409 | retry | warn | stable (public API) |
105
- | `SESSION_TRANSCRIPT_UNREADABLE` | Canonical native session transcript exists but cannot be opened safely | 500 | retry | error | stable (public API) |
106
- | `STREAM_BUFFER_EVICTED` | Resume cursor evicted from in-memory stream buffer | 410 | retry | warn | stable (public API) |
107
- | `CURSOR_OUT_OF_RANGE` | Resume cursor invalid/out of range | 416 | user-fix | warn | stable (public API) |
108
- | `BRIDGE_COMMAND_INVALID` | UI bridge command kind/params invalid | 400 | user-fix | warn | stable (public API) |
109
- | `TOOL_NOT_FOUND` | Requested tool name not present in catalog | 404 | user-fix | warn | stable (public API) |
110
- | `TOOL_INVALID_INPUT` | Tool input fails schema validation | 400 | user-fix | warn | stable (public API) |
111
- | `TOOL_EXECUTION_ERROR` | Tool threw or returned execution failure | 500 | report-bug | error | stable (public API) |
112
- | `AUTHORED_AGENT_ID_INVALID` | Authored agent materialization received an agent type id outside the product-safe grammar | 400 | user-fix | warn | stable (trusted API) |
113
- | `AUTHORED_AGENT_TYPE_MISMATCH` | Trusted host expected one authored agent type but the directory declares another | 409 | user-fix | warn | stable (trusted API) |
114
- | `AUTHORED_AGENT_REFERENCE_UNSUPPORTED` | Authored source contains a non-empty legacy capability/tool/skill/MCP selector; move behavior to trusted host plugins | 400 | user-fix | warn | stable migration error |
115
- | `AUTHORED_AGENT_TOOL_COLLISION` | Normal trusted tool composition produced duplicate or colliding tool names | 409 | user-fix | warn | stable (trusted API) |
116
- | `MCP_AGENT_ARTIFACT_INVALID` | Managed MCP delivery artifact is path-shaped, non-Markdown, binary, malformed UTF-8, or otherwise invalid | 400 | user-fix | warn | stable (public API) |
117
- | `MCP_AGENT_ARTIFACT_TOO_LARGE` | Managed MCP final text, inline Markdown artifact, or serialized result exceeds the delivery v0 byte cap | 413 | user-fix | warn | stable (public API) |
118
- | `MCP_AGENT_ARTIFACT_UNAVAILABLE` | Managed MCP artifact is missing, unreadable through the authorized workspace, or changed during read | 409 | retry | warn | stable (public API) |
119
- | `AGENT_MCP_GRANT_REF_UNGRANTED` | Agent declared an `mcpServerRefs` connector with no matching per-agent MCP grant in the workspace; the connector is dropped, never silently allowed | 200 | user-fix | warn | stable (public API) |
120
- | `AGENT_MCP_GRANT_TOOL_NOT_ALLOWED` | A grant's `allowedTools` entry is not part of the connector's known tool catalog and was dropped during resolution | 200 | user-fix | warn | stable (public API) |
121
- | `AGENT_MCP_GRANT_RECORD_MALFORMED` | A persisted MCP grant record has a missing or malformed config and was skipped rather than crashing the workspace's grant listing | 200 | user-fix | warn | stable (public API) |
122
- | `AGENT_MCP_GRANT_CONNECTOR_UNKNOWN` | A grant's connector is unknown to the supplied connector catalog; the connector is denied rather than left unfiltered | 200 | user-fix | warn | stable (public API) |
123
- | `AGENT_MCP_GRANT_TOOL_NAME_INVALID` | A grant's `allowedTools` entry contains a glob metacharacter (e.g. `*`), which is rejected at write time so it can never be misinterpreted as a wildcard downstream | 400 | user-fix | warn | stable (public API) |
124
- | `PLUGIN_LOAD_FAILED` | Plugin failed to load/register | 500 | report-bug | error | stable (public API) |
125
- | `PLUGIN_NAME_COLLISION` | Plugin name collides with existing tool/plugin | 409 | user-fix | warn | stable (public API) |
126
- | `PLUGIN_RUNTIME_REVISION_MISMATCH` | Browser requested a stale plugin runtime revision after reload | 409 | retry | warn | stable (public API) |
127
- | `PLUGIN_RUNTIME_PRIVATE_FILE` | Plugin runtime request targeted a disallowed private/non-front file | 403 | user-fix | warn | stable (public API) |
128
- | `PLUGIN_RUNTIME_UNSAFE_IMPORT` | Plugin frontend import is browser-unsafe or bypasses the host runtime surface | 400 | user-fix | warn | stable (public API) |
129
- | `PLUGIN_RUNTIME_TRANSFORM_FAILED` | Host runtime could not transform the plugin frontend module graph | 500 | report-bug | error | stable (public API) |
130
- | `RUNTIME_PLUGIN_NOT_FOUND` | Runtime backend gateway could not find a live plugin snapshot | 404 | user-fix | warn | stable (public API) |
131
- | `RUNTIME_PLUGIN_ROUTE_NOT_FOUND` | Runtime backend gateway could not match an exact plugin-owned route | 404 | user-fix | warn | stable (public API) |
132
- | `RUNTIME_PLUGIN_HANDLER_FAILED` | Runtime backend handler threw while serving a plugin-owned route | 500 | report-bug | error | stable (public API) |
133
- | `RUNTIME_PLUGIN_LOAD_FAILED` | Runtime backend module failed to import, validate, capture, or dispose | 500 | report-bug | error | stable (public API) |
134
- | `RUNTIME_PLUGIN_RESPONSE_UNSUPPORTED` | Runtime backend handler returned an unsupported response value | 500 | report-bug | error | stable (public API) |
135
- | `PROVISIONING_LAYOUT_FAILED` | Failed to create/write generated `.boring-agent` layout | 500 | report-bug | error | stable (public API) |
136
- | `PROVISIONING_SKILLS_FAILED` | Failed to mirror plugin skills into `.boring-agent/skills` | 500 | report-bug | error | stable (public API) |
137
- | `PROVISIONING_TEMPLATES_FAILED` | Failed to seed missing workspace template files | 500 | report-bug | error | stable (public API) |
138
- | `PROVISIONING_NODE_PREFLIGHT_FAILED` | Node/npm preflight failed before runtime package install | 500 | user-fix | error | stable (public API) |
139
- | `PROVISIONING_NPM_INSTALL_FAILED` | npm runtime package install failed | 500 | user-fix | error | stable (public API) |
140
- | `PROVISIONING_UV_BOOTSTRAP_FAILED` | uv bootstrap/preflight failed for Python runtime packages | 500 | user-fix | error | stable (public API) |
141
- | `PROVISIONING_UV_INSTALL_FAILED` | uv venv or uv pip install failed | 500 | user-fix | error | stable (public API) |
142
- | `PROVISIONING_ARTIFACT_FAILED` | Runtime-mode adapter failed to prepare/upload install artifact | 500 | retry | error | stable (public API) |
143
- | `SKILL_DISCOVERY_FAILED` | Skill discovery failed without exposing package-manager or host-path details | 200 | retry | warn | stable (public API) |
144
- | `PACKAGE_RESOURCE_INVALID` | A declared or scanned package resource (skill/agent) failed validation | 400 | user-fix | warn | stable (public API) |
145
- | `PACKAGE_RESOURCE_CONFLICT` | Two package resources claim the same identity/path | 409 | user-fix | warn | stable (public API) |
146
- | `RUNTIME_FILESYSTEM_BINDING_DUPLICATE` | A filesystem id was bound more than once while merging host/request-scoped runtime filesystem bindings | 500 | report-bug | error | stable (public API) |
147
- | `RUNTIME_READONLY_FILESYSTEM_POLICY_INVALID` | A readonly primary-workspace path is not a normalized workspace-relative path | 400 | user-fix | warn | stable (public API) |
148
- | `EVENT_STORE_OPEN_FAILED` | `BORING_CHAT_DURABLE_STREAM=1` but the SQLite event-stream store could not be opened: either no host-resolvable root exists (no `sessionRoot` and no host storage root — this deliberately never falls back to an in-sandbox/guest path) or `openDatabase` failed at the resolved path (bad path/permissions); reported via telemetry alongside `DURABLE_STREAM_UNAVAILABLE` | n/a (boot-time diagnostic, not an HTTP response) | report-bug | error | stable (public API) |
149
- | `DURABLE_STREAM_UNAVAILABLE` | `BORING_CHAT_DURABLE_STREAM=1` but the durable event-stream store could not be opened; boot fails loudly with the underlying cause instead of silently falling back to in-memory streaming (flag off = in-memory, unchanged) | n/a (boot-time failure, not an HTTP response) | user-fix | error | stable (public API) |
150
- | `CHANNEL_DURABLE_STREAM_REQUIRED` | `BORING_AGENT_CHANNELS=1` was set without durable chat streaming; boot fails rather than accepting channel traffic that cannot resume | n/a (boot-time failure) | user-fix | error | stable (public API) |
151
- | `CHANNEL_INBOUND_ACCEPTED` | The provider message was durably deduplicated/enqueued and asynchronous channel processing may begin | 200 | none | info | stable (public API) |
152
- | `CHANNEL_UNKNOWN_BINDING` | Channel inbound did not match an active provisioned binding; no session or identity was created | 200 | user-fix | warn | stable (public API) |
153
- | `CHANNEL_INBOUND_PARKED` | Channel inbound exhausted bounded delivery retries and was parked so later messages are not wedged | n/a (worker diagnostic) | retry | warn | stable (public API) |
154
- | `CHANNEL_OUTBOUND_PARKED` | A completed channel turn exhausted bounded send retries and was parked so later turns are not wedged | n/a (worker diagnostic) | retry | warn | stable (public API) |
155
- | `CHANNEL_TURN_STALLED` | A channel turn did not reach a terminal event before its configured deadline; a notice was sent and the observed range was parked | n/a (worker diagnostic) | retry | warn | stable (public API) |
156
- | `CHANNEL_BINDING_BUSY` | Provisioning could not replace a binding while its current generation held a live outbound-delivery lease | n/a (admin operation) | retry | warn | stable (public API) |
157
- | `CHANNEL_BINDING_REVOKED` | A channel binding was revoked while queued work was being resolved | n/a (worker diagnostic) | user-fix | warn | stable (public API) |
158
- | `SESSION_CREATE_TIMEOUT` | The owner-token channel session reservation did not reach ready state within bounded reservation cycles | n/a (worker diagnostic) | retry | warn | stable (public API) |
159
- | `ERR_NOT_IMPLEMENTED_UNTIL_T1` | Headless core method exists but the durable T1 implementation has not landed yet | 501 | retry-after-upgrade | warn | stable (public API) |
160
- | `INTERNAL_ERROR` | Catch-all internal failure | 500 | report-bug | error | internal (may change) |
161
- | `AR1_SHARE_NOT_FOUND` | `GET /a/:id` deep link: no such Lane W share entry, or the entry belongs to a workspace the requester is not authorized/scoped to (identical response either way — no existence oracle) | 404 | user-fix | warn | stable (public API) |
162
- | `AR1_SHARE_TOMBSTONED` | `GET /a/:id` deep link: share entry exists but its target file is gone; response renders provenance + last-known metadata, never a bare 404 | 200 | user-fix | warn | stable (public API) |
163
-
164
- ## Credential registry
165
-
166
- Credential failures use their own stable enum, `CREDENTIAL_ERROR_CODES` in
167
- `src/shared/credentials/errors.ts`, surfaced as `CredentialResolutionError.code`.
168
- These are trusted-API codes: messages are metadata only and never carry a
169
- credential value, a workspace DEK, or KEK material.
170
-
171
- | Code | When it fires | HTTP status | Suggested client action | Log level | Stability |
172
- | --- | --- | --- | --- | --- | --- |
173
- | `CREDENTIAL_PROVIDER_UNKNOWN` | The referenced provider is not in the startup provider registry | 404 | report-bug | warn | stable (trusted API) |
174
- | `CREDENTIAL_NOT_CONFIGURED` | No credential material is configured for that workspace/provider, or a required registered field is absent | 404 | user-fix (connect the provider) | warn | stable (trusted API) |
175
- | `CREDENTIAL_DISABLED` | The workspace credential exists but is administratively disabled | 403 | user-fix | warn | stable (trusted API) |
176
- | `CREDENTIAL_REVOKED` | The workspace credential was revoked; no fallback is attempted | 403 | user-fix (reconnect) | warn | stable (trusted API) |
177
- | `CREDENTIAL_FORBIDDEN` | The verified principal may not use this credential | 403 | user-fix | warn | stable (trusted API) |
178
- | `CREDENTIAL_WORKSPACE_MISMATCH` | A credential artifact does not belong to the authority-verified workspace | 403 | report-bug | error | stable (trusted API) |
179
- | `CREDENTIAL_CONSUMER_MISMATCH` | A reference, binding, and provider triple disagree, or the binding is not registered for its provider | 403 | report-bug | warn | stable (trusted API) |
180
- | `CREDENTIAL_DELIVERY_FORBIDDEN` | The requested delivery channel is not permitted for that binding, or a lease was serialized | 403 | report-bug | error | stable (trusted API) |
181
- | `CREDENTIAL_AUTHORITY_INVALID` | The workspace credential authority is unissued, copied, or expired | 403 | re-auth | warn | stable (trusted API) |
182
- | `CREDENTIAL_SCHEMA_MISMATCH` | A credential reference, registry/binding definition, KEK context, or envelope AAD context failed strict validation | 400 | report-bug | warn | stable (trusted API) |
183
- | `CREDENTIAL_UNREADABLE` | A credential envelope or wrapped workspace DEK failed AES-256-GCM authentication — tampered ciphertext/nonce/tag, a swapped AAD component, a wrong KEK, or another backend's envelope. Never treated as "absent" | 500 | operator-fix | error | stable (trusted API) |
184
- | `CREDENTIAL_BACKEND_UNAVAILABLE` | The credential store or its KEK provider reported not-ready or failed unexpectedly; the operation fails closed with no fallback | 503 | retry/operator-fix | error | stable (trusted API) |
185
- | `CREDENTIAL_KEK_UNAVAILABLE` | The configured KEK source is missing, unreadable, or the wrong length, so no workspace DEK could be wrapped or unwrapped. Never falls back to another backend or to plaintext | 503 | operator-fix (mount/repair the sealed KEK file) | error | stable (trusted API) |
186
- | `CREDENTIAL_LEASE_EXPIRED` | A resolved credential lease was used after disposal or expiry | 410 | resolve a fresh lease | warn | stable (trusted API) |
187
- | `CREDENTIAL_OAUTH_STATE_INVALID` | A one-use OAuth state transaction was unknown, replayed, or expired | 400 | restart the connect flow | warn | stable (trusted API) |
188
- | `CREDENTIAL_OAUTH_REFRESH_FAILED` | Upstream refused a refresh-token exchange | 401 | re-auth | warn | stable (trusted API) |
189
- | `CREDENTIAL_VALIDATION_UNAUTHORIZED` | Provider rejected a pending API key before persistence | 401 | correct the key and retry | warn | stable (trusted API) |
190
- | `CREDENTIAL_VALIDATION_RATE_LIMITED` | Provider rate-limited pending API-key validation | 429 | retry later | warn | stable (trusted API) |
191
- | `CREDENTIAL_VALIDATION_UNAVAILABLE` | Provider validation transport was unavailable | 503 | retry later | warn | stable (trusted API) |
192
- | `CREDENTIAL_VALIDATION_TIMEOUT` | Provider validation exceeded the bounded timeout | 504 | retry later | warn | stable (trusted API) |
193
- | `CREDENTIAL_VALIDATION_UNSUPPORTED` | Provider lacks a trusted API-key validation probe | 400 | use a supported provider/auth method | warn | stable (trusted API) |
194
-
195
- ## Readiness error details
196
-
197
- `WORKSPACE_NOT_READY` is reserved for workspace substrate requirements:
198
-
199
- ```json
200
- {
201
- "code": "WORKSPACE_NOT_READY",
202
- "retryable": true,
203
- "requirement": "workspace-fs"
204
- }
205
- ```
206
-
207
- Runtime dependency preparation is separate so chat/file work can continue while `.boring-agent` dependencies install:
208
-
209
- ```json
210
- {
211
- "code": "AGENT_RUNTIME_NOT_READY",
212
- "retryable": true,
213
- "requirement": "runtime:python",
214
- "state": "preparing",
215
- "workspaceId": "workspace_123"
216
- }
217
- ```
218
-
219
- If dependency provisioning fails, dependency-backed tools return:
220
-
221
- ```json
222
- {
223
- "code": "RUNTIME_PROVISIONING_FAILED",
224
- "retryable": true,
225
- "requirement": "runtime:python",
226
- "state": "failed",
227
- "causeCode": "PROVISIONING_UV_INSTALL_FAILED"
228
- }
229
- ```
@@ -1,100 +0,0 @@
1
- # Known Limitations
2
-
3
- ## Abandoned Vercel sandboxes (xzr)
4
-
5
- `@hachej/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 `@hachej/boring-agent` v1
72
- and `@hachej/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).
package/docs/MIGRATION.md DELETED
@@ -1,50 +0,0 @@
1
- # MIGRATION
2
-
3
- Migration notes for moving from legacy monorepo-integrated agent flows to
4
- `@hachej/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
- - `@hachej/boring-agent`: agent runtime, tool catalog, harness, sandbox/workspace
18
- adapters, and the chat UI (`ChatPanel`).
19
- - `@hachej/boring-workspace`: IDE-style layout/panes plus the UI-bridge tools
20
- (`exec_ui`, `get_ui_state`) and `/api/v1/ui/*` routes.
21
- - App shell: final composition, mode selection, and product policy.
22
-
23
- ## Practical Migration Steps
24
-
25
- 1. Move chat/runtime logic into `@hachej/boring-agent` integration points.
26
- 2. Keep editor/file-tree/layout code in `@hachej/boring-workspace` or your own UI.
27
- 3. Route all filesystem and command execution through runtime mode adapters.
28
- 4. Replace ad-hoc shared types with imports from `@hachej/boring-agent/shared`.
29
- 5. Keep server-only code in server entry points; avoid server imports in
30
- frontend/shared paths.
31
-
32
- ## Runtime Mode Migration
33
-
34
- All three modes ship: `direct`, `local` (`bwrap`), and `vercel-sandbox`. Select
35
- via `mode` in `createAgentApp` or the `BORING_AGENT_MODE` env var. Keep your
36
- integration surface mode-agnostic so adapter swaps are non-breaking. See
37
- [runtime.md](./runtime.md).
38
-
39
- ## Contract Hygiene Checklist
40
-
41
- - No `node:*` imports in `src/shared/**`.
42
- - No `Buffer` in shared contracts (`Uint8Array` only).
43
- - Keep session/tool/workspace behavior aligned with `@hachej/boring-agent/shared`.
44
-
45
- ## References
46
-
47
- - Runtime modes: [runtime.md](./runtime.md)
48
- - API surface: [API.md](./API.md)
49
- - README quickstart/context: `../README.md`
50
- - Historical design notes: `docs/plans/archive/` (archival; not current truth)
@@ -1,68 +0,0 @@
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 @hachej/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 DELETED
@@ -1,138 +0,0 @@
1
- # PLUGINS
2
-
3
- Plugin and extension notes for `@hachej/boring-agent`.
4
-
5
- ## Two extension paths
6
-
7
- There are two ways to extend the agent, depending on host integration:
8
-
9
- 1. **Pi-native resources** (`package.json#pi`: `extensions`, `skills`,
10
- `prompts`, `systemPrompt`) — discovered by the Pi harness in `direct`/`local`
11
- mode and reloadable through `/reload`. This is the right place for portable,
12
- hot-reloadable tools and skills. See [tools.md](./tools.md).
13
- 2. **Trusted server plugins** (`@hachej/boring-workspace` `defineServerPlugin`)
14
- — static/boot-time `agentTools`, routes, and provisioning declared by trusted
15
- server code. See [tools.md](./tools.md) and
16
- [runtime-provisioning.md](./runtime-provisioning.md).
17
-
18
- Both paths converge on the same `AgentTool` contract
19
- (`src/shared/tool.ts`). The standalone `createAgentApp` ships no UI tools;
20
- `exec_ui`/`get_ui_state` and UI routes are owned by `@hachej/boring-workspace`.
21
-
22
- ## Tool contract
23
-
24
- Each tool provides:
25
-
26
- - Stable `name`
27
- - Clear `description`
28
- - JSON-schema `parameters`
29
- - Async `execute(params, ctx)` implementation
30
-
31
- `ctx` is expected to carry:
32
-
33
- - `abortSignal`
34
- - `toolCallId`
35
- - Optional progress callbacks (`onUpdate`)
36
-
37
- Optional fields:
38
-
39
- - `readinessRequirements?: ToolReadinessRequirement[]` — gates the tool on
40
- runtime readiness. `mergeTools` assigns `['workspace-fs']` to plugin tools
41
- that omit it (built-in/`extraTools` tools keep whatever they declare).
42
- - `promptSnippet?` — extra guidance injected into the system prompt when the
43
- tool is registered.
44
-
45
- ## Trust & runtime modes
46
-
47
- Plugin tools' `execute()` run in the **host Node process** and bypass the
48
- sandbox by design — treat plugin code as trusted local/workspace code. Plugin
49
- auto-discovery is local-mode-only (see the `vercel-sandbox` caveat below). For
50
- the full internal-vs-external plugin trust model, see
51
- `packages/workspace/docs/PLUGIN_SYSTEM.md` §1.1.
52
-
53
- ## Safety + Compatibility Rules
54
-
55
- - Validate all plugin input against schema before execution.
56
- - Return deterministic, serializable output structures for reliable rendering.
57
- - Keep plugin-side filesystem and process behavior behind the selected runtime
58
- mode adapter (do not bypass the agent runtime boundary).
59
- - Avoid `node:*` imports in shared/frontend code paths.
60
-
61
- ## Tool Name Collisions
62
-
63
- Collision precedence is explicit: last-registered wins.
64
-
65
- - Built-in catalog tools are registered first.
66
- - App `extraTools` are registered next.
67
- - Plugin tools are registered last.
68
-
69
- If a plugin reuses an existing tool name (for example `bash`), the plugin tool
70
- replaces the earlier one and a warning is logged:
71
-
72
- `[catalog] Tool "bash" overridden by plugin <name>`
73
-
74
- ### Plugin vs Plugin collisions
75
-
76
- If two plugins register the same tool name:
77
-
78
- - the later plugin registration wins
79
- - a warning is logged for the override
80
- - startup does **not** fail; no hard error is thrown
81
-
82
- The rule is implemented in `mergeTools` and is applied uniformly whether the
83
- previous tool came from built-ins, `extraTools`, or another plugin.
84
-
85
- ## Discovery Sources (direct/local)
86
-
87
- This section describes **Pi-native resource discovery** at the harness level.
88
- Workspace runtime/generated plugins are a higher-level package format under
89
- `.pi/extensions/<id>/package.json` documented in
90
- `packages/workspace/docs/PLUGIN_SYSTEM.md`; those packages may themselves
91
- declare `pi.extensions` resources.
92
-
93
- In `direct` and `local` mode, plugin discovery can load tools from:
94
-
95
- - `~/.pi/agent/extensions/*.js|*.mjs` (global)
96
- - `<workspace>/.pi/extensions/*.js|*.mjs` (local)
97
- - `<workspace>/node_modules/pi-plugin-*` packages
98
- - `<workspace>/.pi/extensions.json` `npm` entries (if installed in `node_modules`)
99
-
100
- ## `vercel-sandbox` Mode Caveat
101
-
102
- `createAgentApp` disables automatic plugin loading when runtime mode is
103
- `vercel-sandbox`. This is intentional: plugin files are discovered via host
104
- Node filesystem access, while sandbox workspace/exec are remote.
105
-
106
- Practical effect:
107
-
108
- - Plugin auto-discovery is enabled in `direct` and `local`.
109
- - Plugin auto-discovery is disabled in `vercel-sandbox`.
110
-
111
- ## Manual Packaging Pattern For `vercel-sandbox`
112
-
113
- If you still need extension-like behavior in `vercel-sandbox`, package files
114
- manually into the workspace image/template used to create the sandbox:
115
-
116
- - include `.pi/extensions/*` in your source template
117
- - ensure files exist at `/vercel/sandbox/.pi/extensions/` in the remote workspace
118
- - load/register tools through your app-shell bootstrap (not package auto-discovery)
119
-
120
- Use the same template-seeding concept as local provisioning (for example
121
- `templatePath`/`BORING_AGENT_TEMPLATE_PATH` workflows), but apply it when you
122
- build the sandbox source/snapshot consumed by Vercel.
123
-
124
- ## Environment Differences
125
-
126
- When porting plugins from host modes to `vercel-sandbox`, expect differences:
127
-
128
- - available binaries and OS tools can differ from host machine
129
- - root/path assumptions differ (`/vercel/sandbox` workspace root)
130
- - process/network policy can differ from local development
131
-
132
- ## References
133
-
134
- - Adding tools: [tools.md](./tools.md)
135
- - Chat `/slash` commands as a plugin surface:
136
- `packages/workspace/docs/PLUGIN_SYSTEM.md` §4.7
137
- - Minimal integration sketch: `examples/with-custom-tool/README.md`
138
- - Historical design notes: `docs/plans/archive/` (archival; not current truth)