@hachej/boring-agent 0.1.16 → 0.1.18

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 +243 -54
  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,229 @@
1
+ # Pi-native chat history projection
2
+
3
+ ## Problem
4
+
5
+ Pi has the correct chat model: a session is an ordered sequence of messages, and native follow-ups are just later user messages consumed by the running agent. The current browser adapter still treats follow-ups as special display artifacts because AI SDK reconstructs one active assistant message per request and can merge or misplace multi-turn chunks in a single stream.
6
+
7
+ This causes fragile behavior:
8
+
9
+ - queued messages may require custom insertion/splitting.
10
+ - assistant text can merge across turns unless part IDs are namespaced.
11
+ - tool/reasoning chunks from a follow-up can attach to the wrong AI SDK assistant message.
12
+ - browser `/messages` persistence can race or store a partial text-only projection.
13
+
14
+ ## Goal
15
+
16
+ Follow-up should be special only at the input API boundary:
17
+
18
+ ```ts
19
+ // while pi is idle
20
+ piSession.prompt(text)
21
+
22
+ // while pi is streaming
23
+ piSession.followUp(text)
24
+ ```
25
+
26
+ After pi accepts/consumes the message, rendering/history should not distinguish normal messages from follow-up messages. The browser should render ordered pi messages like pi's own web UI does.
27
+
28
+ ## Reference: pi web-ui
29
+
30
+ `earendil-works/pi/packages/web-ui` renders from pi session state and direct events:
31
+
32
+ - subscribes to `AgentSession` events (`message_start`, `message_update`, `message_end`, `agent_start/end`, `turn_start/end`).
33
+ - renders stable `session.state.messages`.
34
+ - renders the active streaming assistant separately to avoid duplicate stable+streaming display.
35
+
36
+ Because `@boring/agent` runs pi on the server, the browser cannot read `AgentSession.state` directly. We need a server-to-browser projection of pi message events.
37
+
38
+ ## Review feedback integrated
39
+
40
+ Opus reviewed this plan and returned **revise**. The important corrections are now part of the plan:
41
+
42
+ - avoid split-brain migration: do not let AI SDK own first-turn history while pi projection owns follow-up history.
43
+ - define stable browser DTOs rather than exposing opaque pi internals.
44
+ - include stable part keys for text/reasoning/tool state.
45
+ - attach tool results to the owning assistant tool part by `toolCallId`.
46
+ - add monotonic per-session `seq` for ordering and eventual resume.
47
+ - solve persistence/hydration in the same pass, not later.
48
+ - use `clientNonce` + client/server sequence for optimistic queued follow-up reconciliation; do not rely on text matching.
49
+
50
+ ## User decisions
51
+
52
+ The user accepted these choices:
53
+
54
+ 1. Hydration source: read pi JSONL/session entries on demand and convert to UI messages if possible.
55
+ 2. Out-of-order follow-up POST: reject with stable `followup_out_of_order` error first.
56
+ 3. Pi mode scope: pi event reducer owns the whole session from first message.
57
+ 4. Optimistic reconciliation: use `clientNonce` + `clientSeq`.
58
+ 5. AI SDK assistant chunks in pi mode: suppress assistant text/reasoning/tool chunks at the AI SDK adapter layer; emit `data-pi-*` events instead, then the frontend reducer converts those into normal `UIMessage.parts` for existing text/reasoning/tool renderers. This does **not** remove UI formatting.
59
+ 6. Review artifacts: move review output under `packages/agent/docs/plans/reviews/`.
60
+
61
+ ## Decision
62
+
63
+ Build a pi-message projection channel and reducer from turn 0.
64
+
65
+ AI SDK remains useful as HTTP/SSE transport machinery, but it is not the chat history authority in pi mode. Pi events are the history authority for the whole session.
66
+
67
+ ## Server contract
68
+
69
+ Emit browser-safe pi message chunks for all pi messages, not just follow-ups. Every event carries a monotonic `seq` scoped to the boring session.
70
+
71
+ ```ts
72
+ type PiChatEvent =
73
+ | { type: 'pi-mode'; seq: number; enabled: true }
74
+ | { type: 'pi-agent-start'; seq: number }
75
+ | { type: 'pi-agent-end'; seq: number }
76
+ | { type: 'pi-message-start'; seq: number; messageId: string; role: 'user' | 'assistant'; clientNonce?: string; text?: string }
77
+ | { type: 'pi-message-end'; seq: number; messageId: string; role: 'user' | 'assistant'; final: PiUiMessageSnapshot }
78
+ | { type: 'pi-text-start'; seq: number; messageId: string; partId: string }
79
+ | { type: 'pi-text-delta'; seq: number; messageId: string; partId: string; delta: string }
80
+ | { type: 'pi-text-end'; seq: number; messageId: string; partId: string; text?: string }
81
+ | { type: 'pi-reasoning-start'; seq: number; messageId: string; partId: string }
82
+ | { type: 'pi-reasoning-delta'; seq: number; messageId: string; partId: string; delta: string }
83
+ | { type: 'pi-reasoning-end'; seq: number; messageId: string; partId: string; text?: string }
84
+ | { type: 'pi-tool-call-start'; seq: number; messageId: string; toolCallId: string; toolName: string }
85
+ | { type: 'pi-tool-call-delta'; seq: number; messageId: string; toolCallId: string; delta: unknown }
86
+ | { type: 'pi-tool-call-end'; seq: number; messageId: string; toolCallId: string; input: unknown }
87
+ | { type: 'pi-tool-result'; seq: number; messageId: string; toolCallId: string; output: unknown; isError?: boolean }
88
+
89
+ type PiUiMessageSnapshot = {
90
+ id: string
91
+ role: 'user' | 'assistant'
92
+ parts: UIMessage['parts']
93
+ }
94
+ ```
95
+
96
+ AI SDK data chunk names:
97
+
98
+ - `data-pi-message-start`
99
+ - `data-pi-message-update`
100
+ - `data-pi-message-end`
101
+ - `data-pi-agent-start`
102
+ - `data-pi-agent-end`
103
+
104
+ In pi mode, the server must not send assistant text/reasoning/tool chunks that mutate AI SDK's active assistant history. It can still send data chunks, status chunks, and transport finish/error chunks. No split brain.
105
+
106
+ `data-pi-mode { enabled: true }` is emitted before the first content event so the client switches to the pi reducer for the whole session.
107
+
108
+ ## Client reducer
109
+
110
+ Maintain `piMessages: UIMessage[]` derived from pi events.
111
+
112
+ Rules:
113
+
114
+ 1. `pi-message-start user`
115
+ - append/update a normal user message using pi `messageId`.
116
+ - if it matches a locally queued pending follow-up, mark that pending entry consumed.
117
+
118
+ 2. `pi-message-start assistant`
119
+ - append a normal assistant message at the correct ordered position.
120
+
121
+ 3. Part events
122
+ - `pi-text-*` update text parts by `{ messageId, partId }`.
123
+ - `pi-reasoning-*` update reasoning parts by `{ messageId, partId }`.
124
+ - `pi-tool-call-*` update tool parts by `{ messageId, toolCallId }`.
125
+ - `pi-tool-result` locates the owning assistant tool part by `toolCallId` and sets output/error state. Do not append standalone tool result bubbles.
126
+
127
+ 4. `pi-message-end assistant`
128
+ - replace/complete the assistant from pi's final message snapshot.
129
+ - if streaming deltas were missing, synthesize final text from the snapshot.
130
+
131
+ 5. Local queued follow-up while streaming
132
+ - generate `clientNonce` and append an optimistic user bubble with status `queued`/`waiting`.
133
+ - post to `/followup` using FIFO serialization and include `{ clientNonce, clientSeq }`.
134
+ - when pi emits the real user message with matching `clientNonce`, replace/mark the optimistic bubble as committed and adopt pi `messageId`.
135
+ - if no nonce is available (legacy server), fallback to FIFO only, never text equality as primary identity.
136
+
137
+ 6. Duplicate events are idempotent by `seq`, `messageId`, and part key.
138
+
139
+ ## FIFO follow-up posting
140
+
141
+ Client must serialize follow-up POSTs:
142
+
143
+ ```txt
144
+ send q1(clientSeq=1) -> await 202 -> send q2(clientSeq=2) -> await 202 -> send q3(clientSeq=3)
145
+ ```
146
+
147
+ Do not fire all queued POSTs concurrently. Pi executes follow-ups in arrival order, so client must preserve arrival order.
148
+
149
+ Server should also track the next expected `clientSeq` per active session while a stream is open. If an out-of-order follow-up arrives, either:
150
+
151
+ - reject with `409 followup_out_of_order`, or
152
+ - hold it server-side until missing earlier sequence arrives.
153
+
154
+ Default recommendation: reject with stable error code first; add hold/reorder later only if UX needs retries.
155
+
156
+ ## Persistence and hydration
157
+
158
+ This is part of the implementation, not a later enhancement.
159
+
160
+ - Do not manually persist text-only projected history to `/messages`.
161
+ - Add a server endpoint to return canonical pi session messages converted to `UIMessage[]`.
162
+ - On reload, hydrate from canonical pi history.
163
+ - While pi mode is enabled, client `/messages` persistence is disabled or ignored for chat history.
164
+ - `/messages` may remain for non-pi/fallback harnesses only.
165
+
166
+ Open design choice: the canonical endpoint can either:
167
+
168
+ 1. read pi session JSONL and convert entries to UI messages on demand; or
169
+ 2. maintain a server-side projection cache during streaming and persist that projection.
170
+
171
+ Prefer 1 if pi JSONL exposes enough stable message data; otherwise use 2 with pi event replay.
172
+
173
+ ## Rendering
174
+
175
+ Use the existing `ChatPanel` message renderer, but feed it pi-projected `UIMessage[]` when pi event mode is active.
176
+
177
+ Waiting follow-ups:
178
+
179
+ - render optimistic queued user bubble in italic
180
+ - muted/different background
181
+ - small `Waiting…` label
182
+ - after pi emits the real user message, render as normal user bubble
183
+
184
+ Tool/reasoning follow-up turns:
185
+
186
+ - must render under the assistant message that owns them.
187
+ - no tool/reasoning chunks from follow-up turns may attach to the first assistant message.
188
+
189
+ ## Ordering and resume
190
+
191
+ Each `data-pi-*` event gets monotonic `seq`.
192
+
193
+ Client reducer ignores events with `seq <= lastAppliedSeq`.
194
+
195
+ Initial implementation must at least support full rehydrate from canonical pi messages on page load. Streaming resume via `sinceSeq` can be added if the existing HTTP stream-resume path needs it, but dropped-stream recovery must never rely on a stale partial client projection.
196
+
197
+ ## Tests
198
+
199
+ Unit:
200
+
201
+ - reducer handles user -> assistant text.
202
+ - reducer handles user -> assistant reasoning -> text.
203
+ - reducer handles user -> assistant tool call -> tool result -> text, with tool result attached by `toolCallId`.
204
+ - reducer handles 3 queued follow-ups in FIFO order.
205
+ - duplicate pi start/end events do not duplicate messages.
206
+ - duplicate/out-of-order `seq` events are ignored or rejected as specified.
207
+ - optimistic queued message reconciles by `clientNonce`, including duplicate text messages.
208
+
209
+ Server:
210
+
211
+ - emits pi message events for initial user/assistant.
212
+ - emits pi message events for consumed follow-up user/assistant.
213
+ - emits `data-pi-mode` before content.
214
+ - suppresses AI SDK assistant chunks while pi mode is enabled so they do not mutate AI SDK history.
215
+ - canonical pi hydration endpoint returns the same ordered messages after reload.
216
+ - follow-up POST rejects or handles out-of-order `clientSeq`.
217
+
218
+ Browser/integration:
219
+
220
+ - real playground + OpenRouter Qwen 3.6.
221
+ - queue three text follow-ups; assert exact user/assistant interleaving.
222
+ - queue a tool follow-up (`list files` / `open package.json`) while first turn streams; assert tool UI appears under the follow-up assistant turn, not the first assistant.
223
+
224
+ ## Non-goals
225
+
226
+ - Do not replace pi runtime.
227
+ - Do not invent a separate history truth.
228
+ - Do not persist lossy projected text-only history.
229
+ - Do not make follow-up a separate chat concept after pi consumes it; it is just a normal user message.