agentgui 1.0.923 → 1.0.925

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 (3) hide show
  1. package/bin/gmgui.cjs +2 -1
  2. package/package.json +4 -4
  3. package/test.js +46 -30
package/bin/gmgui.cjs CHANGED
@@ -43,7 +43,8 @@ async function gmgui(args = []) {
43
43
 
44
44
  const port = process.env.PORT || 3000;
45
45
  const baseUrl = process.env.BASE_URL || '/gm';
46
- const runtime = 'node';
46
+ const bunAvailable = (() => { try { return spawnSync('bun', ['--version'], { shell: true }).status === 0; } catch { return false; } })();
47
+ const runtime = bunAvailable ? 'bun' : 'node';
47
48
 
48
49
  return new Promise((resolve, reject) => {
49
50
  const ps = spawn(runtime, [path.join(projectRoot, 'server.js')], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.923",
3
+ "version": "1.0.925",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -16,9 +16,9 @@
16
16
  },
17
17
  "homepage": "https://github.com/AnEntrypoint/agentgui#readme",
18
18
  "scripts": {
19
- "start": "node server.js",
19
+ "start": "bun server.js || node server.js",
20
20
  "dev": "node server.js --watch",
21
- "postinstall": "node scripts/patch-fsbrowse.js && node scripts/copy-vendor.js",
21
+ "postinstall": "node scripts/patch-fsbrowse.js && node scripts/copy-vendor.js && (cd node_modules/better-sqlite3 && node-gyp rebuild 2>/dev/null) || true",
22
22
  "electron": "electron electron/main.js",
23
23
  "electron:dev": "PORT=3000 electron electron/main.js"
24
24
  },
@@ -28,7 +28,7 @@
28
28
  "@google/gemini-cli": "latest",
29
29
  "@kilocode/cli": "latest",
30
30
  "@lanmower/ccf": "^1.0.4",
31
- "better-sqlite3": "^12.6.2",
31
+ "better-sqlite3": "^12.9.0",
32
32
  "busboy": "^1.6.0",
33
33
  "ccfollow": "^1.0.7",
34
34
  "execa": "^9.6.1",
package/test.js CHANGED
@@ -131,21 +131,13 @@ await ok('agent-descriptors: initialize + cache', () => {
131
131
  assert.ok(d.specs.input.properties.model); assert.ok(d.specs.thread_state.properties.sessionId);
132
132
  assert.equal(getAgentDescriptor('nope'), null);
133
133
  });
134
- await ok('ws-optimizer: high-priority flushes immediately', () => {
135
- const opt = new WSOptimizer(); const sent = [];
136
- const ws = { readyState: 1, clientId: 'c1', send: (b) => sent.push(decode(b)) };
137
- opt.sendToClient(ws, { type: 'streaming_start', id: 1 });
138
- assert.equal(sent.length, 1); assert.equal(sent[0].type, 'streaming_start');
139
- opt.removeClient(ws); assert.equal(opt.getStats().clients, 0);
140
- });
141
- await ok('ws-optimizer: low-priority batches via timer', async () => {
142
- const opt = new WSOptimizer(); const sent = [];
143
- const ws = { readyState: 1, clientId: 'c2', latencyTier: 'excellent', send: (b) => sent.push(decode(b)) };
144
- opt.sendToClient(ws, { type: 'tts_audio', n: 1 }); opt.sendToClient(ws, { type: 'tts_audio', n: 2 });
145
- assert.equal(sent.length, 0);
146
- await new Promise(r => setTimeout(r, 40));
147
- assert.equal(sent.length, 1); assert.equal(sent[0].length, 2);
148
- opt.removeClient(ws);
134
+ await ok('ws-optimizer: high-priority flush + low-priority batch', async () => {
135
+ const opt = new WSOptimizer(); const s1 = []; const s2 = [];
136
+ const w1 = { readyState: 1, clientId: 'c1', send: (b) => s1.push(decode(b)) };
137
+ const w2 = { readyState: 1, clientId: 'c2', latencyTier: 'excellent', send: (b) => s2.push(decode(b)) };
138
+ opt.sendToClient(w1, { type: 'streaming_start', id: 1 }); assert.equal(s1.length, 1); opt.removeClient(w1); assert.equal(opt.getStats().clients, 0);
139
+ opt.sendToClient(w2, { type: 'tts_audio', n: 1 }); opt.sendToClient(w2, { type: 'tts_audio', n: 2 }); assert.equal(s2.length, 0);
140
+ await new Promise(r => setTimeout(r, 40)); assert.equal(s2.length, 1); assert.equal(s2[0].length, 2); opt.removeClient(w2);
149
141
  });
150
142
  await ok('acp-protocol: session/update + result + error mapping', () => {
151
143
  const h = createACPProtocolHandler(); const ctx = { sessionId: 's1' };
@@ -161,23 +153,47 @@ await ok('acp-protocol: session/update + result + error mapping', () => {
161
153
  await ok('http-utils: sendJSON + compressAndSend size threshold', () => {
162
154
  const req = { headers: { 'accept-encoding': 'gzip, deflate' } };
163
155
  assert.equal(acceptsEncoding(req, 'gzip'), true); assert.equal(acceptsEncoding({ headers: {} }, 'gzip'), false);
164
- const small = mockRes(); sendJSON(req, small, 200, { ok: 1 });
165
- assert.equal(small.statusCode, 200); assert.equal(small.headers['Content-Type'], 'application/json');
166
- assert.ok(!small.headers['Content-Encoding']);
167
- const big = mockRes(); compressAndSend(req, big, 200, 'text/plain', 'x'.repeat(2000));
168
- assert.equal(big.headers['Content-Encoding'], 'gzip');
169
- const noGz = mockRes(); compressAndSend({ headers: {} }, noGz, 200, 'text/html', 'y'.repeat(2000));
170
- assert.equal(noGz.headers['Cache-Control'], 'no-store'); assert.ok(!noGz.headers['Content-Encoding']);
156
+ const sm = mockRes(); sendJSON(req, sm, 200, { ok: 1 }); assert.equal(sm.statusCode, 200); assert.equal(sm.headers['Content-Type'], 'application/json');
157
+ const big = mockRes(); compressAndSend(req, big, 200, 'text/plain', 'x'.repeat(2000)); assert.equal(big.headers['Content-Encoding'], 'gzip');
158
+ const ng = mockRes(); compressAndSend({ headers: {} }, ng, 200, 'text/html', 'y'.repeat(2000)); assert.equal(ng.headers['Cache-Control'], 'no-store');
171
159
  });
172
160
  await ok('jsonl-parser: register + remove + clear', () => {
173
- const calls = []; const fakeQ = { getConversationByClaudeSessionId: () => null };
174
- const p = new JsonlParser({ broadcastSync: (m) => calls.push(m), queries: fakeQ });
175
- p.registerSession('claude-1', 'conv-1', 'db-sess-1');
176
- assert.equal(p._convMap.get('claude-1'), 'conv-1'); assert.equal(p._sessions.get('claude-1'), 'db-sess-1');
177
- p.removeSid('claude-1');
178
- assert.equal(p._convMap.has('claude-1'), false); assert.equal(p._sessions.has('claude-1'), false);
179
- p.registerSession('s2', 'c2', 'd2'); p.clear();
180
- assert.equal(p._convMap.size, 0); assert.equal(p._sessions.size, 0); assert.equal(calls.length, 0);
161
+ const p = new JsonlParser({ broadcastSync: () => {}, queries: { getConversationByClaudeSessionId: () => null } });
162
+ p.registerSession('s1', 'c1', 'd1'); assert.equal(p._convMap.get('s1'), 'c1');
163
+ p.removeSid('s1'); assert.equal(p._convMap.has('s1'), false);
164
+ p.registerSession('s2', 'c2', 'd2'); p.clear(); assert.equal(p._convMap.size, 0);
165
+ });
166
+ await okDb('conv-routes+thread-routes+auth-config+util-routes', async () => {
167
+ const [{ register: rC }, { register: rT }, { register: rA }, { register: rU }] = await Promise.all(['./lib/routes-conversations.js','./lib/routes-threads.js','./lib/routes-auth-config.js','./lib/routes-util.js'].map(m => import(m)));
168
+ const { db: d2, prep: p2, gid: g2 } = inMemDb(); const q2 = createQueries(d2, p2, g2);
169
+ const sj2 = (req, res, c, data) => { res.statusCode = c; res.body = JSON.stringify(data); };
170
+ const pb2 = async (req) => req?._b || {}; const mr = () => mockRes();
171
+ const cR = rC({ sendJSON: sj2, parseBody: pb2, queries: q2, activeExecutions: new Map(), broadcastSync: () => {} });
172
+ const c1 = q2.createConversation('claude-code', 'T');
173
+ const lr = mr(); await cR['GET /api/conversations'](null, lr); assert.equal(lr.statusCode, 200);
174
+ const cr = mr(); await cR['POST /api/conversations']({ _b: { agentId: 'claude-code', title: 'N' } }, cr);
175
+ assert.equal(cr.statusCode, 201); const nid = JSON.parse(cr.body).conversation.id;
176
+ const gr = mr(); await cR._match('GET', `/api/conversations/${nid}`)(null, gr); assert.equal(gr.statusCode, 200);
177
+ const dr = mr(); await cR._match('DELETE', `/api/conversations/${nid}`)(null, dr); assert.equal(dr.statusCode, 200);
178
+ const nr = mr(); await cR._match('GET', '/api/conversations/nope')(null, nr); assert.equal(nr.statusCode, 404);
179
+ const ar = mr(); await cR._match('POST', `/api/conversations/${c1.id}/archive`)(null, ar); assert.equal(ar.statusCode, 200);
180
+ const rr = mr(); await cR._match('POST', `/api/conversations/${c1.id}/restore`)(null, rr); assert.equal(rr.statusCode, 200);
181
+ const tR = rT({ sendJSON: sj2, parseBody: pb2, queries: q2 });
182
+ const tr = mr(); await tR['POST /api/threads']({ _b: { metadata: { k: 1 } } }, tr); assert.equal(tr.statusCode, 201);
183
+ const tid = JSON.parse(tr.body).thread_id;
184
+ const tgr = mr(); await tR._match('GET', `/api/threads/${tid}`)(null, tgr); assert.equal(tgr.statusCode, 200);
185
+ const tpr = mr(); await tR._match('PATCH', `/api/threads/${tid}`)({ _b: { metadata: { k: 2 } } }, tpr); assert.equal(tpr.statusCode, 200);
186
+ const tsr = { statusCode: 0, writeHead: (c) => { tsr.statusCode = c; }, end: () => {} };
187
+ await tR._match('DELETE', `/api/threads/${tid}`)(null, tsr); assert.equal(tsr.statusCode, 204);
188
+ const ssr = mr(); await tR['POST /api/threads/search']({ _b: {} }, ssr); assert.equal(ssr.statusCode, 200);
189
+ const aR = rA({ sendJSON: sj2, parseBody: pb2, getProviderConfigs: () => ({ anthropic: { hasKey: false } }), saveProviderConfig: () => '/x' });
190
+ const agr = mr(); aR['GET /api/auth/configs'](null, agr); assert.equal(agr.statusCode, 200); assert.ok(JSON.parse(agr.body).anthropic !== undefined);
191
+ const br = mr(); await aR['POST /api/auth/save-config']({ _b: { providerId: '', apiKey: 'x' } }, br); assert.equal(br.statusCode, 400);
192
+ const sr = mr(); await aR['POST /api/auth/save-config']({ _b: { providerId: 'anthropic', apiKey: 'sk-123456789' } }, sr); assert.equal(sr.statusCode, 200);
193
+ const uR = rU({ sendJSON: sj2, parseBody: pb2, queries: q2, STARTUP_CWD: process.cwd(), PKG_VERSION: '1.0.0' });
194
+ const hr = mr(); await uR['GET /api/home'](null, hr); assert.equal(hr.statusCode, 200); assert.ok(JSON.parse(hr.body).home);
195
+ const vr = mr(); await uR['GET /api/version'](null, vr); assert.equal(vr.statusCode, 200);
196
+ const fr = mr(); await uR['POST /api/folders']({ _b: { path: process.cwd() } }, fr); assert.equal(fr.statusCode, 200); assert.ok(Array.isArray(JSON.parse(fr.body).folders));
181
197
  });
182
198
  console.log(`\n${passed} passed, ${failed} failed`);
183
199
  process.exit(failed === 0 ? 0 : 1);