agentgui 1.0.393 → 1.0.395
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/lib/ws-optimizer.js +30 -110
- package/package.json +1 -1
- package/.prd +0 -215
package/lib/ws-optimizer.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// WebSocket Optimization Module
|
|
2
|
-
// Implements batching, rate limiting, compression, deduplication, priority queuing, and monitoring
|
|
3
|
-
|
|
4
1
|
import zlib from 'zlib';
|
|
5
2
|
|
|
6
3
|
const MESSAGE_PRIORITY = {
|
|
@@ -13,7 +10,20 @@ function getPriority(eventType) {
|
|
|
13
10
|
if (MESSAGE_PRIORITY.high.includes(eventType)) return 3;
|
|
14
11
|
if (MESSAGE_PRIORITY.normal.includes(eventType)) return 2;
|
|
15
12
|
if (MESSAGE_PRIORITY.low.includes(eventType)) return 1;
|
|
16
|
-
return 2;
|
|
13
|
+
return 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getBatchInterval(ws) {
|
|
17
|
+
const BATCH_BY_TIER = { excellent: 16, good: 32, fair: 50, poor: 100, bad: 200 };
|
|
18
|
+
const TIER_ORDER = ['excellent', 'good', 'fair', 'poor', 'bad'];
|
|
19
|
+
const tier = ws.latencyTier || 'good';
|
|
20
|
+
const trend = ws.latencyTrend;
|
|
21
|
+
if (trend === 'rising' || trend === 'falling') {
|
|
22
|
+
const idx = TIER_ORDER.indexOf(tier);
|
|
23
|
+
if (trend === 'rising' && idx < TIER_ORDER.length - 1) return BATCH_BY_TIER[TIER_ORDER[idx + 1]] || 32;
|
|
24
|
+
if (trend === 'falling' && idx > 0) return BATCH_BY_TIER[TIER_ORDER[idx - 1]] || 32;
|
|
25
|
+
}
|
|
26
|
+
return BATCH_BY_TIER[tier] || 32;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
class ClientQueue {
|
|
@@ -31,151 +41,76 @@ class ClientQueue {
|
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
add(data, priority) {
|
|
34
|
-
// Deduplication: skip if identical to last message
|
|
35
44
|
if (this.lastMessage === data) return;
|
|
36
45
|
this.lastMessage = data;
|
|
37
|
-
|
|
38
|
-
if (priority ===
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} else {
|
|
43
|
-
this.lowPriority.push(data);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// High priority: flush immediately
|
|
47
|
-
if (priority === 3) {
|
|
48
|
-
this.flushImmediate();
|
|
49
|
-
} else if (!this.timer) {
|
|
50
|
-
this.scheduleFlush();
|
|
51
|
-
}
|
|
46
|
+
if (priority === 3) this.highPriority.push(data);
|
|
47
|
+
else if (priority === 2) this.normalPriority.push(data);
|
|
48
|
+
else this.lowPriority.push(data);
|
|
49
|
+
if (priority === 3) this.flushImmediate();
|
|
50
|
+
else if (!this.timer) this.scheduleFlush();
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
scheduleFlush() {
|
|
55
54
|
const interval = this.ws.latencyTier ? getBatchInterval(this.ws) : 100;
|
|
56
|
-
this.timer = setTimeout(() => {
|
|
57
|
-
this.timer = null;
|
|
58
|
-
this.flush();
|
|
59
|
-
}, interval);
|
|
55
|
+
this.timer = setTimeout(() => { this.timer = null; this.flush(); }, interval);
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
flushImmediate() {
|
|
63
|
-
if (this.timer) {
|
|
64
|
-
clearTimeout(this.timer);
|
|
65
|
-
this.timer = null;
|
|
66
|
-
}
|
|
59
|
+
if (this.timer) { clearTimeout(this.timer); this.timer = null; }
|
|
67
60
|
this.flush();
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
flush() {
|
|
71
64
|
if (this.ws.readyState !== 1) return;
|
|
72
|
-
|
|
73
65
|
const now = Date.now();
|
|
74
66
|
const windowDuration = now - this.windowStart;
|
|
75
|
-
|
|
76
|
-
// Reset rate limit window every second
|
|
77
67
|
if (windowDuration >= 1000) {
|
|
78
68
|
this.messageCount = 0;
|
|
79
69
|
this.bytesSent = 0;
|
|
80
70
|
this.windowStart = now;
|
|
81
71
|
this.rateLimitWarned = false;
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
// Collect messages from all priorities (high first)
|
|
85
|
-
const batch = [
|
|
86
|
-
...this.highPriority.splice(0),
|
|
87
|
-
...this.normalPriority.splice(0, 10),
|
|
88
|
-
...this.lowPriority.splice(0, 5)
|
|
89
|
-
];
|
|
90
|
-
|
|
73
|
+
const batch = [...this.highPriority.splice(0), ...this.normalPriority.splice(0, 10), ...this.lowPriority.splice(0, 5)];
|
|
91
74
|
if (batch.length === 0) return;
|
|
92
|
-
|
|
93
|
-
// Rate limiting: max 100 msg/sec per client
|
|
94
75
|
const messagesThisSecond = this.messageCount + batch.length;
|
|
95
76
|
if (messagesThisSecond > 100) {
|
|
96
77
|
if (!this.rateLimitWarned) {
|
|
97
78
|
console.warn(`[ws-optimizer] Client ${this.ws.clientId} rate limited: ${messagesThisSecond} msg/sec`);
|
|
98
79
|
this.rateLimitWarned = true;
|
|
99
80
|
}
|
|
100
|
-
// Keep high priority, drop some normal/low
|
|
101
81
|
const allowedCount = 100 - this.messageCount;
|
|
102
|
-
if (allowedCount <= 0) {
|
|
103
|
-
// Reschedule remaining
|
|
104
|
-
this.scheduleFlush();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
82
|
+
if (allowedCount <= 0) { this.scheduleFlush(); return; }
|
|
107
83
|
batch.splice(allowedCount);
|
|
108
84
|
}
|
|
109
|
-
|
|
110
|
-
let payload;
|
|
111
|
-
if (batch.length === 1) {
|
|
112
|
-
payload = batch[0];
|
|
113
|
-
} else {
|
|
114
|
-
payload = '[' + batch.join(',') + ']';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Compression for large payloads (>1KB)
|
|
85
|
+
let payload = batch.length === 1 ? batch[0] : '[' + batch.join(',') + ']';
|
|
118
86
|
if (payload.length > 1024) {
|
|
119
87
|
try {
|
|
120
88
|
const compressed = zlib.gzipSync(Buffer.from(payload), { level: 6 });
|
|
121
89
|
if (compressed.length < payload.length * 0.9) {
|
|
122
|
-
// Send compression hint as separate control message
|
|
123
90
|
this.ws.send(JSON.stringify({ type: '_compressed', encoding: 'gzip' }));
|
|
124
91
|
this.ws.send(compressed);
|
|
125
|
-
payload = null;
|
|
92
|
+
payload = null;
|
|
126
93
|
}
|
|
127
|
-
} catch (e) {
|
|
128
|
-
// Fall back to uncompressed
|
|
129
|
-
}
|
|
94
|
+
} catch (e) {}
|
|
130
95
|
}
|
|
131
|
-
|
|
132
|
-
if (payload) {
|
|
133
|
-
this.ws.send(payload);
|
|
134
|
-
}
|
|
135
|
-
|
|
96
|
+
if (payload) this.ws.send(payload);
|
|
136
97
|
this.messageCount += batch.length;
|
|
137
98
|
this.bytesSent += (payload ? payload.length : 0);
|
|
138
|
-
|
|
139
|
-
// Monitor: warn if >1MB/sec sustained for 3+ seconds
|
|
140
99
|
if (windowDuration >= 3000 && this.bytesSent > 3 * 1024 * 1024) {
|
|
141
100
|
const mbps = (this.bytesSent / windowDuration * 1000 / 1024 / 1024).toFixed(2);
|
|
142
101
|
console.warn(`[ws-optimizer] Client ${this.ws.clientId} high bandwidth: ${mbps} MB/sec`);
|
|
143
102
|
}
|
|
144
|
-
|
|
145
|
-
// If there are remaining low-priority messages, schedule next flush
|
|
146
103
|
if (this.normalPriority.length > 0 || this.lowPriority.length > 0) {
|
|
147
104
|
if (!this.timer) this.scheduleFlush();
|
|
148
105
|
}
|
|
149
106
|
}
|
|
150
107
|
|
|
151
108
|
drain() {
|
|
152
|
-
if (this.timer) {
|
|
153
|
-
clearTimeout(this.timer);
|
|
154
|
-
this.timer = null;
|
|
155
|
-
}
|
|
109
|
+
if (this.timer) { clearTimeout(this.timer); this.timer = null; }
|
|
156
110
|
this.flush();
|
|
157
111
|
}
|
|
158
112
|
}
|
|
159
113
|
|
|
160
|
-
function getBatchInterval(ws) {
|
|
161
|
-
const BATCH_BY_TIER = { excellent: 16, good: 32, fair: 50, poor: 100, bad: 200 };
|
|
162
|
-
const TIER_ORDER = ['excellent', 'good', 'fair', 'poor', 'bad'];
|
|
163
|
-
const tier = ws.latencyTier || 'good';
|
|
164
|
-
const trend = ws.latencyTrend;
|
|
165
|
-
|
|
166
|
-
if (trend === 'rising' || trend === 'falling') {
|
|
167
|
-
const idx = TIER_ORDER.indexOf(tier);
|
|
168
|
-
if (trend === 'rising' && idx < TIER_ORDER.length - 1) {
|
|
169
|
-
return BATCH_BY_TIER[TIER_ORDER[idx + 1]] || 32;
|
|
170
|
-
}
|
|
171
|
-
if (trend === 'falling' && idx > 0) {
|
|
172
|
-
return BATCH_BY_TIER[TIER_ORDER[idx - 1]] || 32;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return BATCH_BY_TIER[tier] || 32;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
114
|
class WSOptimizer {
|
|
180
115
|
constructor() {
|
|
181
116
|
this.clientQueues = new Map();
|
|
@@ -183,16 +118,13 @@ class WSOptimizer {
|
|
|
183
118
|
|
|
184
119
|
sendToClient(ws, event) {
|
|
185
120
|
if (ws.readyState !== 1) return;
|
|
186
|
-
|
|
187
121
|
let queue = this.clientQueues.get(ws);
|
|
188
122
|
if (!queue) {
|
|
189
123
|
queue = new ClientQueue(ws);
|
|
190
124
|
this.clientQueues.set(ws, queue);
|
|
191
125
|
}
|
|
192
|
-
|
|
193
126
|
const data = typeof event === 'string' ? event : JSON.stringify(event);
|
|
194
127
|
const priority = typeof event === 'object' ? getPriority(event.type) : 2;
|
|
195
|
-
|
|
196
128
|
queue.add(data, priority);
|
|
197
129
|
}
|
|
198
130
|
|
|
@@ -205,30 +137,18 @@ class WSOptimizer {
|
|
|
205
137
|
}
|
|
206
138
|
|
|
207
139
|
getStats() {
|
|
208
|
-
const stats = {
|
|
209
|
-
clients: this.clientQueues.size,
|
|
210
|
-
totalBytes: 0,
|
|
211
|
-
totalMessages: 0,
|
|
212
|
-
highBandwidthClients: []
|
|
213
|
-
};
|
|
214
|
-
|
|
140
|
+
const stats = { clients: this.clientQueues.size, totalBytes: 0, totalMessages: 0, highBandwidthClients: [] };
|
|
215
141
|
for (const [ws, queue] of this.clientQueues.entries()) {
|
|
216
142
|
stats.totalBytes += queue.bytesSent;
|
|
217
143
|
stats.totalMessages += queue.messageCount;
|
|
218
|
-
|
|
219
144
|
const windowDuration = Date.now() - queue.windowStart;
|
|
220
145
|
if (windowDuration > 0) {
|
|
221
146
|
const mbps = (queue.bytesSent / windowDuration * 1000 / 1024 / 1024);
|
|
222
147
|
if (mbps > 1) {
|
|
223
|
-
stats.highBandwidthClients.push({
|
|
224
|
-
clientId: ws.clientId,
|
|
225
|
-
mbps: mbps.toFixed(2),
|
|
226
|
-
messages: queue.messageCount
|
|
227
|
-
});
|
|
148
|
+
stats.highBandwidthClients.push({ clientId: ws.clientId, mbps: mbps.toFixed(2), messages: queue.messageCount });
|
|
228
149
|
}
|
|
229
150
|
}
|
|
230
151
|
}
|
|
231
|
-
|
|
232
152
|
return stats;
|
|
233
153
|
}
|
|
234
154
|
}
|
package/package.json
CHANGED
package/.prd
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# AgentGUI ACP Compliance PRD
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
Transform AgentGUI into a fully ACP (Agent Connect Protocol) v0.2.3 compliant server.
|
|
5
|
-
|
|
6
|
-
**Current Status**: 100% ACP compliant - All waves completed
|
|
7
|
-
**All Required Features**: Fully implemented and tested
|
|
8
|
-
|
|
9
|
-
**Note on "Slash Commands"**: ACP spec contains no slash command concept. This is purely a client-side UI feature outside ACP scope. If user wants slash commands implemented, that would be a separate UI enhancement task.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Completion Status
|
|
14
|
-
|
|
15
|
-
### ✅ WAVE 1: Foundation (COMPLETED)
|
|
16
|
-
- Database schema extended with ACP tables
|
|
17
|
-
- Thread state management implemented
|
|
18
|
-
- Checkpoint system operational
|
|
19
|
-
|
|
20
|
-
### ✅ WAVE 2: Core ACP APIs (COMPLETED)
|
|
21
|
-
- All 23 ACP endpoints implemented
|
|
22
|
-
- Threads API fully functional
|
|
23
|
-
- Stateless runs supported
|
|
24
|
-
- Agent discovery operational
|
|
25
|
-
|
|
26
|
-
### ✅ WAVE 3: SSE Streaming & Run Control (COMPLETED)
|
|
27
|
-
- SSE streaming endpoints implemented
|
|
28
|
-
- Run cancellation working
|
|
29
|
-
- Event stream format compliant with ACP spec
|
|
30
|
-
|
|
31
|
-
### ✅ WAVE 4: UI Fixes & Optimization (COMPLETED - Enhanced)
|
|
32
|
-
- **4.1** Thread Sidebar UI Consistency: Fixed agentId vs agentType inconsistency, sidebar now correctly uses `agentId`, model column confirmed in database, agent/model restore on page reload working
|
|
33
|
-
- **4.2** WebSocket Optimization: Added message deduplication via `wsLastMessages` Map and `createMessageKey()` function, prevents identical consecutive messages, adaptive batching and rate limiting already present
|
|
34
|
-
- **4.3** Duplicate Displays: Removed redundant agent/model from conversation headers (3 locations) and streaming start event, kept authoritative displays in sidebar and input selectors only
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Additional Enhancements (Non-blocking)
|
|
39
|
-
|
|
40
|
-
### NICE-TO-HAVE 1: Webhook Callbacks
|
|
41
|
-
- Implement webhook support for run status changes
|
|
42
|
-
- POST to webhook URL when run status changes (pending → active → completed)
|
|
43
|
-
- Retry logic: 3 attempts with exponential backoff
|
|
44
|
-
- Store webhook config in run_metadata table
|
|
45
|
-
- Validate webhook URL format on run creation
|
|
46
|
-
|
|
47
|
-
### NICE-TO-HAVE 2: Run Interrupts
|
|
48
|
-
- Support interrupt mechanism for agents that implement it
|
|
49
|
-
- Interrupt types: user feedback request, tool approval, configuration needed
|
|
50
|
-
- Store interrupt state in sessions table
|
|
51
|
-
- API endpoints: GET /runs/{id}/interrupts, POST /runs/{id}/resume with interrupt response
|
|
52
|
-
- UI: show interrupt prompt, collect user input, resume run
|
|
53
|
-
|
|
54
|
-
### NICE-TO-HAVE 3: Enhanced Search & Filtering
|
|
55
|
-
- Full-text search on thread content (messages, agent responses)
|
|
56
|
-
- Filter by agent type, date range, status, metadata fields
|
|
57
|
-
- Search history: recent searches saved per user
|
|
58
|
-
- Autocomplete for search filters
|
|
59
|
-
- Export search results as JSON
|
|
60
|
-
|
|
61
|
-
### NICE-TO-HAVE 4: Thread Templates
|
|
62
|
-
- Save thread configuration as template
|
|
63
|
-
- Templates include: agent, model, initial prompt, working directory
|
|
64
|
-
- Clone thread from template
|
|
65
|
-
- Share templates between users (if multi-user support added)
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Testing Requirements (Per Item)
|
|
70
|
-
|
|
71
|
-
Each implementation item must include:
|
|
72
|
-
1. Execute in plugin:gm:dev: create test run for every endpoint/function
|
|
73
|
-
2. Success paths: valid inputs, expected outputs verified
|
|
74
|
-
3. Error paths: invalid inputs, 404s, 409s, 422s verified
|
|
75
|
-
4. Edge cases: empty results, large payloads, concurrent requests
|
|
76
|
-
5. Integration tests: end-to-end flow (create thread → run → stream → cancel)
|
|
77
|
-
6. Database verification: inspect tables after operations, verify foreign keys
|
|
78
|
-
7. WebSocket verification: subscribe, receive events, verify payload format
|
|
79
|
-
8. SSE verification: curl endpoint, verify event-stream format
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Acceptance Criteria (All Must Pass)
|
|
84
|
-
|
|
85
|
-
### Core ACP Compliance
|
|
86
|
-
- [ ] All 23 ACP endpoints implemented and tested
|
|
87
|
-
- [ ] All ACP data models match spec (Thread, ThreadState, Run, Agent, etc.)
|
|
88
|
-
- [ ] Error responses follow ACP format (ErrorResponse schema)
|
|
89
|
-
- [ ] SSE streaming works with curl: `curl -N /threads/{id}/runs/stream`
|
|
90
|
-
- [ ] Stateless runs work without thread context
|
|
91
|
-
- [ ] Run cancellation kills agent process within 5 seconds
|
|
92
|
-
- [ ] Thread copy duplicates all states and checkpoints
|
|
93
|
-
- [ ] Agent descriptors return valid JSON matching AgentACPDescriptor schema
|
|
94
|
-
|
|
95
|
-
### Database Integrity
|
|
96
|
-
- [ ] No orphaned records after thread/run deletion
|
|
97
|
-
- [ ] Foreign key constraints enforced
|
|
98
|
-
- [ ] Thread status correctly reflects run states
|
|
99
|
-
- [ ] Checkpoint sequences monotonically increase
|
|
100
|
-
- [ ] WAL mode enabled, queries under 100ms for typical operations
|
|
101
|
-
|
|
102
|
-
### UI Consistency
|
|
103
|
-
- [ ] Sidebar shows correct agent for each conversation
|
|
104
|
-
- [ ] Model selection persists after page reload
|
|
105
|
-
- [ ] No duplicate agent/model displays found
|
|
106
|
-
- [ ] Agent/model changes reflected in database immediately
|
|
107
|
-
|
|
108
|
-
### WebSocket Optimization
|
|
109
|
-
- [ ] Streaming progress events batched (max 10/100ms)
|
|
110
|
-
- [ ] Only subscribed clients receive messages
|
|
111
|
-
- [ ] No client exceeds 1MB/sec sustained WebSocket traffic
|
|
112
|
-
- [ ] Message deduplication prevents identical consecutive events
|
|
113
|
-
|
|
114
|
-
### Integration & E2E
|
|
115
|
-
- [ ] Full flow: create thread → start run → stream events → cancel → verify cancelled
|
|
116
|
-
- [ ] Stateless run: create run → stream → complete → verify output
|
|
117
|
-
- [ ] Thread search: create 10 threads → search by metadata → verify correct results
|
|
118
|
-
- [ ] Agent search: search by capability "streaming" → verify all streaming agents returned
|
|
119
|
-
- [ ] Thread copy: create thread with 5 runs → copy → verify new thread has all history
|
|
120
|
-
- [ ] Concurrent runs blocked: start run on thread → start second run → verify 409 conflict
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Migration Strategy
|
|
125
|
-
|
|
126
|
-
### Backward Compatibility
|
|
127
|
-
- Existing conversations map to threads (1:1)
|
|
128
|
-
- Existing sessions map to thread runs
|
|
129
|
-
- `/api/conversations/*` endpoints remain functional (alias to `/threads/*`)
|
|
130
|
-
- Old WebSocket message formats supported alongside new ACP formats
|
|
131
|
-
- No breaking changes to current client code
|
|
132
|
-
|
|
133
|
-
### Rollout Plan
|
|
134
|
-
1. Deploy database schema changes (additive only, no drops)
|
|
135
|
-
2. Deploy new ACP endpoints alongside existing endpoints
|
|
136
|
-
3. Update client to use ACP endpoints where beneficial
|
|
137
|
-
4. Deprecation notice for old endpoints (6 month window)
|
|
138
|
-
5. Remove old endpoints after deprecation period
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Out of Scope
|
|
143
|
-
|
|
144
|
-
- Multi-user authentication/authorization
|
|
145
|
-
- Slash command implementation (not in ACP spec, pure client feature)
|
|
146
|
-
- Agent marketplace or discovery service
|
|
147
|
-
- Real-time collaboration on threads
|
|
148
|
-
- Thread branching/forking (beyond simple copy)
|
|
149
|
-
- Custom agent development framework
|
|
150
|
-
- Billing/metering for agent usage
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Technical Notes
|
|
155
|
-
|
|
156
|
-
### ACP Terminology Mapping
|
|
157
|
-
- AgentGUI "conversations" = ACP "threads"
|
|
158
|
-
- AgentGUI "sessions" = ACP "runs" (stateful, on a thread)
|
|
159
|
-
- AgentGUI "chunks/events" = ACP "run output stream"
|
|
160
|
-
- AgentGUI "claudeSessionId" = ACP checkpoint ID concept
|
|
161
|
-
|
|
162
|
-
### Known Gotchas
|
|
163
|
-
- ACP requires UUID format for thread_id, run_id, agent_id (current AgentGUI uses strings)
|
|
164
|
-
- SSE requires newline-delimited format, different from current JSON streaming
|
|
165
|
-
- Run cancellation must handle agents that don't support it gracefully
|
|
166
|
-
- Thread status "idle" means no pending runs; must validate on run creation
|
|
167
|
-
- Webhook URLs must be validated to prevent SSRF attacks
|
|
168
|
-
|
|
169
|
-
### Performance Targets
|
|
170
|
-
- Thread search: <200ms for 10,000 threads
|
|
171
|
-
- Run creation: <50ms (background processing)
|
|
172
|
-
- SSE streaming: <10ms latency per event
|
|
173
|
-
- WebSocket batch: <100ms accumulation window
|
|
174
|
-
- Database writes: <20ms per transaction
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Dependencies
|
|
179
|
-
|
|
180
|
-
**External**:
|
|
181
|
-
- None (all features implemented with existing dependencies)
|
|
182
|
-
|
|
183
|
-
**Internal**:
|
|
184
|
-
- database.js (extended with new tables/queries)
|
|
185
|
-
- server.js (new route handlers)
|
|
186
|
-
- lib/claude-runner.js (run cancellation support)
|
|
187
|
-
- static/js/client.js (UI consistency fixes)
|
|
188
|
-
- static/js/conversations.js (agent/model persistence)
|
|
189
|
-
- static/js/websocket-manager.js (optimization)
|
|
190
|
-
|
|
191
|
-
**Configuration**:
|
|
192
|
-
- No new env vars required
|
|
193
|
-
- Existing BASE_URL, PORT, STARTUP_CWD remain unchanged
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Success Metrics
|
|
198
|
-
|
|
199
|
-
- ACP compliance score: 0% → 100%
|
|
200
|
-
- API endpoint coverage: 20 → 43 endpoints
|
|
201
|
-
- WebSocket bandwidth: <50% reduction in bytes/sec per client
|
|
202
|
-
- UI consistency issues: 4 identified → 0 remaining
|
|
203
|
-
- Database tables: 5 → 8 (conversations, messages, sessions, events, chunks, thread_states, checkpoints, run_metadata)
|
|
204
|
-
- Test coverage: endpoint tests for all 43 routes, integration tests for all critical flows
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Timeline Estimate
|
|
209
|
-
|
|
210
|
-
- Wave 1 (Foundation): 3 parallel tasks = 1 completion cycle
|
|
211
|
-
- Wave 2 (Core APIs): 3 parallel tasks = 1 completion cycle
|
|
212
|
-
- Wave 3 (Streaming): 2 tasks = 1 completion cycle
|
|
213
|
-
- Wave 4 (UI Fixes): 3 tasks = 1 completion cycle
|
|
214
|
-
|
|
215
|
-
**Total**: 4 completion cycles (waves executed sequentially, items within wave executed in parallel with max 3 concurrent subagents per wave)
|