@hachej/boring-agent 0.1.17 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{sandbox-handle-store-hK76cTjn.d.ts → agentPluginEvents-zyIvVjsA.d.ts} +28 -32
  2. package/dist/{chunk-F3CE5CNW.js → chunk-B5JECXMG.js} +5 -7
  3. package/dist/front/index.d.ts +23 -7
  4. package/dist/front/index.js +764 -421
  5. package/dist/front/styles.css +61 -0
  6. package/dist/{tool-ui-DSmWuqGe.d.ts → harness-DRrTn_5T.d.ts} +43 -24
  7. package/dist/server/index.d.ts +79 -10
  8. package/dist/server/index.js +251 -60
  9. package/dist/shared/index.d.ts +5 -3
  10. package/dist/shared/index.js +3 -1
  11. package/dist/tool-ui-DIFNGwYd.d.ts +20 -0
  12. package/docs/ACCESSIBILITY.md +55 -0
  13. package/docs/API.md +64 -0
  14. package/docs/CSP.md +40 -0
  15. package/docs/ERROR_CODES.md +54 -0
  16. package/docs/KNOWN_LIMITATIONS.md +100 -0
  17. package/docs/MIGRATION.md +50 -0
  18. package/docs/PERFORMANCE.md +68 -0
  19. package/docs/PLUGINS.md +108 -0
  20. package/docs/README.md +17 -0
  21. package/docs/RISKS-MULTI-TAB.md +46 -0
  22. package/docs/STYLING.md +71 -0
  23. package/docs/UI-SHADCN.md +56 -0
  24. package/docs/VERCEL_COSTS.md +103 -0
  25. package/docs/plans/AGENT_EVAL_FRAMEWORK.md +466 -0
  26. package/docs/plans/agent-package-spec.md +1777 -0
  27. package/docs/plans/harness-followup-capabilities.md +440 -0
  28. package/docs/plans/harness-tool-ui-capabilities.md +144 -0
  29. package/docs/plans/pi-followup-history-projection.md +229 -0
  30. package/docs/plans/pi-tools-migration.md +1061 -0
  31. package/docs/plans/reviews/pi-followup-history-codex-review.md +11 -0
  32. package/docs/plans/reviews/pi-followup-history-gpt-review.md +64 -0
  33. package/docs/plans/reviews/pi-followup-history-opus-review.md +43 -0
  34. package/docs/plans/reviews/pi-followup-history-xai-review.md +43 -0
  35. package/docs/plans/vercel-base-snapshot-template-plan.md +527 -0
  36. package/docs/plans/vercel-persistent-sandbox-adapter.md +553 -0
  37. package/docs/runtime.md +56 -0
  38. package/docs/tools.md +75 -0
  39. package/package.json +4 -3
@@ -0,0 +1,440 @@
1
+ # Harness follow-up capabilities plan
2
+
3
+ ## Problem
4
+
5
+ The current `followup-pi-history` branch proves that pi-native follow-up can fix ordering/duplication for queued chat messages, but the implementation leaks pi-specific stream semantics into the shared chat UI:
6
+
7
+ - `ChatPanel` reduces `data-pi-*` chunks directly.
8
+ - Generic AI SDK message persistence is disabled in the panel.
9
+ - Follow-up posting assumes the harness can accept `/followup` while a stream is active.
10
+
11
+ That is wrong for pluggable harnesses. DeepAgent / AI SDK Agent integrations are already designed around the AI SDK UI Message Stream Protocol: one message submission produces one assistant response stream. They do not expose a pi-style `followUp()` API for injecting another user message into a currently running stream.
12
+
13
+ ## Research notes
14
+
15
+ ### AI SDK Agent
16
+
17
+ AI SDK Agent exposes a per-call interface:
18
+
19
+ ```ts
20
+ agent.generate({ messages | prompt, abortSignal, ... })
21
+ agent.stream({ messages | prompt, abortSignal, ... })
22
+ ```
23
+
24
+ `createAgentUIStream({ agent, uiMessages, abortSignal })` consumes a UI message array and streams one assistant response as `UIMessageChunk`s. It does not advertise native user-message queueing while the stream is active.
25
+
26
+ ### `useChat`
27
+
28
+ Current AI SDK React `sendMessage()` pushes/replaces a user message and immediately calls `makeRequest()`. `makeRequest()` creates a new `activeResponse` and starts a transport request. There is no built-in queued-user-message behavior for `sendMessage()` while status is `submitted`/`streaming`.
29
+
30
+ Therefore, for ordinary AI SDK/DeepAgent transports, safe behavior is:
31
+
32
+ ```txt
33
+ ready -> sendMessage()
34
+ busy -> block send, or app-owned local queue that drains after ready
35
+ ```
36
+
37
+ ### DeepAgent
38
+
39
+ DeepAgent / DeepAgentSDK documentation and package metadata indicate AI SDK integration via the AI SDK UI Message Stream Protocol. It should not need to emit Boring/pi-specific DTOs. It should be usable as a normal AI SDK Agent/transport path.
40
+
41
+ ### Pi
42
+
43
+ Pi is special because pi sessions support native follow-up queueing:
44
+
45
+ ```ts
46
+ piSession.followUp(text)
47
+ ```
48
+
49
+ That API lets a second user turn be queued while the first response is still streaming. This should be treated as pi-harness internal machinery, exposed only through an explicit capability.
50
+
51
+ ## Target design
52
+
53
+ Separate three concerns:
54
+
55
+ 1. **Composer busy policy** — what the UI does when user presses send while status is busy.
56
+ 2. **Harness follow-up capability** — whether the backend can accept a busy-time follow-up.
57
+ 3. **Stream/history protocol** — how assistant/user turns are represented to `useChat`.
58
+
59
+ The common ChatPanel must not assume `data-pi-*` for every harness.
60
+
61
+ ## Capability model
62
+
63
+ Add frontend-visible runtime capabilities, fetched or embedded with session/runtime metadata. Prefer **not** to mutate the minimal `AgentHarness` interface unless implementation proves it is necessary; keep capabilities at the app/session/route metadata layer so the harness seam remains small and stable.
64
+
65
+ ```ts
66
+ export interface AgentRuntimeCapabilities {
67
+ /**
68
+ * Runtime can accept a user follow-up while the current response is busy.
69
+ * If false, composer send is disabled while busy.
70
+ */
71
+ nativeFollowUp: boolean
72
+
73
+ /**
74
+ * AI SDK `useChat().messages` is the canonical history source.
75
+ * If false, the harness/adapter owns canonical history projection.
76
+ */
77
+ aiSdkOwnsHistory: boolean
78
+ }
79
+ ```
80
+
81
+ Initial defaults come from runtime/session metadata, not from inspecting the harness interface:
82
+
83
+ ```ts
84
+ nativeFollowUp = runtimeCapabilities.nativeFollowUp ?? false
85
+ aiSdkOwnsHistory = runtimeCapabilities.aiSdkOwnsHistory ?? true
86
+ ```
87
+
88
+ Pi runtime metadata can opt into:
89
+
90
+ ```ts
91
+ {
92
+ nativeFollowUp: true,
93
+ aiSdkOwnsHistory: false, // temporary: pi adapter owns history projection
94
+ }
95
+ ```
96
+
97
+ `aiSdkOwnsHistory: false` is not a new shared protocol. It is an explicit session/harness-scoped escape hatch for adapters where AI SDK message reconstruction is not the canonical source. In the current implementation, only pi uses this path, via temporary `data-pi-*` projection, while we verify whether pi can emit fully transparent AI SDK `UIMessageChunk`s for inline native follow-up.
98
+
99
+ DeepAgent / AI SDK Agent harness should use:
100
+
101
+ ```ts
102
+ {
103
+ nativeFollowUp: false,
104
+ aiSdkOwnsHistory: true,
105
+ }
106
+ ```
107
+
108
+ A future frontend-owned queue/drain mode can be added later if needed, but it is not part of this first-pass capability surface.
109
+
110
+ ## Composer behavior
111
+
112
+ Let:
113
+
114
+ ```ts
115
+ const isBusy = status === 'submitted' || status === 'streaming'
116
+ const sendDisabled = !hasMessage || (isBusy && !capabilities.nativeFollowUp)
117
+ ```
118
+
119
+ Behavior:
120
+
121
+ | Capability | Busy send button | Busy Enter | Action |
122
+ |---|---:|---:|---|
123
+ | `nativeFollowUp: false` | disabled | no-op | Composer may stay editable; show hint: `Wait for the current response to finish.` |
124
+ | `nativeFollowUp: true` | enabled | queues | Show waiting bubble, POST `/followup`, harness owns queue. |
125
+
126
+ For this branch, do not implement frontend queue/drain for non-native runtimes.
127
+
128
+ ## Pi-native behavior
129
+
130
+ When `nativeFollowUp === true` and user sends while busy:
131
+
132
+ 1. Frontend appends an optimistic queued user bubble.
133
+ 2. Frontend POSTs `/api/v1/agent/chat/:sessionId/followup` with `clientSeq` + `clientNonce`.
134
+ 3. Server validates sequence/idempotency.
135
+ 4. Pi harness calls `piSession.followUp(...)`.
136
+ 5. Pi harness owns ordering and consumption.
137
+ 6. Frontend removes the waiting state when harness emits the consumed/user turn signal.
138
+
139
+ Important: the UI should treat waiting bubbles as display-only; it should not mutate AI SDK canonical messages except for the native pi projection path while it exists.
140
+
141
+ ## Non-pi behavior
142
+
143
+ When `nativeFollowUp === false` and user sends while busy:
144
+
145
+ - Do not POST `/followup`.
146
+ - Do not call `sendMessage()`.
147
+ - Keep composer text/files intact.
148
+ - Disable send button and prevent Enter submit.
149
+ - Display helper copy near send/composer.
150
+
151
+ DeepAgent and AI SDK Agent harnesses therefore remain protocol-transparent.
152
+
153
+ ## History protocol direction
154
+
155
+ ### Short-term
156
+
157
+ Keep adapter-owned projection isolated behind `aiSdkOwnsHistory === false`.
158
+
159
+ - `ChatPanel` only runs the temporary `data-pi-*` reducer when AI SDK does not own history and the active runtime is the pi projection path.
160
+ - Otherwise, `useChat().messages` remains canonical and existing AI SDK persistence stays enabled.
161
+ - `/messages` pi projection helper should only be applied for adapter-owned/pi-projection sessions, not globally.
162
+
163
+ ### Long-term
164
+
165
+ Prefer transparent AI SDK messages for pi too:
166
+
167
+ - Pi adapter converts pi events into ordinary AI SDK `UIMessageChunk`s where possible.
168
+ - If AI SDK cannot stream multiple assistant/user message boundaries in one response cleanly, keep pi-native projection local to a Pi transport/panel.
169
+ - Do not require DeepAgent or other harnesses to translate into Boring-specific custom data parts.
170
+
171
+ ## Implementation blueprint
172
+
173
+ Locked implementation choices for the first pass:
174
+
175
+ - Adapt the current `followup-pi-history` implementation in place by capability-gating it. Do not pause for a full transparent-AI-SDK rewrite.
176
+ - Non-pi / DeepAgent / AI SDK Agent mode blocks send while busy. Do **not** implement frontend queue/drain yet.
177
+ - Capabilities should come from runtime/session metadata exposed to the frontend, preferably a dedicated `GET /api/v1/agent/capabilities` route.
178
+ - Keep current pi projection as temporary, adapter-owned compatibility behind `aiSdkOwnsHistory === false`.
179
+ - Add a stable unsupported-follow-up error code: `FOLLOWUP_UNSUPPORTED`.
180
+
181
+ This means the next implementation bead should transform the already-working pi follow-up branch into a pluggable design:
182
+
183
+ ```txt
184
+ pi runtime:
185
+ nativeFollowUp = true
186
+ aiSdkOwnsHistory = false
187
+ current waiting bubble + /followup + pi reducer remains active
188
+
189
+ DeepAgent / AI SDK Agent runtime:
190
+ nativeFollowUp = false
191
+ aiSdkOwnsHistory = true
192
+ send blocked while busy
193
+ normal useChat messages/persistence
194
+ no data-pi reducer
195
+ no /followup
196
+ ```
197
+
198
+ ### 1. Define shared capability types
199
+
200
+ Add a shared type near existing shared contracts, e.g. `packages/agent/src/shared/capabilities.ts`:
201
+
202
+ ```ts
203
+ export interface AgentRuntimeCapabilities {
204
+ nativeFollowUp: boolean
205
+ aiSdkOwnsHistory: boolean
206
+ }
207
+
208
+ export const DEFAULT_AGENT_RUNTIME_CAPABILITIES: AgentRuntimeCapabilities = {
209
+ nativeFollowUp: false,
210
+ aiSdkOwnsHistory: true,
211
+ }
212
+ ```
213
+
214
+ Do not make this a required `AgentHarness` field in the first pass. Resolve capabilities from the runtime binding/app configuration.
215
+
216
+ ### 2. Expose capabilities to the frontend
217
+
218
+ Add a small route, locked first-pass shape:
219
+
220
+ ```txt
221
+ GET /api/v1/agent/capabilities
222
+ ```
223
+
224
+ Response:
225
+
226
+ ```json
227
+ {
228
+ "nativeFollowUp": true,
229
+ "aiSdkOwnsHistory": false
230
+ }
231
+ ```
232
+
233
+ For current pi-only `registerAgentRoutes()` runtime binding, return pi capabilities:
234
+
235
+ ```ts
236
+ {
237
+ nativeFollowUp: true,
238
+ aiSdkOwnsHistory: false,
239
+ }
240
+ ```
241
+
242
+ For future DeepAgent / AI SDK Agent route registration, return defaults:
243
+
244
+ ```ts
245
+ {
246
+ nativeFollowUp: false,
247
+ aiSdkOwnsHistory: true,
248
+ }
249
+ ```
250
+
251
+ Capability scope invariant: the capabilities consumed by `ChatPanel`, `/followup`, and `/messages` projection must be derived from the **same runtime/session binding** as chat and catalog routes. Do not use an unrelated app-global constant if workspaces/sessions can resolve to different harnesses. For request-scoped workspaces, this route must call the same workspace/runtime resolution path as chat/catalog.
252
+
253
+ ### 3. Consume capabilities in `ChatPanel`
254
+
255
+ Add capability loading to the frontend hook/panel layer. Initial default before fetch should be safe:
256
+
257
+ ```ts
258
+ nativeFollowUp = false
259
+ aiSdkOwnsHistory = true
260
+ ```
261
+
262
+ Interaction while capabilities load:
263
+
264
+ - Existing history may render using the safe AI SDK path until capabilities arrive.
265
+ - Sending while busy remains blocked because the default is `nativeFollowUp: false`.
266
+ - First non-busy submit may proceed with defaults if capability fetch is still pending; this is acceptable because the normal AI SDK path is the safe baseline.
267
+ - Once capabilities load for a pi runtime, subsequent busy sends can use native follow-up and pi projection activates.
268
+ - Optional polish: disable only the send button until capabilities load if the implementation wants to avoid this temporary downgrade, but this is not required for correctness.
269
+
270
+ Then compute busy-send policy:
271
+
272
+ ```ts
273
+ const isBusy = status === 'submitted' || status === 'streaming'
274
+ const sendDisabled = !hasMessage || (isBusy && !capabilities.nativeFollowUp)
275
+ ```
276
+
277
+ Behavior:
278
+
279
+ - `nativeFollowUp === false`:
280
+ - composer remains editable;
281
+ - send button disabled while busy;
282
+ - Enter submit is a no-op while busy;
283
+ - no queued bubble;
284
+ - no `/followup` request;
285
+ - optional helper copy: `Wait for the current response to finish.`
286
+ - `nativeFollowUp === true`:
287
+ - current pi waiting-bubble behavior is enabled;
288
+ - busy send creates optimistic queued bubble;
289
+ - POST `/api/v1/agent/chat/:sessionId/followup`;
290
+ - pi harness owns actual queueing/ordering.
291
+ - frontend queue/drain:
292
+ - deferred future extension only;
293
+ - do not expose an active queue/drain mode in the first pass;
294
+ - do not implement local drain queue in this branch.
295
+
296
+ ### 4. Gate pi projection in `ChatPanel`
297
+
298
+ All of the following must run only when:
299
+
300
+ ```ts
301
+ aiSdkOwnsHistory === false
302
+ ```
303
+
304
+ - `data-pi-*` reducer into `piMessages`;
305
+ - `data-followup-consumed` handling;
306
+ - projected follow-up assistant tail logic;
307
+ - `rebuildPiMessagesFromDataParts()` effects;
308
+ - pi-specific persistence that strips/projects `data-pi-*`;
309
+ - `persistMessages: false` for `useAgentChat`.
310
+
311
+ When `aiSdkOwnsHistory === true`:
312
+
313
+ - pass `persistMessages: true` / omit `persistMessages: false`;
314
+ - render `messages` from `useChat` directly;
315
+ - do not inspect `data-pi-*`;
316
+ - do not use pi canonical projection helpers.
317
+
318
+ ### 5. Gate server `/followup`
319
+
320
+ `POST /api/v1/agent/chat/:sessionId/followup` must check runtime capabilities before accepting.
321
+
322
+ If unsupported:
323
+
324
+ ```http
325
+ 409 Conflict
326
+ {
327
+ "error": {
328
+ "code": "FOLLOWUP_UNSUPPORTED",
329
+ "message": "follow-up is not supported by this runtime"
330
+ }
331
+ }
332
+ ```
333
+
334
+ Add `FOLLOWUP_UNSUPPORTED` to the canonical error code enum/source (`packages/agent/src/shared/error-codes.ts` and any published error-code docs/tests). Do not return raw ad hoc strings outside that path.
335
+
336
+ If supported, keep existing `clientSeq`/`clientNonce` idempotency and pi `harness.followUp(...)` path.
337
+
338
+ ### 6. Gate server history projection
339
+
340
+ `projectPiDataMessages()` should only run for sessions/runtimes where `aiSdkOwnsHistory === false` and the active adapter is the current pi projection path.
341
+
342
+ For `aiSdkOwnsHistory === true`, `/messages` should save the incoming `UIMessage[]` normally, only applying generic safe stripping such as removing large `data:` file URLs.
343
+
344
+ ### 7. Tests
345
+
346
+ Required tests for the first pass:
347
+
348
+ - Capabilities route:
349
+ - pi runtime returns `nativeFollowUp: true` + `aiSdkOwnsHistory: false`;
350
+ - default/non-pi test fixture returns `nativeFollowUp: false` + `aiSdkOwnsHistory: true`.
351
+ - Composer behavior:
352
+ - `nativeFollowUp: false` disables send while busy;
353
+ - Enter while busy does not submit;
354
+ - no waiting bubble is created;
355
+ - no `/followup` request is made.
356
+ - Pi native behavior:
357
+ - `nativeFollowUp: true` preserves current busy-send waiting bubble;
358
+ - POST `/followup` still includes `clientSeq` + `clientNonce`;
359
+ - consumed marker clears waiting state.
360
+ - History ownership:
361
+ - `aiSdkOwnsHistory: true` uses normal `useChat().messages` and normal persistence;
362
+ - `aiSdkOwnsHistory: false` keeps existing pi follow-up ordering and canonical persistence tests green for the current pi adapter.
363
+ - Server route:
364
+ - unsupported `/followup` returns stable `FOLLOWUP_UNSUPPORTED`;
365
+ - supported duplicate `{ clientSeq, clientNonce }` remains idempotent;
366
+ - stale different nonce remains rejected.
367
+
368
+ ### 8. Refactor target for the current branch
369
+
370
+ The existing branch already contains working pieces:
371
+
372
+ - `ChatPanel` pending follow-up queue/waiting bubble UI;
373
+ - serialized `/followup` posting with `clientSeq`/`clientNonce`;
374
+ - pi `data-pi-*` reducer and canonical persistence;
375
+ - server-side pi DTO projection and follow-up idempotency;
376
+ - real browser smoke coverage for pi native follow-up.
377
+
378
+ Do not throw this away. Refactor it as follows:
379
+
380
+ ```ts
381
+ const adapterOwnsHistory = !capabilities.aiSdkOwnsHistory
382
+ ```
383
+
384
+ Then gate current behavior:
385
+
386
+ - if `adapterOwnsHistory` for the current pi adapter, enable current `data-pi-*` reducer/persistence;
387
+ - if not `adapterOwnsHistory`, leave `useChat().messages` canonical and keep normal AI SDK persistence;
388
+ - if `canNativeFollowUp && isBusy`, use current queued bubble + `/followup` posting path;
389
+ - if `!canNativeFollowUp && isBusy`, block send and keep composer text/files intact;
390
+ - keep pi browser smoke tests as regression coverage;
391
+ - add non-pi capability tests to prove no pi-specific code path activates.
392
+
393
+ This achieves both goals in one implementation pass: preserve pi message queue correctness and restore a cleaner adapter boundary for future DeepAgent/AI SDK Agent harnesses.
394
+
395
+ ### 9. Docs
396
+
397
+ Document follow-up modes in `packages/agent/docs/plans/agent-package-spec.md` or a follow-up interface doc.
398
+
399
+ State explicitly:
400
+
401
+ - DeepAgent / AI SDK Agent uses `nativeFollowUp: false` initially.
402
+ - DeepAgent / AI SDK Agent uses `aiSdkOwnsHistory: true` and the normal AI SDK UI Message Stream Protocol.
403
+ - Pi native follow-up is an optimization/adapter behavior, not a shared chat protocol.
404
+
405
+ ## PR review: chat cleanup issues
406
+
407
+ Plan file path: `packages/agent/docs/plans/harness-followup-capabilities.md`.
408
+
409
+ The current PR proves the pi follow-up/history behavior, but it still leaves cleanup work before this can be a stable shared chat surface:
410
+
411
+ 1. **`ChatPanel` still owns pi protocol details.** `packages/agent/src/front/ChatPanel.tsx` directly reduces `data-pi-*`, tracks pending follow-ups, rebuilds persisted pi history, and renders projected assistant tails. Extract this into a pi projection/transport helper or gate every path behind `aiSdkOwnsHistory === false`; the common panel should not become the pi adapter.
412
+ 2. **Capabilities are not wired through the runtime boundary yet.** The current code still behaves as if every runtime has pi semantics: `persistMessages: false` is unconditional, busy submit always queues `/followup`, and there is no frontend-visible capabilities route/default. Add the shared capability type and route before exposing this to non-pi harnesses.
413
+ 3. **Busy-send behavior is unsafe for non-native runtimes.** `handleSubmit()` queues a follow-up whenever `status` is busy. For DeepAgent / AI SDK Agent runtimes, busy send must be disabled/no-op with composer contents preserved; it must not create waiting bubbles or POST `/followup`.
414
+ 4. **`/followup` currently reports success even when unsupported.** `packages/agent/src/server/http/routes/chat.ts` calls `runtime.harness.followUp?.(...)` and returns `202` even if the harness has no `followUp`. It should resolve capabilities first and return stable `FOLLOWUP_UNSUPPORTED` before sequence/idempotency state is mutated.
415
+ 5. **Server history projection is global instead of capability-gated.** `projectPiDataMessages()` is applied from the generic `/messages` route. That projection must only run for the pi adapter-owned-history path; normal AI SDK runtimes should persist their `UIMessage[]` with only generic safe stripping.
416
+ 6. **Client and server duplicate pi projection logic.** `rebuildPiMessagesFromDataParts()` in `packages/agent/src/front/piChatProjection.ts` and `projectPiDataMessages()` in `packages/agent/src/server/http/routes/chat.ts` can drift on text parts, reasoning, tool parts, and message ordering. Hard-refactor follow-up: move projection logic into a tested platform-safe shared helper, e.g. `packages/agent/src/shared/pi-chat-projection.ts`, or make one side authoritative.
417
+ 7. **In-flight follow-up posting can outlive a session switch.** `postPendingFollowUps()` has no abort/generation guard. If `sessionId` changes while a POST loop is running, stale queued work can still retry or mutate refs. Add a per-session generation token or abort controller.
418
+ 8. **Display state and canonical state can disagree.** `displayMessages` may use `piMessages`/projected tails while actions such as regenerate use `messages` for last-message checks. Once projection is gated/extracted, action eligibility should be computed from the same canonical display/history source.
419
+ 9. **Test coverage should lock the cleanup, not just pi success.** Add non-pi tests proving: busy submit does not POST `/followup`; `persistMessages` stays enabled when `aiSdkOwnsHistory === true`; `/followup` returns `FOLLOWUP_UNSUPPORTED`; and `/messages` does not run pi projection for default runtimes.
420
+
421
+ ## Related tool UI plan
422
+
423
+ Harness/plugin-specific tool activation and plugin-owned tool UI are adjacent but independently shippable. See [`harness-tool-ui-capabilities.md`](./harness-tool-ui-capabilities.md).
424
+
425
+ ## Open questions
426
+
427
+ 1. Where should capabilities be resolved for frontend: session metadata, `/models`, `/capabilities`, or `createAgentApp` injected config? Current recommendation: session/runtime metadata or `/capabilities`, not a required `AgentHarness` field.
428
+ 2. Should a future release add frontend-owned queue/drain for non-native runtimes, or should unsupported runtimes always block send while busy?
429
+ 3. Can pi adapter emit fully transparent AI SDK multi-message streams without custom `data-pi-*` history projection?
430
+
431
+ ## Recommendation
432
+
433
+ Do not merge current `data-pi-*` behavior as unconditional ChatPanel logic. First isolate it behind `aiSdkOwnsHistory === false` and gate busy-send follow-up behind `nativeFollowUp === true`. Treat adapter-owned projection as temporary pi-only compatibility for the current implementation, not a shared frontend protocol.
434
+
435
+ This gives us:
436
+
437
+ - Pi: native follow-up works.
438
+ - DeepAgent / Agent SDK: normal AI SDK protocol remains clean.
439
+ - Other harnesses: composer safely blocks send while busy.
440
+ - Future: optional frontend-owned queue/drain can be added without touching harnesses.
@@ -0,0 +1,144 @@
1
+ # Harness tool UI capabilities plan
2
+
3
+ ## Problem
4
+
5
+ Harnesses and plugins can contribute agent tools, but the frontend must not assume every runtime has the same tools or the same rich UI affordances. Pi-specific tools such as a subagent tool should be additive harness/plugin contributions, not core shared assumptions.
6
+
7
+ This plan splits tool activation and tool UI from the follow-up/history capability work. It follows the same runtime capability principle: active runtime/session metadata and catalogs drive what is available.
8
+
9
+ Related plan: [`harness-followup-capabilities.md`](./harness-followup-capabilities.md).
10
+
11
+ ## Current tool flow
12
+
13
+ - `AgentTool` is the shared server-side tool contract.
14
+ - `registerAgentRoutes()` builds core tools from the runtime bundle:
15
+ - harness/shell tools (`bash`, `execute_isolated_code` when available),
16
+ - filesystem tools (`read`, `write`, `edit`, `find`, `grep`, `ls`),
17
+ - upload tools.
18
+ - Hosts can add tools with `extraTools` / `getExtraTools`.
19
+ - Plugins can add tools through the pi plugin loader.
20
+ - `mergeTools()` combines standard, host, scoped, and plugin tools; later registrations can override by name.
21
+ - The active harness receives the final tool list. The pi harness currently adapts that list into pi `customTools`.
22
+ - The frontend catalog endpoint exposes active tool names/descriptions/schemas.
23
+ - `ChatPanel` renders tool parts generically, with optional custom renderers via `toolRenderers`.
24
+
25
+ ## Harness-specific tools
26
+
27
+ A harness may activate additional harness-specific tools without making them core.
28
+
29
+ Examples:
30
+
31
+ ```txt
32
+ pi runtime only:
33
+ pi.subagent / subagent
34
+
35
+ deepagent runtime only:
36
+ deepagent.handoff / memory / etc.
37
+ ```
38
+
39
+ Guidelines:
40
+
41
+ 1. Core tools should be the portable workspace/tooling baseline.
42
+ 2. Harness-specific tools should be contributed by the harness/runtime binding, not assumed by the shared frontend.
43
+ 3. Tool names should be stable and collision-aware. Prefer namespacing for harness-only semantics if the tool is not portable (`pi.subagent`, `deepagent.handoff`) unless compatibility with an existing tool name is intentional.
44
+ 4. Prompt snippets/system prompt entries must be derived from the active tool list only.
45
+ 5. The catalog must reflect the current runtime/session tool list; the UI must not assume every harness has every tool.
46
+
47
+ ## Plugin-owned tool UI
48
+
49
+ - Today, plugins can bring server-side agent tools.
50
+ - Today, the host/app shell can provide matching frontend renderers through `ChatPanel.toolRenderers`.
51
+ - There is not yet a formal automatic plugin contract for bundling `server tool + frontend tool renderer` as one unit.
52
+
53
+ Future plugin shape could be explicit app-shell composition, for example:
54
+
55
+ ```ts
56
+ // server side
57
+ export const tools = [myTool]
58
+
59
+ // frontend side, bundled by the host app
60
+ export const toolRenderers = {
61
+ my_tool: MyToolRenderer,
62
+ }
63
+ ```
64
+
65
+ Do not dynamically load arbitrary plugin frontend code from the server at runtime. Frontend renderers should be explicit imports/registrations in the host/app shell so bundling, trust, and versioning remain clear.
66
+
67
+ ## Pi subagent overlay example
68
+
69
+ A pi subagent tool should reuse the original pi subagent package/tool for execution. Boring should not fork or reimplement subagent orchestration just to get UI.
70
+
71
+ Preferred shape:
72
+
73
+ ```txt
74
+ original pi subagent package
75
+ = execution behavior, prompts, tool semantics
76
+
77
+ boring pi-subagent overlay plugin
78
+ = wraps/adapts the original tool
79
+ = adds Boring-specific structured UI details/events
80
+ = exports/registers a frontend tool renderer through app-shell composition
81
+ ```
82
+
83
+ Server overlay responsibilities:
84
+
85
+ - import or construct the original pi subagent tool;
86
+ - expose it as an `AgentTool` under a stable tool name, e.g. `pi.subagent` or the upstream-compatible `subagent` if intentional;
87
+ - preserve upstream behavior and prompt semantics;
88
+ - enrich tool output with structured UI metadata when available.
89
+
90
+ Example result shape:
91
+
92
+ ```ts
93
+ return {
94
+ content: [{ type: 'text', text: finalSummary }],
95
+ details: {
96
+ uiKind: 'pi-subagent',
97
+ task,
98
+ agent,
99
+ status: 'done',
100
+ transcript,
101
+ steps,
102
+ },
103
+ }
104
+ ```
105
+
106
+ Frontend overlay responsibilities:
107
+
108
+ ```ts
109
+ export const toolRenderers = {
110
+ 'pi.subagent': PiSubagentToolRenderer,
111
+ subagent: PiSubagentToolRenderer, // optional upstream-compatible alias
112
+ }
113
+ ```
114
+
115
+ The renderer should consume structured `input`, `output`, and UI details/events from the tool part. If the upstream tool only returns plain text, the renderer can fall back to a basic summary UI. Rich display requires the overlay to surface structured details or streaming updates.
116
+
117
+ ## Tool UI metadata path
118
+
119
+ Open implementation note: today tool parts reliably expose `input`/`output`; verify whether `ToolResult.details` is preserved into frontend tool parts. If not, add an explicit, typed tool UI metadata path before relying on rich renderers.
120
+
121
+ Do not encode rich UI state only as untyped human text.
122
+
123
+ Possible future shape:
124
+
125
+ ```ts
126
+ interface ToolUiMetadata {
127
+ rendererId?: string
128
+ displayGroup?: string
129
+ icon?: string
130
+ details?: unknown
131
+ }
132
+ ```
133
+
134
+ This can be surfaced either through tool result details if the stream adapter preserves them, or through a dedicated typed data part keyed by `toolCallId`.
135
+
136
+ ## Open questions
137
+
138
+ 1. Should tool capability metadata include renderer hints (`rendererId`, display group, icon), or should renderers remain keyed only by tool name for now?
139
+ 2. Should plugin-owned renderer registration live in `@boring/agent/front`, the app shell, or a future plugin manifest format?
140
+ 3. Should pi subagent use the upstream-compatible `subagent` name, the namespaced `pi.subagent` name, or expose both?
141
+
142
+ ## Recommendation
143
+
144
+ Keep harness/plugin-specific tools and renderers additive and capability/catalog-driven. Do not hardcode harness-only tools into shared chat assumptions. For pi subagent, reuse the original subagent package for execution and add only a Boring overlay for structured UI metadata plus a frontend renderer.