create-byan-agent 2.8.1 → 2.9.0

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.
@@ -9,6 +9,10 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const { WebSocketServer } = require('ws');
11
11
 
12
+ const SessionManager = require('./chat/session-manager');
13
+ const { detectCLIs } = require('./chat/cli-detector');
14
+ const { createBridge } = require('./chat/bridge');
15
+
12
16
  const MIME_TYPES = {
13
17
  '.html': 'text/html; charset=utf-8',
14
18
  '.css': 'text/css; charset=utf-8',
@@ -28,14 +32,19 @@ class ByanWebUI {
28
32
  this.wss = null;
29
33
  this.clients = new Set();
30
34
  this.api = require('./api');
35
+ this.sessionManager = null;
36
+ this.chatBridges = new Map();
31
37
  }
32
38
 
33
39
  start() {
40
+ this.sessionManager = new SessionManager(this.projectRoot);
41
+
34
42
  this.server = http.createServer((req, res) => this.handleRequest(req, res));
35
43
  this.wss = new WebSocketServer({ server: this.server });
36
44
 
37
45
  this.wss.on('connection', (ws) => {
38
46
  this.clients.add(ws);
47
+ ws.on('message', (raw) => this.handleChatMessage(ws, raw));
39
48
  ws.on('close', () => this.clients.delete(ws));
40
49
  ws.on('error', () => this.clients.delete(ws));
41
50
  });
@@ -121,7 +130,7 @@ class ByanWebUI {
121
130
  return;
122
131
  }
123
132
 
124
- if (method === 'POST' || method === 'PUT') {
133
+ if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
125
134
  let body = '';
126
135
  req.on('data', (chunk) => { body += chunk; });
127
136
  req.on('end', () => {
@@ -172,8 +181,120 @@ class ByanWebUI {
172
181
  this.broadcast({ type: 'complete', success, summary });
173
182
  }
174
183
 
184
+ handleChatMessage(ws, raw) {
185
+ let data;
186
+ try {
187
+ data = JSON.parse(raw.toString());
188
+ } catch {
189
+ ws.send(JSON.stringify({ type: 'error', error: 'Invalid JSON' }));
190
+ return;
191
+ }
192
+
193
+ switch (data.type) {
194
+ case 'chat-subscribe':
195
+ ws._chatSessionId = data.sessionId || null;
196
+ ws.send(JSON.stringify({ type: 'subscribed', sessionId: data.sessionId }));
197
+ break;
198
+
199
+ case 'chat-start':
200
+ this._wsStartChat(ws, data);
201
+ break;
202
+
203
+ case 'chat-send':
204
+ this._wsSendChat(ws, data);
205
+ break;
206
+
207
+ case 'chat-stop':
208
+ this._wsStopChat(ws, data);
209
+ break;
210
+
211
+ default:
212
+ break;
213
+ }
214
+ }
215
+
216
+ async _wsStartChat(ws, data) {
217
+ const { cli, agent, model } = data;
218
+ const cliName = cli || 'claude';
219
+ const session = this.sessionManager.create(cliName, agent || null);
220
+ const sessionId = session.id;
221
+
222
+ ws._chatSessionId = sessionId;
223
+
224
+ try {
225
+ const bridge = createBridge(cliName, {
226
+ projectRoot: this.projectRoot,
227
+ agent: agent || null,
228
+ model: model || null,
229
+ onChunk: (chunk) => {
230
+ this._sendToSession(sessionId, { type: 'chat', sessionId, chunk, role: 'assistant' });
231
+ },
232
+ onToolUse: (tool) => {
233
+ this._sendToSession(sessionId, { type: 'chat-tool', sessionId, tool });
234
+ },
235
+ onComplete: (result) => {
236
+ this._sendToSession(sessionId, { type: 'chat-complete', sessionId, result });
237
+ },
238
+ onError: (err) => {
239
+ this._sendToSession(sessionId, { type: 'chat-error', sessionId, error: err.message });
240
+ },
241
+ });
242
+
243
+ await bridge.start();
244
+ this.chatBridges.set(sessionId, bridge);
245
+
246
+ ws.send(JSON.stringify({ type: 'chat-started', sessionId, cli: cliName }));
247
+ } catch (err) {
248
+ this.sessionManager.delete(sessionId);
249
+ ws.send(JSON.stringify({ type: 'chat-error', sessionId, error: err.message }));
250
+ }
251
+ }
252
+
253
+ async _wsSendChat(ws, data) {
254
+ const { sessionId, message } = data;
255
+ const bridge = this.chatBridges.get(sessionId);
256
+ if (!bridge) {
257
+ ws.send(JSON.stringify({ type: 'chat-error', sessionId, error: 'No active bridge' }));
258
+ return;
259
+ }
260
+
261
+ this.sessionManager.addMessage(sessionId, 'user', message);
262
+
263
+ try {
264
+ await bridge.send(message);
265
+ } catch (err) {
266
+ ws.send(JSON.stringify({ type: 'chat-error', sessionId, error: err.message }));
267
+ }
268
+ }
269
+
270
+ async _wsStopChat(ws, data) {
271
+ const { sessionId } = data;
272
+ const bridge = this.chatBridges.get(sessionId);
273
+ if (bridge) {
274
+ await bridge.stop();
275
+ this.chatBridges.delete(sessionId);
276
+ }
277
+ ws.send(JSON.stringify({ type: 'chat-stopped', sessionId }));
278
+ }
279
+
280
+ _sendToSession(sessionId, data) {
281
+ const payload = JSON.stringify(data);
282
+ for (const client of this.clients) {
283
+ if (client.readyState === 1) {
284
+ if (!client._chatSessionId || client._chatSessionId === sessionId) {
285
+ client.send(payload);
286
+ }
287
+ }
288
+ }
289
+ }
290
+
175
291
  stop() {
176
- return new Promise((resolve) => {
292
+ return new Promise(async (resolve) => {
293
+ for (const [id, bridge] of this.chatBridges) {
294
+ try { await bridge.stop(); } catch { /* best effort */ }
295
+ }
296
+ this.chatBridges.clear();
297
+
177
298
  for (const client of this.clients) {
178
299
  client.close();
179
300
  }