@hachej/boring-agent 0.1.40 → 0.1.42

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 (35) hide show
  1. package/README.md +31 -252
  2. package/dist/{agentPluginEvents-CKrZLW3g.d.ts → agentPluginEvents-ZoOjcb5J.d.ts} +21 -0
  3. package/dist/front/index.d.ts +3 -1
  4. package/dist/front/index.js +9 -3
  5. package/dist/server/index.d.ts +2 -2
  6. package/dist/server/index.js +429 -257
  7. package/dist/shared/index.d.ts +2 -2
  8. package/docs/ACCESSIBILITY.md +11 -13
  9. package/docs/API.md +86 -59
  10. package/docs/CSP.md +10 -9
  11. package/docs/ERROR_CODES.md +1 -1
  12. package/docs/KNOWN_LIMITATIONS.md +3 -3
  13. package/docs/MIGRATION.md +16 -16
  14. package/docs/PERFORMANCE.md +1 -1
  15. package/docs/PLUGINS.md +42 -12
  16. package/docs/README.md +89 -12
  17. package/docs/RISKS-MULTI-TAB.md +1 -1
  18. package/docs/STYLING.md +6 -4
  19. package/docs/UI-SHADCN.md +7 -7
  20. package/docs/runtime-provisioning.md +7 -3
  21. package/docs/runtime.md +68 -13
  22. package/docs/tools.md +37 -14
  23. package/package.json +2 -2
  24. /package/docs/plans/{AGENT_EVAL_FRAMEWORK.md → archive/AGENT_EVAL_FRAMEWORK.md} +0 -0
  25. /package/docs/plans/{agent-package-spec.md → archive/agent-package-spec.md} +0 -0
  26. /package/docs/plans/{harness-followup-capabilities.md → archive/harness-followup-capabilities.md} +0 -0
  27. /package/docs/plans/{harness-tool-ui-capabilities.md → archive/harness-tool-ui-capabilities.md} +0 -0
  28. /package/docs/plans/{pi-followup-history-projection.md → archive/pi-followup-history-projection.md} +0 -0
  29. /package/docs/plans/{pi-tools-migration.md → archive/pi-tools-migration.md} +0 -0
  30. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-codex-review.md +0 -0
  31. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-gpt-review.md +0 -0
  32. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-opus-review.md +0 -0
  33. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-xai-review.md +0 -0
  34. /package/docs/plans/{vercel-base-snapshot-template-plan.md → archive/vercel-base-snapshot-template-plan.md} +0 -0
  35. /package/docs/plans/{vercel-persistent-sandbox-adapter.md → archive/vercel-persistent-sandbox-adapter.md} +0 -0
package/README.md CHANGED
@@ -7,291 +7,70 @@
7
7
 
8
8
  </div>
9
9
 
10
- A pane-embeddable coding agent with three execution modes behind one interface. Ships as a standalone CLI (`npx @hachej/boring-agent`) and composes into any app shell.
10
+ A pane-embeddable coding agent: an LLM agent loop, a tool catalog, and a chat UI
11
+ behind one interface, with three swappable execution modes. The same agent,
12
+ tools, and UI run in `direct` (host process), `local` (bwrap sandbox), or
13
+ `vercel-sandbox` (Firecracker microVM) mode.
11
14
 
12
- ```bash
13
- npx @hachej/boring-agent
14
- ```
15
-
16
- ---
17
-
18
- ## TL;DR
19
-
20
- **The Problem**: You want a coding agent in the browser — but also as a CLI — and you need it to run your code somewhere safe (or not). Existing solutions force you into one deployment model and one UI.
21
-
22
- **The Solution**: One package that ships a full LLM agent loop, tool catalog, and chat UI — with swappable execution backends. Same agent, same tools, same UI. Three modes. Zero config to start.
23
-
24
- ### Why Use @hachej/boring-agent?
25
-
26
- | Feature | What It Does |
27
- |---------|--------------|
28
- | **Three execution modes** | `direct` (no isolation, macOS dev) / `local` (bwrap sandbox) / `vercel-sandbox` (Firecracker microVM) |
29
- | **CLI + embeddable** | `npx @hachej/boring-agent` works standalone; `<ChatPanel />` composes into any layout |
30
- | **7 standard tools** | `bash`, `read`, `write`, `edit`, `find`, `grep`, `ls` — ported from pi-coding-agent |
31
- | **Workspace-local runtime provisioning** | Generates `.boring-agent` inside the selected workspace for mirrored skills, SDKs, CLIs, and templates |
32
- | **Workspace-agnostic FS** | `Workspace` interface — agent tools and HTTP routes share the same filesystem view |
33
- | **Session management** | List, create, switch, delete sessions with streamed history hydration |
34
- | **UI bridge** | Agent opens files, panels, and surfaces in the workbench via typed commands |
35
- | **Model picker + thinking toggle** | Inline in the composer — switch models and reasoning depth per message |
36
-
37
- ### Quick Example
15
+ ## Install
38
16
 
39
17
  ```bash
40
- # Start the agent in your current directory — zero setup
41
- npx @hachej/boring-agent
42
-
43
- # Or run with a specific workspace root
44
- BORING_AGENT_WORKSPACE_ROOT=/path/to/project npx @hachej/boring-agent
45
-
46
- # Set the API key
47
- ANTHROPIC_API_KEY=sk-ant-... npx @hachej/boring-agent
48
-
49
- # Run in local sandbox mode (Linux + bubblewrap)
50
- BORING_AGENT_MODE=local npx @hachej/boring-agent
51
- ```
52
-
53
- In the browser chat, try:
54
- ```
55
- read the README and summarize it
56
- find all TypeScript files that import "react"
57
- write a test for src/utils.ts
58
- ```
59
-
60
- ---
61
-
62
- ## Workspace-local runtime provisioning
63
-
64
- Boring UI keeps generated runtime state in the selected workspace at
65
- `$BORING_AGENT_WORKSPACE_ROOT/.boring-agent`. Plugin skills are mirrored to
66
- `.boring-agent/skills`, runtime CLIs live under `.boring-agent/node` or
67
- `.boring-agent/venv`, and templates seed only missing workspace files. The
68
- folder is generated/disposable and should not be hand-edited or committed.
69
-
70
- See [docs/runtime-provisioning.md](docs/runtime-provisioning.md) for the full
71
- user and plugin-author contract, including package metadata shape,
72
- `provisionWorkspace: false`, `/api/v1/agent/reload`, and direct/local/Vercel
73
- mode behavior.
74
-
75
- ---
76
-
77
- ## Architecture
78
-
79
- ```
80
- ┌─────────────────────────────────────┐
81
- │ Chat UI (browser) │
82
- │ Composer · Messages · SessionBar │
83
- └──────────────────┬──────────────────┘
84
- │ UIMessage stream (SSE)
85
- ┌──────────────────▼──────────────────┐
86
- │ Agent Harness │
87
- │ (pi-coding-agent loop) │
88
- └──────────────────┬──────────────────┘
89
- │ AgentTool[]
90
- ┌──────────────────▼──────────────────┐
91
- │ Tool Catalog │
92
- │ bash · read · write · edit │
93
- │ find · grep · ls │
94
- └──────┬──────────────┬───────────────┘
95
- │ │
96
- ┌──────▼─────┐ ┌──────▼─────┐
97
- │ Workspace │ │ Sandbox │
98
- │ (fs ops) │ │ (exec) │
99
- │ read/write │ │ bwrap │
100
- │ readdir │ │ vercel │
101
- └────────────┘ └────────────┘
102
- ```
103
-
104
- **Two layers, clear boundary:**
105
-
106
- - **Layer 1 (Core runtime):** `AgentHarness` · `Catalog` · `Workspace` · `Sandbox` — interfaces locked; adapters swap per mode.
107
- - **Layer 2 (Integration):** `SessionStore` · `UiBridge` · `Provisioning` — replaceable plumbing, independent evolution.
108
-
109
- ### Execution Modes
110
-
111
- | Mode | Workspace | Sandbox | Isolation | Use Case |
112
- |------|-----------|---------|-----------|----------|
113
- | `direct` | `NodeWorkspace` | `DirectSandbox` | None | macOS/Windows dev, quick tests |
114
- | `local` | `NodeWorkspace` | `BwrapSandbox` | bwrap process jail | Linux deployments, safer default |
115
- | `vercel-sandbox` | `VercelSandboxWorkspace` | `VercelSandboxExec` | Firecracker microVM | Multi-tenant, remote execution |
116
-
117
- **Pairing invariant:** Workspace + Sandbox must target the same filesystem substrate. The adapter factory enforces this at construction — mismatched pairs are impossible.
118
-
119
- ---
120
-
121
- ## Installation
122
-
123
- ```bash
124
- # npm
125
- npm install @hachej/boring-agent
126
-
127
- # pnpm
128
18
  pnpm add @hachej/boring-agent
129
-
130
- # standalone (no install needed)
131
- npx @hachej/boring-agent
132
- ```
133
-
134
- ### From Source
135
-
136
- ```bash
137
- git clone https://github.com/hachej/boring-ui.git
138
- cd boring-ui
139
- pnpm install
140
- pnpm --filter @hachej/boring-agent build
141
- ```
142
-
143
- ---
144
-
145
- ## Quick Start
146
-
147
- ### 1. As a CLI
148
-
149
- ```bash
150
- # Set your API key
151
- export ANTHROPIC_API_KEY=sk-ant-...
152
-
153
- # Run in your project directory
154
- cd /path/to/project
155
- npx @hachej/boring-agent
19
+ # or: npm install @hachej/boring-agent
156
20
  ```
157
21
 
158
- Opens `http://localhost:5200` with a full agent workspace pointed at your cwd.
22
+ Peer deps (optional): `react`, `react-dom`, `tailwindcss`.
159
23
 
160
- ### 2. Embedded in an App
24
+ ## Usage
161
25
 
162
- **Server:**
26
+ **Server** — standalone Fastify app:
163
27
 
164
28
  ```ts
165
29
  import { createAgentApp } from "@hachej/boring-agent/server"
166
30
 
167
31
  const app = await createAgentApp({
168
- mode: "local", // "direct" | "local" | "vercel-sandbox"
32
+ mode: "local", // "direct" | "local" | "vercel-sandbox"
169
33
  workspaceRoot: process.cwd(),
170
- apiBaseUrl: "http://localhost:3000",
171
34
  })
172
35
  await app.listen({ port: 3001 })
173
36
  ```
174
37
 
175
- **Frontend:**
176
-
177
- ```tsx
178
- import { ChatPanel, useAgentChat } from "@hachej/boring-agent"
179
- import "@hachej/boring-agent/front/styles.css"
180
-
181
- function App() {
182
- return <ChatPanel apiBaseUrl="http://localhost:3000" />
183
- }
184
- ```
38
+ To embed routes into an existing Fastify instance, use `registerAgentRoutes`
39
+ instead. The full IDE shell (file tree, panes, UI-bridge tools) lives in
40
+ `@hachej/boring-workspace`, which mounts this agent.
185
41
 
186
- ### 3. Composed with Workspace
42
+ **Frontend** the chat panel:
187
43
 
188
44
  ```tsx
189
- import { WorkspaceProvider, IdeLayout } from "@hachej/boring-workspace"
190
45
  import { ChatPanel } from "@hachej/boring-agent"
46
+ import "@hachej/boring-agent/front/styles.css"
191
47
 
192
48
  function App() {
193
- return (
194
- <WorkspaceProvider chatPanel={ChatPanel} workspaceId="proj-1">
195
- <IdeLayout />
196
- </WorkspaceProvider>
197
- )
49
+ return <ChatPanel apiBaseUrl="http://localhost:3001" />
198
50
  }
199
51
  ```
200
52
 
201
- ---
202
-
203
- ## Package Surfaces
204
-
205
- | Import | Environment | What You Get |
206
- |--------|-------------|--------------|
207
- | `@hachej/boring-agent` | Browser | `ChatPanel`, `SessionToolbar`, primitives, hooks, `theme.css` |
208
- | `@hachej/boring-agent/server` | Node | `createAgentApp`, routes, harness, sandbox, workspace adapters |
209
- | `@hachej/boring-agent/front` | Browser | Frontend-specific (same as top-level, explicit subpath) |
210
- | `@hachej/boring-agent/shared` | Any | `AgentHarness`, `Workspace`, `Sandbox`, `AgentTool`, `SessionStore` interfaces |
211
- | `@hachej/boring-agent/front/styles.css` | Browser | CSS custom properties for theming |
212
- | `@hachej/boring-agent/eval` | Node | Evaluation toolkit for agent behavior |
213
-
214
- ---
215
-
216
53
  ## Configuration
217
54
 
218
- ### Environment Variables
219
-
220
- | Variable | Required | Default | Description |
221
- |----------|----------|---------|-------------|
222
- | `ANTHROPIC_API_KEY` | Yes | — | Anthropic API key for Claude |
223
- | `BORING_AGENT_MODE` | No | `direct` | `direct`, `local`, or `vercel-sandbox` |
224
- | `BORING_AGENT_WORKSPACE_ROOT` | No | `.` | Root directory for workspace |
225
- | `BORING_AGENT_DEFAULT_MODEL_PROVIDER` | No | `anthropic` | Default model provider |
226
- | `BORING_AGENT_DEFAULT_MODEL_ID` | No | `claude-sonnet-4-6` | Default model ID |
227
- | `VERCEL_OIDC_TOKEN` | Remote only | — | Required for `vercel-sandbox` mode |
228
- | `PORT` | No | `5200` | Server port |
229
- | `HOST` | No | `localhost` | Server host |
230
-
231
- ### Config File
232
-
233
- `boring.app.toml` (optional, for embedded mode):
234
-
235
- ```toml
236
- [runtime]
237
- mode = "local" # direct | local | vercel-sandbox
238
-
239
- [model]
240
- default_provider = "anthropic"
241
- default_id = "claude-sonnet-4-6"
242
- ```
243
-
244
- ---
245
-
246
- ## Troubleshooting
247
-
248
- | Error | Cause | Fix |
249
- |-------|-------|-----|
250
- | `ANTHROPIC_API_KEY not set` | Missing API key | `export ANTHROPIC_API_KEY=sk-ant-...` |
251
- | `bwrap not found` (local mode) | bubblewrap not installed | `sudo apt install bubblewrap` (Debian/Ubuntu) |
252
- | `port already in use` | Port 5200 occupied | `PORT=5201 npx @hachej/boring-agent` |
253
- | `workspace root not found` | Invalid `BORING_AGENT_WORKSPACE_ROOT` | Point to an existing directory |
254
- | `Vercel sandbox auth failed` (remote mode) | Missing/invalid OIDC token | Set `VERCEL_OIDC_TOKEN` |
255
- | `model provider not supported` | Unknown provider in config | Use `anthropic` (only supported provider in v1) |
256
-
257
- ---
258
-
259
- ## Limitations
260
-
261
- - **Single model provider**: Only Anthropic (Claude) is supported in v1. The harness interface is designed to accept others, but only `anthropic` is wired.
262
- - **No multi-user auth**: The agent is single-workspace-per-instance. Multi-user auth, billing, and workspace CRUD belong to `@hachej/boring-core`.
263
- - **No git UI**: The agent runs git via `bash`, but there's no status bar, diff pane, or branch picker. When git UI lands, thin routes will be added.
264
- - **Plugin loading is local-only**: Pi plugins load in the backend Node process. They're disabled in `vercel-sandbox` mode for security.
265
- - **No browser-agent mode yet**: The `AgentHarness` interface has a `placement: "browser"` option, but no browser harness is implemented.
266
- - **No MCP tool integration**: Not in scope for v1.
267
-
268
- ---
269
-
270
- ## FAQ
271
-
272
- **Q: What's the difference between `direct` and `local` mode?**
273
- A: `direct` runs bash commands with no sandbox — the agent has full access to your machine. `local` wraps commands in bubblewrap (`bwrap`), which provides filesystem and process isolation on Linux. Use `direct` for macOS dev; use `local` on Linux servers.
274
-
275
- **Q: Can I use OpenAI or other model providers?**
276
- A: Not in v1. Only Anthropic's Claude is wired up. The harness interface is provider-agnostic — community PRs for other providers are welcome.
277
-
278
- **Q: How do sessions persist?**
279
- A: Via pi-coding-agent's JSONL session files under `${workdir}/.pi/sessions/`. The `PiSessionStore` reads and manages lifecycle. SQLite and IndexedDB implementations are planned.
280
-
281
- **Q: Can I add custom tools to the agent?**
282
- A: Yes. Use the `CatalogDeps` pattern to build tools that bind to `Workspace` and `Sandbox`. For pi-native tools, register them via pi's extension system (`pi.extensions` in config). In `vercel-sandbox` mode, extensions are disabled.
283
-
284
- **Q: What's the UI bridge for?**
285
- A: It lets the agent programmatically open files, panels, and surfaces in the workbench. The agent calls `exec_ui({ kind: "openFile", params: { path: "src/index.ts" } })` and the panel opens. It's a typed pubsub bus between backend and frontend.
55
+ Set an API key for the model provider (e.g. `ANTHROPIC_API_KEY`). Common env
56
+ vars: `BORING_AGENT_MODE` (default `direct`), `BORING_AGENT_WORKSPACE_ROOT`
57
+ (default cwd), `BORING_AGENT_PORT`, and the `BORING_AGENT_DEFAULT_MODEL*` /
58
+ `BORING_AGENT_CUSTOM_MODEL*` / `BORING_AGENT_INFOMANIAK*` provider settings. See
59
+ [docs/runtime.md](./docs/runtime.md) and [docs/API.md](./docs/API.md).
286
60
 
287
- **Q: How does stream resumption work?**
288
- A: The server wraps the harness's event stream in a per-turn ring buffer. Disconnected clients reconnect via `GET /api/v1/agent/pi-chat/:sessionId/events?from=<seq>`; the replay buffer serves missed events.
61
+ ## Documentation
289
62
 
290
- ---
63
+ See [docs/README.md](./docs/README.md) for the full doc index — architecture,
64
+ the export surfaces (`/front`, `/server`, `/shared`, `/eval`), runtime modes and
65
+ provisioning, theming, plugins, error codes, and risk/cost notes.
291
66
 
292
- *About Contributions:* Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via `gh` and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
67
+ ## Contributions
293
68
 
294
- ---
69
+ I do not accept outside contributions for my projects: I don't have the
70
+ bandwidth to review them, and it's my name on the result. Issues and bug reports
71
+ are welcome; PRs may be used to illustrate a fix, but I won't merge them
72
+ directly — I'll have Claude or Codex review submissions via `gh` and
73
+ independently decide whether and how to address them.
295
74
 
296
75
  ## License
297
76
 
@@ -215,6 +215,20 @@ interface WorkspaceChangeEvent {
215
215
  oldPath?: string;
216
216
  mtimeMs?: number;
217
217
  }
218
+ /**
219
+ * Result of a watcher's readiness probe. `ok: false` means the
220
+ * implementation decided it cannot observe this workspace (e.g. the
221
+ * tree is too large to watch without harming the host process) —
222
+ * hosts should tell clients to fall back rather than wait for events
223
+ * that will never come.
224
+ */
225
+ type WorkspaceWatcherReadiness = {
226
+ ok: true;
227
+ } | {
228
+ ok: false;
229
+ reason: string;
230
+ message?: string;
231
+ };
218
232
  interface WorkspaceWatcher {
219
233
  /**
220
234
  * Add a listener for change events. Returns an unsubscribe fn —
@@ -223,6 +237,13 @@ interface WorkspaceWatcher {
223
237
  * does NOT tear down the watcher.
224
238
  */
225
239
  subscribe(listener: (event: WorkspaceChangeEvent) => void): () => void;
240
+ /**
241
+ * Optional readiness probe. Implementations with a startup guard
242
+ * (workspace-size check, native module availability, …) resolve it
243
+ * once the underlying source is observing — or with `ok: false`
244
+ * when observation was refused. Absent → assume always ready.
245
+ */
246
+ whenReady?(): Promise<WorkspaceWatcherReadiness>;
226
247
  /**
227
248
  * Tear down the underlying observation source (chokidar instance,
228
249
  * sandbox emitter binding, …). Idempotent. Subscribers added after
@@ -535,6 +535,8 @@ interface PiChatPanelProps {
535
535
  serverResourcesEnabled?: boolean;
536
536
  mentionedFiles?: string[] | (() => string[]);
537
537
  commands?: SlashCommand[];
538
+ /** Built-in slash command names to omit from the composer command registry. */
539
+ excludeBuiltinCommands?: string[];
538
540
  toolRenderers?: ToolRendererOverrides;
539
541
  createRemoteSession?: (options: RemotePiSessionOptions) => RemotePiSession;
540
542
  remoteSessionOptions?: UsePiSessionsOptions['remoteSessionOptions'];
@@ -552,7 +554,7 @@ interface PiChatPanelProps {
552
554
  onComposerStop?: () => void;
553
555
  onComposerBlockerAction?: (blocker: ComposerBlocker, action: string) => void;
554
556
  }
555
- declare function PiChatPanel({ sessionId, extraCommands, apiBaseUrl, workspaceId, storageScope, requestHeaders, storage, fetch, className, chrome, debug, showSessions, hotReloadEnabled, suggestions, emptyState, emptyPlacement, composerPlaceholder, initialDraft, autoSubmitInitialDraft, onDraftRestored, onAutoSubmitInitialDraftAccepted, onAutoSubmitInitialDraftSettled, model, defaultModel, availableModels, thinkingLevel, thinkingControl, serverResourcesEnabled, mentionedFiles, commands, toolRenderers, createRemoteSession, remoteSessionOptions, hydrateMessages, workspaceWarmupStatus, onSessionReset, onBeforeSubmit, onReloadAgentPlugins, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onData, onOpenArtifact, composerBlockers, onComposerStop, onComposerBlockerAction, }: PiChatPanelProps): react_jsx_runtime.JSX.Element;
557
+ declare function PiChatPanel({ sessionId, extraCommands, apiBaseUrl, workspaceId, storageScope, requestHeaders, storage, fetch, className, chrome, debug, showSessions, hotReloadEnabled, suggestions, emptyState, emptyPlacement, composerPlaceholder, initialDraft, autoSubmitInitialDraft, onDraftRestored, onAutoSubmitInitialDraftAccepted, onAutoSubmitInitialDraftSettled, model, defaultModel, availableModels, thinkingLevel, thinkingControl, serverResourcesEnabled, mentionedFiles, commands, excludeBuiltinCommands, toolRenderers, createRemoteSession, remoteSessionOptions, hydrateMessages, workspaceWarmupStatus, onSessionReset, onBeforeSubmit, onReloadAgentPlugins, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onData, onOpenArtifact, composerBlockers, onComposerStop, onComposerBlockerAction, }: PiChatPanelProps): react_jsx_runtime.JSX.Element;
556
558
 
557
559
  interface DebugDrawerProps {
558
560
  apiBaseUrl?: string;
@@ -8534,7 +8534,7 @@ function SlashCommandPicker({ query, commands, onSelect, onDismiss }) {
8534
8534
  autoComplete: "off"
8535
8535
  }
8536
8536
  ),
8537
- groups.length > 1 && /* @__PURE__ */ jsx30("div", { className: "flex flex-wrap gap-1 border-b border-border/50 px-2 py-1.5", role: "tablist", "aria-label": "Filter by plugin", children: [ALL_PLUGINS, ...groups].map((g) => {
8537
+ /* @__PURE__ */ jsx30("div", { className: "flex flex-wrap gap-1 border-b border-border/50 px-2 py-1.5", role: "tablist", "aria-label": "Filter by plugin", children: [ALL_PLUGINS, ...groups].map((g) => {
8538
8538
  const selected = plugin === g;
8539
8539
  return /* @__PURE__ */ jsx30(
8540
8540
  "button",
@@ -9173,6 +9173,7 @@ function errorMessage2(error, fallback) {
9173
9173
  import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
9174
9174
  var DebugDrawer2 = lazy(() => import("../DebugDrawer-2PUDZ2RL.js").then((m) => ({ default: m.DebugDrawer })));
9175
9175
  var EMPTY_COMMANDS = [];
9176
+ var EMPTY_COMMAND_NAMES = [];
9176
9177
  var EMPTY_BLOCKERS = [];
9177
9178
  function PiChatPanel({
9178
9179
  sessionId,
@@ -9205,6 +9206,7 @@ function PiChatPanel({
9205
9206
  serverResourcesEnabled = true,
9206
9207
  mentionedFiles,
9207
9208
  commands = EMPTY_COMMANDS,
9209
+ excludeBuiltinCommands = EMPTY_COMMAND_NAMES,
9208
9210
  toolRenderers,
9209
9211
  createRemoteSession,
9210
9212
  remoteSessionOptions,
@@ -9363,12 +9365,16 @@ function PiChatPanel({
9363
9365
  setLocalSubmittedSessionId(void 0);
9364
9366
  }, []);
9365
9367
  const registry = useMemo12(() => {
9366
- const effectiveBuiltins = hotReloadEnabled ? builtinCommands : builtinCommands.filter((command) => command.name !== "reload");
9368
+ const excludedBuiltins = new Set(excludeBuiltinCommands);
9369
+ const effectiveBuiltins = builtinCommands.filter((command) => {
9370
+ if (!hotReloadEnabled && command.name === "reload") return false;
9371
+ return !excludedBuiltins.has(command.name);
9372
+ });
9367
9373
  const next = createCommandRegistry(effectiveBuiltins);
9368
9374
  for (const command of extraCommands ?? []) next.register(command);
9369
9375
  for (const command of commands) next.register(command);
9370
9376
  return next;
9371
- }, [apiBaseUrl, commands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
9377
+ }, [apiBaseUrl, commands, excludeBuiltinCommands, extraCommands, hotReloadEnabled, normalizedRequestHeaders, serverResourcesEnabled, serverSkillsRefreshKey, storageScope]);
9372
9378
  const commandsStamp = useServerCommands({
9373
9379
  registry,
9374
9380
  requestHeaders: normalizedRequestHeaders,
@@ -1,5 +1,5 @@
1
- import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, F as FileSearch, P as PluginRestartWarning, A as AgentTool, q as AgentHarnessFactory } from '../agentPluginEvents-CKrZLW3g.js';
2
- export { r as AgentHarnessFactoryInput } from '../agentPluginEvents-CKrZLW3g.js';
1
+ import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, F as FileSearch, P as PluginRestartWarning, A as AgentTool, q as AgentHarnessFactory } from '../agentPluginEvents-ZoOjcb5J.js';
2
+ export { r as AgentHarnessFactoryInput } from '../agentPluginEvents-ZoOjcb5J.js';
3
3
  import { Sandbox as Sandbox$1 } from '@vercel/sandbox';
4
4
  import { FastifyInstance, FastifyRequest, FastifyPluginAsync } from 'fastify';
5
5
  import { PackageSource, ExtensionFactory, SettingsManager } from '@mariozechner/pi-coding-agent';