@bytespell/shella 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-plugins/agent/AGENT_SPEC.md +611 -0
- package/bundled-plugins/agent/README.md +7 -0
- package/bundled-plugins/agent/components.json +24 -0
- package/bundled-plugins/agent/eslint.config.js +23 -0
- package/bundled-plugins/agent/index.html +13 -0
- package/bundled-plugins/agent/package-lock.json +12140 -0
- package/bundled-plugins/agent/package.json +62 -0
- package/bundled-plugins/agent/public/vite.svg +1 -0
- package/bundled-plugins/agent/server.js +631 -0
- package/bundled-plugins/agent/src/App.tsx +755 -0
- package/bundled-plugins/agent/src/assets/react.svg +1 -0
- package/bundled-plugins/agent/src/components/ui/alert-dialog.tsx +182 -0
- package/bundled-plugins/agent/src/components/ui/badge.tsx +45 -0
- package/bundled-plugins/agent/src/components/ui/button.tsx +60 -0
- package/bundled-plugins/agent/src/components/ui/card.tsx +94 -0
- package/bundled-plugins/agent/src/components/ui/combobox.tsx +294 -0
- package/bundled-plugins/agent/src/components/ui/dropdown-menu.tsx +253 -0
- package/bundled-plugins/agent/src/components/ui/field.tsx +225 -0
- package/bundled-plugins/agent/src/components/ui/input-group.tsx +147 -0
- package/bundled-plugins/agent/src/components/ui/input.tsx +19 -0
- package/bundled-plugins/agent/src/components/ui/label.tsx +24 -0
- package/bundled-plugins/agent/src/components/ui/select.tsx +185 -0
- package/bundled-plugins/agent/src/components/ui/separator.tsx +26 -0
- package/bundled-plugins/agent/src/components/ui/switch.tsx +31 -0
- package/bundled-plugins/agent/src/components/ui/textarea.tsx +18 -0
- package/bundled-plugins/agent/src/index.css +131 -0
- package/bundled-plugins/agent/src/lib/utils.ts +6 -0
- package/bundled-plugins/agent/src/main.tsx +11 -0
- package/bundled-plugins/agent/src/reducer.test.ts +359 -0
- package/bundled-plugins/agent/src/reducer.ts +255 -0
- package/bundled-plugins/agent/src/store.ts +379 -0
- package/bundled-plugins/agent/src/types.ts +98 -0
- package/bundled-plugins/agent/src/utils.test.ts +393 -0
- package/bundled-plugins/agent/src/utils.ts +158 -0
- package/bundled-plugins/agent/tsconfig.app.json +32 -0
- package/bundled-plugins/agent/tsconfig.json +13 -0
- package/bundled-plugins/agent/tsconfig.node.json +26 -0
- package/bundled-plugins/agent/vite.config.ts +14 -0
- package/bundled-plugins/agent/vitest.config.ts +17 -0
- package/bundled-plugins/terminal/README.md +7 -0
- package/bundled-plugins/terminal/index.html +24 -0
- package/bundled-plugins/terminal/package-lock.json +3346 -0
- package/bundled-plugins/terminal/package.json +38 -0
- package/bundled-plugins/terminal/server.ts +265 -0
- package/bundled-plugins/terminal/src/App.tsx +153 -0
- package/bundled-plugins/terminal/src/TERMINAL_SPEC.md +404 -0
- package/bundled-plugins/terminal/src/main.tsx +9 -0
- package/bundled-plugins/terminal/src/store.ts +114 -0
- package/bundled-plugins/terminal/tsconfig.json +22 -0
- package/bundled-plugins/terminal/vite.config.ts +10 -0
- package/package.json +1 -2
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# Agent Plugin UI Specification
|
|
2
|
+
|
|
3
|
+
This document specifies how the agent plugin UI should handle all RPC events from the pi-agent and render the conversation.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Architecture Overview](#architecture-overview)
|
|
8
|
+
2. [State Management](#state-management)
|
|
9
|
+
3. [Event Handling](#event-handling)
|
|
10
|
+
4. [Message Rendering](#message-rendering)
|
|
11
|
+
5. [Streaming Behavior](#streaming-behavior)
|
|
12
|
+
6. [Tool Execution](#tool-execution)
|
|
13
|
+
7. [User Interactions](#user-interactions)
|
|
14
|
+
8. [Edge Cases](#edge-cases)
|
|
15
|
+
9. [Components](#components)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Architecture Overview
|
|
20
|
+
|
|
21
|
+
### Data Flow
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ WebSocket │
|
|
26
|
+
│ │
|
|
27
|
+
│ Client (App.tsx) ◄────────────────────► Server (server.js) │
|
|
28
|
+
│ │ │ │
|
|
29
|
+
│ │ │ │
|
|
30
|
+
│ ▼ ▼ │
|
|
31
|
+
│ React State pi --mode rpc │
|
|
32
|
+
│ - messages[] (stdio JSON lines) │
|
|
33
|
+
│ - sessionState │
|
|
34
|
+
│ - activeToolCalls │
|
|
35
|
+
│ - streamingContent │
|
|
36
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Key Principle
|
|
40
|
+
|
|
41
|
+
**Messages are the source of truth.** The UI builds its render tree from the `messages[]` array. Streaming updates modify the last message in-place. Tool executions are tracked separately only for showing progress indicators.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## State Management
|
|
46
|
+
|
|
47
|
+
### Primary State
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
interface AppState {
|
|
51
|
+
// Connection
|
|
52
|
+
status: 'connecting' | 'connected' | 'disconnected' | 'error'
|
|
53
|
+
error: string | null
|
|
54
|
+
currentCwd: string | null
|
|
55
|
+
|
|
56
|
+
// Session
|
|
57
|
+
sessionState: SessionState | null
|
|
58
|
+
sessionStats: SessionStats | null
|
|
59
|
+
|
|
60
|
+
// Messages - THE source of truth for rendering
|
|
61
|
+
messages: AgentMessage[]
|
|
62
|
+
|
|
63
|
+
// Streaming indicators (not for rendering content, just for UI feedback)
|
|
64
|
+
isStreaming: boolean // Is the agent currently processing?
|
|
65
|
+
streamingMessageIndex: number | null // Which message is being streamed?
|
|
66
|
+
|
|
67
|
+
// Active tool calls (for progress indicators during execution)
|
|
68
|
+
activeToolCalls: Map<string, ActiveToolCall>
|
|
69
|
+
|
|
70
|
+
// UI state
|
|
71
|
+
availableModels: AvailableModel[]
|
|
72
|
+
pendingExtensionRequest: ExtensionUIRequest | null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface ActiveToolCall {
|
|
76
|
+
toolCallId: string
|
|
77
|
+
toolName: string
|
|
78
|
+
args: Record<string, unknown>
|
|
79
|
+
status: 'running' | 'done' | 'error'
|
|
80
|
+
partialOutput: string | null // Streaming output for bash/etc
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface SessionState {
|
|
84
|
+
model?: Model
|
|
85
|
+
thinkingLevel: ThinkingLevel
|
|
86
|
+
isStreaming: boolean
|
|
87
|
+
isCompacting: boolean
|
|
88
|
+
steeringMode: 'all' | 'one-at-a-time'
|
|
89
|
+
followUpMode: 'all' | 'one-at-a-time'
|
|
90
|
+
sessionFile?: string
|
|
91
|
+
sessionId: string
|
|
92
|
+
autoCompactionEnabled: boolean
|
|
93
|
+
messageCount: number
|
|
94
|
+
pendingMessageCount: number // Queued steering/follow-up messages
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Message Types
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
type AgentMessage =
|
|
102
|
+
| UserMessage
|
|
103
|
+
| AssistantMessage
|
|
104
|
+
| ToolResultMessage
|
|
105
|
+
| BashExecutionMessage
|
|
106
|
+
| CompactionSummaryMessage
|
|
107
|
+
| CustomMessage
|
|
108
|
+
|
|
109
|
+
interface UserMessage {
|
|
110
|
+
role: 'user'
|
|
111
|
+
content: string | ContentBlock[]
|
|
112
|
+
timestamp: number
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface AssistantMessage {
|
|
116
|
+
role: 'assistant'
|
|
117
|
+
content: ContentBlock[] // text, thinking, toolCall blocks
|
|
118
|
+
model: string
|
|
119
|
+
usage: Usage
|
|
120
|
+
stopReason: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'
|
|
121
|
+
timestamp: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ToolResultMessage {
|
|
125
|
+
role: 'toolResult'
|
|
126
|
+
toolCallId: string
|
|
127
|
+
toolName: string
|
|
128
|
+
content: ContentBlock[]
|
|
129
|
+
details?: unknown // Tool-specific display data
|
|
130
|
+
isError: boolean
|
|
131
|
+
timestamp: number
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type ContentBlock =
|
|
135
|
+
| { type: 'text'; text: string }
|
|
136
|
+
| { type: 'thinking'; thinking: string }
|
|
137
|
+
| { type: 'toolCall'; id: string; name: string; arguments: Record<string, unknown> }
|
|
138
|
+
| { type: 'image'; data: string; mimeType: string }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Event Handling
|
|
144
|
+
|
|
145
|
+
### Connection Events
|
|
146
|
+
|
|
147
|
+
| Event | Action |
|
|
148
|
+
|-------|--------|
|
|
149
|
+
| `ready` | Set status='connected', set cwd if provided, request initial state |
|
|
150
|
+
| `cwd_changed` | Update cwd, clear messages (new session in new directory) |
|
|
151
|
+
| `error` | Display error message |
|
|
152
|
+
| `process_exit` | Show reconnection UI or error |
|
|
153
|
+
|
|
154
|
+
### Agent Lifecycle Events
|
|
155
|
+
|
|
156
|
+
| Event | Action |
|
|
157
|
+
|-------|--------|
|
|
158
|
+
| `agent_start` | Set isStreaming=true, clear activeToolCalls |
|
|
159
|
+
| `agent_end` | Set isStreaming=false, clear activeToolCalls, request stats |
|
|
160
|
+
|
|
161
|
+
### Message Events
|
|
162
|
+
|
|
163
|
+
| Event | Action |
|
|
164
|
+
|-------|--------|
|
|
165
|
+
| `message_start` | Append message to messages[], set streamingMessageIndex if assistant |
|
|
166
|
+
| `message_update` | Update message at streamingMessageIndex with new content |
|
|
167
|
+
| `message_end` | Clear streamingMessageIndex |
|
|
168
|
+
|
|
169
|
+
**Important:** `message_update` includes `assistantMessageEvent` with granular streaming info:
|
|
170
|
+
- `text_delta` - Append text to current text block
|
|
171
|
+
- `thinking_delta` - Append to thinking block
|
|
172
|
+
- `toolcall_end` - Tool call block complete
|
|
173
|
+
- `done` - Message complete
|
|
174
|
+
|
|
175
|
+
### Tool Execution Events
|
|
176
|
+
|
|
177
|
+
| Event | Action |
|
|
178
|
+
|-------|--------|
|
|
179
|
+
| `tool_execution_start` | Add to activeToolCalls with status='running' |
|
|
180
|
+
| `tool_execution_update` | Update partialOutput in activeToolCalls |
|
|
181
|
+
| `tool_execution_end` | Update status='done' or 'error', keep until toolResult message arrives |
|
|
182
|
+
|
|
183
|
+
**When `message_start` arrives with role='toolResult':**
|
|
184
|
+
- Remove corresponding entry from activeToolCalls (it's now in messages)
|
|
185
|
+
|
|
186
|
+
### Response Events
|
|
187
|
+
|
|
188
|
+
| Event | Action |
|
|
189
|
+
|-------|--------|
|
|
190
|
+
| `response` to `get_state` | Update sessionState |
|
|
191
|
+
| `response` to `get_messages` | Set messages (only on initial load, not during streaming) |
|
|
192
|
+
| `response` to `get_session_stats` | Update sessionStats |
|
|
193
|
+
| `response` to `get_available_models` | Update availableModels |
|
|
194
|
+
|
|
195
|
+
### Extension UI Events
|
|
196
|
+
|
|
197
|
+
| Event | Action |
|
|
198
|
+
|-------|--------|
|
|
199
|
+
| `extension_ui_request` | Show appropriate dialog/input, send response back |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Message Rendering
|
|
204
|
+
|
|
205
|
+
### Render Order
|
|
206
|
+
|
|
207
|
+
Messages are rendered in array order. Each message type has specific rendering:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
┌─────────────────────────────────────────┐
|
|
211
|
+
│ User message (right-aligned bubble) │
|
|
212
|
+
├─────────────────────────────────────────┤
|
|
213
|
+
│ Assistant text (left-aligned bubble) │
|
|
214
|
+
│ - Render markdown │
|
|
215
|
+
│ - Show thinking in collapsible │
|
|
216
|
+
├─────────────────────────────────────────┤
|
|
217
|
+
│ Tool Call Card │
|
|
218
|
+
│ - Show tool name + args │
|
|
219
|
+
│ - While executing: show progress │
|
|
220
|
+
├─────────────────────────────────────────┤
|
|
221
|
+
│ Tool Result Card │
|
|
222
|
+
│ - For bash: show $ command + output │
|
|
223
|
+
│ - For others: show formatted result │
|
|
224
|
+
│ - Error state: red styling │
|
|
225
|
+
├─────────────────────────────────────────┤
|
|
226
|
+
│ Assistant text (continuation) │
|
|
227
|
+
│ - Streaming cursor if active │
|
|
228
|
+
└─────────────────────────────────────────┘
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Splitting Assistant Messages
|
|
232
|
+
|
|
233
|
+
Assistant messages may contain mixed content (text + toolCall + more text). Split for display:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
function splitAssistantContent(content: ContentBlock[]): RenderItem[] {
|
|
237
|
+
const items: RenderItem[] = []
|
|
238
|
+
let textBuffer: ContentBlock[] = []
|
|
239
|
+
|
|
240
|
+
for (const block of content) {
|
|
241
|
+
if (block.type === 'toolCall') {
|
|
242
|
+
// Flush text buffer
|
|
243
|
+
if (textBuffer.length > 0) {
|
|
244
|
+
items.push({ type: 'text-chunk', blocks: textBuffer })
|
|
245
|
+
textBuffer = []
|
|
246
|
+
}
|
|
247
|
+
items.push({ type: 'tool-call', block })
|
|
248
|
+
} else {
|
|
249
|
+
textBuffer.push(block)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Flush remaining text
|
|
254
|
+
if (textBuffer.length > 0) {
|
|
255
|
+
items.push({ type: 'text-chunk', blocks: textBuffer })
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return items
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Content Block Rendering
|
|
263
|
+
|
|
264
|
+
| Block Type | Rendering |
|
|
265
|
+
|------------|-----------|
|
|
266
|
+
| `text` | Markdown with syntax highlighting. Use streaming-capable renderer. |
|
|
267
|
+
| `thinking` | Collapsible "Thinking..." section, muted styling |
|
|
268
|
+
| `toolCall` | Tool call card showing name + arguments (only during streaming before result) |
|
|
269
|
+
| `image` | Inline image |
|
|
270
|
+
|
|
271
|
+
### Tool Result Rendering
|
|
272
|
+
|
|
273
|
+
| Tool | Rendering |
|
|
274
|
+
|------|-----------|
|
|
275
|
+
| `bash` / `Bash` | Mini-terminal: `$ command` header + scrollable output |
|
|
276
|
+
| `Read` | File content with path header |
|
|
277
|
+
| `Write` / `Edit` | Diff view component |
|
|
278
|
+
| `Grep` / `Glob` | Formatted list of matches/files |
|
|
279
|
+
| Other | JSON-formatted arguments + result |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Streaming Behavior
|
|
284
|
+
|
|
285
|
+
### Text Streaming
|
|
286
|
+
|
|
287
|
+
1. On `message_start` (assistant): Add message to array, set streamingMessageIndex
|
|
288
|
+
2. On `message_update`: Replace message at streamingMessageIndex
|
|
289
|
+
3. Show pulsing cursor after last text in streaming message
|
|
290
|
+
4. On `message_end`: Clear streamingMessageIndex, cursor disappears
|
|
291
|
+
|
|
292
|
+
### Tool Execution Streaming
|
|
293
|
+
|
|
294
|
+
1. `tool_execution_start`: Add to activeToolCalls, show card with spinner
|
|
295
|
+
2. `tool_execution_update`: Update partialOutput, show in card (for bash output)
|
|
296
|
+
3. `tool_execution_end`: Mark complete, show success/error indicator
|
|
297
|
+
4. `message_start` (toolResult): Move from activeToolCalls to messages
|
|
298
|
+
|
|
299
|
+
**Visual transition:** The tool card should smoothly transition from "running" state (in activeToolCalls) to "complete" state (in messages) without flashing or jumping.
|
|
300
|
+
|
|
301
|
+
### Render Priority
|
|
302
|
+
|
|
303
|
+
During streaming, render in this order:
|
|
304
|
+
1. All messages from `messages[]` (split as needed)
|
|
305
|
+
2. Active tool calls from `activeToolCalls` (only those not yet in messages)
|
|
306
|
+
3. (Streaming cursor shown inline in last assistant message)
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Tool Execution
|
|
311
|
+
|
|
312
|
+
### Tool Call Card (During Execution)
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
┌────────────────────────────────────────┐
|
|
316
|
+
│ ● bash Running │
|
|
317
|
+
│ $ ls -la │
|
|
318
|
+
├────────────────────────────────────────┤
|
|
319
|
+
│ total 48 │
|
|
320
|
+
│ drwxr-xr-x 12 user staff 384 ... │
|
|
321
|
+
│ ... │
|
|
322
|
+
│ (streaming output) │
|
|
323
|
+
└────────────────────────────────────────┘
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
- Yellow pulsing dot while running
|
|
327
|
+
- Green dot when complete
|
|
328
|
+
- Red dot on error
|
|
329
|
+
- Show command for bash tools
|
|
330
|
+
- Stream partial output as it arrives
|
|
331
|
+
|
|
332
|
+
### Tool Result Card (After Completion)
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
┌────────────────────────────────────────┐
|
|
336
|
+
│ ● $ ls -la │
|
|
337
|
+
├────────────────────────────────────────┤
|
|
338
|
+
│ total 48 │
|
|
339
|
+
│ drwxr-xr-x 12 user staff 384 ... │
|
|
340
|
+
│ ... │
|
|
341
|
+
│ (full output, scrollable) │
|
|
342
|
+
└────────────────────────────────────────┘
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- Green dot for success, red for error
|
|
346
|
+
- Command in header for bash
|
|
347
|
+
- Scrollable output area (max-height with overflow)
|
|
348
|
+
- Error output in red text
|
|
349
|
+
|
|
350
|
+
### Special Tool Rendering
|
|
351
|
+
|
|
352
|
+
**Edit/Write Tools (Diff View):**
|
|
353
|
+
```
|
|
354
|
+
┌────────────────────────────────────────┐
|
|
355
|
+
│ Edit: src/App.tsx │
|
|
356
|
+
├────────────────────────────────────────┤
|
|
357
|
+
│ - old line │
|
|
358
|
+
│ + new line │
|
|
359
|
+
│ unchanged line │
|
|
360
|
+
└────────────────────────────────────────┘
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Read Tool:**
|
|
364
|
+
```
|
|
365
|
+
┌────────────────────────────────────────┐
|
|
366
|
+
│ Read: package.json │
|
|
367
|
+
├────────────────────────────────────────┤
|
|
368
|
+
│ { │
|
|
369
|
+
│ "name": "my-app", │
|
|
370
|
+
│ ... │
|
|
371
|
+
│ } │
|
|
372
|
+
└────────────────────────────────────────┘
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## User Interactions
|
|
378
|
+
|
|
379
|
+
### Sending Messages
|
|
380
|
+
|
|
381
|
+
**Normal prompt:**
|
|
382
|
+
```typescript
|
|
383
|
+
ws.send({ type: 'prompt', message: text })
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**With images:**
|
|
387
|
+
```typescript
|
|
388
|
+
ws.send({ type: 'prompt', message: text, images: [{ type: 'image', data: base64, mimeType: 'image/png' }] })
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Steering (Interrupt)
|
|
392
|
+
|
|
393
|
+
When agent is streaming and user sends a message:
|
|
394
|
+
```typescript
|
|
395
|
+
ws.send({ type: 'steer', message: text })
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- Interrupts current tool execution
|
|
399
|
+
- Message is injected before next LLM turn
|
|
400
|
+
- Show indicator that message was queued for steering
|
|
401
|
+
|
|
402
|
+
### Follow-Up (Queue)
|
|
403
|
+
|
|
404
|
+
Queue a message to run after agent finishes:
|
|
405
|
+
```typescript
|
|
406
|
+
ws.send({ type: 'follow_up', message: text })
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
- Does not interrupt
|
|
410
|
+
- Show indicator that message is queued
|
|
411
|
+
- Show pending count from sessionState.pendingMessageCount
|
|
412
|
+
|
|
413
|
+
### Abort
|
|
414
|
+
|
|
415
|
+
Stop agent execution:
|
|
416
|
+
```typescript
|
|
417
|
+
ws.send({ type: 'abort' })
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
- Show "Stop" button when isStreaming
|
|
421
|
+
- Cancel current tool execution
|
|
422
|
+
- Agent will emit `agent_end`
|
|
423
|
+
|
|
424
|
+
### UI Indicators
|
|
425
|
+
|
|
426
|
+
| State | Indicator |
|
|
427
|
+
|-------|-----------|
|
|
428
|
+
| Agent streaming | Pulsing cursor, "Stop" button |
|
|
429
|
+
| Tool running | Yellow spinner on tool card |
|
|
430
|
+
| Message queued | Badge showing pending count |
|
|
431
|
+
| Compacting | "Compacting..." status |
|
|
432
|
+
| Error | Red error banner |
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Edge Cases
|
|
437
|
+
|
|
438
|
+
### 1. Multiple Tool Calls in One Message
|
|
439
|
+
|
|
440
|
+
Assistant may request multiple tools at once:
|
|
441
|
+
```typescript
|
|
442
|
+
{
|
|
443
|
+
role: 'assistant',
|
|
444
|
+
content: [
|
|
445
|
+
{ type: 'text', text: 'I will read both files...' },
|
|
446
|
+
{ type: 'toolCall', id: 'call_1', name: 'Read', arguments: { path: 'a.txt' } },
|
|
447
|
+
{ type: 'toolCall', id: 'call_2', name: 'Read', arguments: { path: 'b.txt' } }
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Handling:**
|
|
453
|
+
- Split into: text chunk, tool card 1, tool card 2
|
|
454
|
+
- Tool results arrive as separate `toolResult` messages
|
|
455
|
+
- Match by `toolCallId`
|
|
456
|
+
|
|
457
|
+
### 2. Steering Mid-Tool-Execution
|
|
458
|
+
|
|
459
|
+
User steers while tool is running:
|
|
460
|
+
- Tool execution continues until next checkpoint
|
|
461
|
+
- Steering message is queued
|
|
462
|
+
- After tool completes, steering is processed
|
|
463
|
+
- May result in remaining tool calls being skipped
|
|
464
|
+
|
|
465
|
+
### 3. Auto-Compaction
|
|
466
|
+
|
|
467
|
+
Events: `auto_compaction_start`, `auto_compaction_end`
|
|
468
|
+
|
|
469
|
+
**Handling:**
|
|
470
|
+
- Show "Compacting context..." indicator
|
|
471
|
+
- On completion, a `compactionSummary` message may be added
|
|
472
|
+
- Older messages may be removed from context (but kept in history)
|
|
473
|
+
|
|
474
|
+
### 4. Auto-Retry
|
|
475
|
+
|
|
476
|
+
Events: `auto_retry_start`, `auto_retry_end`
|
|
477
|
+
|
|
478
|
+
**Handling:**
|
|
479
|
+
- Show "Retrying... (attempt N of M)"
|
|
480
|
+
- Show countdown timer if delay > 1s
|
|
481
|
+
- On success, continue normally
|
|
482
|
+
- On final failure, show error
|
|
483
|
+
|
|
484
|
+
### 5. Extension UI Requests
|
|
485
|
+
|
|
486
|
+
Agent may request user input via `extension_ui_request`:
|
|
487
|
+
|
|
488
|
+
| Method | UI |
|
|
489
|
+
|--------|-----|
|
|
490
|
+
| `select` | Dropdown/radio buttons |
|
|
491
|
+
| `confirm` | Yes/No dialog |
|
|
492
|
+
| `input` | Text input |
|
|
493
|
+
| `editor` | Multi-line text editor |
|
|
494
|
+
| `notify` | Toast notification |
|
|
495
|
+
|
|
496
|
+
**Response:**
|
|
497
|
+
```typescript
|
|
498
|
+
ws.send({
|
|
499
|
+
type: 'extension_ui_response',
|
|
500
|
+
id: request.id,
|
|
501
|
+
value: userInput // or { cancelled: true }
|
|
502
|
+
})
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 6. Connection Loss
|
|
506
|
+
|
|
507
|
+
- Show disconnected state
|
|
508
|
+
- Auto-reconnect with exponential backoff
|
|
509
|
+
- On reconnect, request `get_state` and `get_messages` to sync
|
|
510
|
+
|
|
511
|
+
### 7. CWD Change
|
|
512
|
+
|
|
513
|
+
- Kills pi process, respawns in new directory
|
|
514
|
+
- Clears messages (new session)
|
|
515
|
+
- Show brief "Switching directory..." indicator
|
|
516
|
+
|
|
517
|
+
### 8. Session Switch
|
|
518
|
+
|
|
519
|
+
User may switch to different session file:
|
|
520
|
+
- Current conversation is saved
|
|
521
|
+
- New session loaded
|
|
522
|
+
- Messages replaced with new session's history
|
|
523
|
+
|
|
524
|
+
### 9. Empty Responses
|
|
525
|
+
|
|
526
|
+
Assistant may return empty text (only tool calls, or aborted):
|
|
527
|
+
- Don't render empty text bubbles
|
|
528
|
+
- Still show tool calls if present
|
|
529
|
+
|
|
530
|
+
### 10. Long-Running Tools
|
|
531
|
+
|
|
532
|
+
Some tools take a long time (build, test):
|
|
533
|
+
- Show elapsed time indicator
|
|
534
|
+
- Stream partial output if available
|
|
535
|
+
- Allow abort via "Stop" button
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Components
|
|
540
|
+
|
|
541
|
+
### Required Components
|
|
542
|
+
|
|
543
|
+
1. **MessageBubble** - User/assistant text messages with markdown
|
|
544
|
+
2. **ToolCallCard** - Tool execution progress (running state)
|
|
545
|
+
3. **ToolResultCard** - Tool result display (completed state)
|
|
546
|
+
4. **BashResultCard** - Specialized for bash with command header
|
|
547
|
+
5. **DiffView** - For Edit/Write tool results
|
|
548
|
+
6. **ThinkingBlock** - Collapsible thinking content
|
|
549
|
+
7. **StreamingCursor** - Pulsing cursor for active streaming
|
|
550
|
+
8. **QueueIndicator** - Shows pending message count
|
|
551
|
+
9. **StatusBar** - Session stats, model, connection status
|
|
552
|
+
10. **DirectoryPicker** - CWD selection
|
|
553
|
+
11. **ExtensionDialog** - Generic dialog for extension UI requests
|
|
554
|
+
|
|
555
|
+
### Component Hierarchy
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
<App>
|
|
559
|
+
<Header>
|
|
560
|
+
<DirectoryPicker />
|
|
561
|
+
<SessionStats />
|
|
562
|
+
<StatusIndicator />
|
|
563
|
+
<SettingsMenu />
|
|
564
|
+
</Header>
|
|
565
|
+
|
|
566
|
+
<MessageList>
|
|
567
|
+
{messages.map(msg => {
|
|
568
|
+
if (msg.role === 'user') return <UserBubble />
|
|
569
|
+
if (msg.role === 'assistant') return <AssistantMessage />
|
|
570
|
+
if (msg.role === 'toolResult') return <ToolResultCard />
|
|
571
|
+
...
|
|
572
|
+
})}
|
|
573
|
+
|
|
574
|
+
{activeToolCalls.map(tool => (
|
|
575
|
+
<ToolCallCard status="running" />
|
|
576
|
+
))}
|
|
577
|
+
</MessageList>
|
|
578
|
+
|
|
579
|
+
<InputArea>
|
|
580
|
+
<Textarea />
|
|
581
|
+
<SendButton /> or <StopButton />
|
|
582
|
+
{pendingCount > 0 && <QueueIndicator count={pendingCount} />}
|
|
583
|
+
</InputArea>
|
|
584
|
+
|
|
585
|
+
{extensionRequest && <ExtensionDialog />}
|
|
586
|
+
</App>
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Implementation Notes
|
|
592
|
+
|
|
593
|
+
### Performance
|
|
594
|
+
|
|
595
|
+
1. **Virtualization:** For long conversations, consider virtualizing the message list
|
|
596
|
+
2. **Memoization:** Memoize message components to avoid re-renders
|
|
597
|
+
3. **Streaming:** Use streaming-capable markdown renderer (e.g., streamdown)
|
|
598
|
+
4. **Debounce:** Debounce rapid `message_update` events if needed
|
|
599
|
+
|
|
600
|
+
### Accessibility
|
|
601
|
+
|
|
602
|
+
1. Keyboard navigation for message list
|
|
603
|
+
2. Screen reader announcements for new messages
|
|
604
|
+
3. Focus management for dialogs
|
|
605
|
+
4. High contrast support for tool status indicators
|
|
606
|
+
|
|
607
|
+
### Mobile
|
|
608
|
+
|
|
609
|
+
1. Responsive layout for smaller screens
|
|
610
|
+
2. Touch-friendly buttons and controls
|
|
611
|
+
3. Swipe gestures for common actions
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Agent Plugin
|
|
2
|
+
|
|
3
|
+
A web UI for AI coding agents. Point it at any directory and start a conversation—the agent can read files, write code, run commands, and iterate on your codebase. Sessions persist across restarts, so you can pick up where you left off.
|
|
4
|
+
|
|
5
|
+
**Usage:** Split a panel, select "Agent", choose a working directory, and start prompting. Switch between sessions or start new ones from the session picker. Each panel instance maintains its own independent session.
|
|
6
|
+
|
|
7
|
+
**Vision:** Your AI pair programmer, always available in a panel alongside your terminal and editor. No context switching to a separate app—just split, prompt, and ship.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-vega",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/index.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
},
|
|
21
|
+
"menuColor": "default",
|
|
22
|
+
"menuAccent": "subtle",
|
|
23
|
+
"registries": {}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Agent</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|