agentgui 1.0.766 → 1.0.768

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.
@@ -0,0 +1,80 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import * as toolInstallMachine from './tool-install-machine.js';
5
+ import * as execMachine from './execution-machine.js';
6
+
7
+ export function register(deps) {
8
+ const { sendJSON, queries, activeExecutions, messageQueues, syncClients, wsOptimizer, _errLogPath } = deps;
9
+ const routes = {};
10
+
11
+ routes['GET /api/debug'] = async (req, res) => {
12
+ const execSnap = {};
13
+ for (const [k, v] of activeExecutions) execSnap[k] = { pid: v.pid, startTime: v.startTime, sessionId: v.sessionId, lastActivity: v.lastActivity };
14
+ const queueSnap = {};
15
+ for (const [k, v] of messageQueues) queueSnap[k] = { length: v.length, messageIds: v.map(m => m.messageId) };
16
+ let streamingConvs = [];
17
+ try { streamingConvs = queries.getConversationsList().filter(c => c.isStreaming).map(c => ({ id: c.id, isStreaming: c.isStreaming })); } catch (_) {}
18
+ const clientSubs = [];
19
+ for (const ws of syncClients) clientSubs.push({ clientId: ws.clientId, subs: ws.subscriptions ? [...ws.subscriptions] : [] });
20
+ let recentErrors = [];
21
+ try {
22
+ const raw = fs.readFileSync(_errLogPath, 'utf8');
23
+ recentErrors = raw.trim().split('\n').slice(-20).map(l => { try { return JSON.parse(l); } catch { return l; } });
24
+ } catch (_) {}
25
+ sendJSON(req, res, 200, { activeExecutions: execSnap, messageQueues: queueSnap, streamingConversations: streamingConvs, wsClients: clientSubs, recentErrors });
26
+ };
27
+
28
+ routes['GET /api/backup'] = async (req, res) => {
29
+ const dbPath = path.join(os.homedir(), '.gmgui', 'data.db');
30
+ if (!fs.existsSync(dbPath)) { sendJSON(req, res, 404, { error: 'Database not found' }); return; }
31
+ const stat = fs.statSync(dbPath);
32
+ res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename="agentgui-backup.db"', 'Content-Length': stat.size });
33
+ fs.createReadStream(dbPath).pipe(res);
34
+ };
35
+
36
+ routes['POST /api/restore'] = async (req, res) => {
37
+ const dbPath = path.join(os.homedir(), '.gmgui', 'data.db');
38
+ const backupPath = dbPath + '.bak-' + Date.now();
39
+ const chunks = [];
40
+ req.on('data', (chunk) => chunks.push(chunk));
41
+ req.on('end', () => {
42
+ try {
43
+ const body = Buffer.concat(chunks);
44
+ if (body.length < 100 || body.slice(0, 16).toString() !== 'SQLite format 3\0') {
45
+ sendJSON(req, res, 400, { error: 'Invalid SQLite database file' }); return;
46
+ }
47
+ fs.copyFileSync(dbPath, backupPath);
48
+ fs.writeFileSync(dbPath, body);
49
+ sendJSON(req, res, 200, { success: true, backupPath, size: body.length });
50
+ } catch (e) {
51
+ sendJSON(req, res, 500, { error: e.message });
52
+ }
53
+ });
54
+ };
55
+
56
+ routes['GET /api/debug/machines'] = async (req, res) => {
57
+ if (!process.env.DEBUG) { sendJSON(req, res, 404, { error: 'Not found' }); return; }
58
+ const toolSnap = {};
59
+ for (const [id, actor] of toolInstallMachine.getMachineActors()) {
60
+ const s = actor.getSnapshot();
61
+ toolSnap[id] = { state: s.value, context: s.context };
62
+ }
63
+ const execSnap = {};
64
+ for (const id of activeExecutions.keys()) {
65
+ const s = execMachine.snapshot(id);
66
+ if (s) execSnap[id] = { state: s.value, context: { pid: s.context.pid, sessionId: s.context.sessionId, queueLen: s.context.queue?.length } };
67
+ }
68
+ sendJSON(req, res, 200, { toolInstall: toolSnap, execution: execSnap });
69
+ };
70
+
71
+ routes['GET /api/ws-stats'] = async (req, res) => {
72
+ sendJSON(req, res, 200, wsOptimizer.getStats());
73
+ };
74
+
75
+ routes['_match'] = (method, pathOnly) => {
76
+ return routes[`${method} ${pathOnly}`] || null;
77
+ };
78
+
79
+ return routes;
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.766",
3
+ "version": "1.0.768",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -23,6 +23,7 @@ import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
23
23
  import { register as registerUtilRoutes } from './lib/routes-util.js';
24
24
  import { register as registerToolRoutes } from './lib/routes-tools.js';
25
25
  import { register as registerThreadRoutes } from './lib/routes-threads.js';
26
+ import { register as registerDebugRoutes } from './lib/routes-debug.js';
26
27
  import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
27
28
  import { WSOptimizer } from './lib/ws-optimizer.js';
28
29
  import { WsRouter } from './lib/ws-protocol.js';
@@ -1205,79 +1206,9 @@ const server = http.createServer(async (req, res) => {
1205
1206
  }
1206
1207
 
1207
1208
 
1208
- if (pathOnly === '/api/debug' && req.method === 'GET') {
1209
- const execSnap = {};
1210
- for (const [k, v] of activeExecutions) execSnap[k] = { pid: v.pid, startTime: v.startTime, sessionId: v.sessionId, lastActivity: v.lastActivity };
1211
- const queueSnap = {};
1212
- for (const [k, v] of messageQueues) queueSnap[k] = { length: v.length, messageIds: v.map(m => m.messageId) };
1213
- let streamingConvs = [];
1214
- try { streamingConvs = queries.getConversationsList().filter(c => c.isStreaming).map(c => ({ id: c.id, isStreaming: c.isStreaming })); } catch (_) {}
1215
- const clientSubs = [];
1216
- for (const ws of syncClients) clientSubs.push({ clientId: ws.clientId, subs: ws.subscriptions ? [...ws.subscriptions] : [] });
1217
- let recentErrors = [];
1218
- try {
1219
- const raw = fs.readFileSync(_errLogPath, 'utf8');
1220
- recentErrors = raw.trim().split('\n').slice(-20).map(l => { try { return JSON.parse(l); } catch { return l; } });
1221
- } catch (_) {}
1222
- sendJSON(req, res, 200, { activeExecutions: execSnap, messageQueues: queueSnap, streamingConversations: streamingConvs, wsClients: clientSubs, recentErrors });
1223
- return;
1224
- }
1225
-
1226
- if (pathOnly === '/api/backup' && req.method === 'GET') {
1227
- const dbPath = path.join(os.homedir(), '.gmgui', 'data.db');
1228
- if (!fs.existsSync(dbPath)) { sendJSON(req, res, 404, { error: 'Database not found' }); return; }
1229
- const stat = fs.statSync(dbPath);
1230
- res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename="agentgui-backup.db"', 'Content-Length': stat.size });
1231
- fs.createReadStream(dbPath).pipe(res);
1232
- return;
1233
- }
1234
1209
 
1235
- if (pathOnly === '/api/restore' && req.method === 'POST') {
1236
- const dbPath = path.join(os.homedir(), '.gmgui', 'data.db');
1237
- const backupPath = dbPath + '.bak-' + Date.now();
1238
- const chunks = [];
1239
- req.on('data', (chunk) => chunks.push(chunk));
1240
- req.on('end', () => {
1241
- try {
1242
- const body = Buffer.concat(chunks);
1243
- if (body.length < 100 || body.slice(0, 16).toString() !== 'SQLite format 3\0') {
1244
- sendJSON(req, res, 400, { error: 'Invalid SQLite database file' });
1245
- return;
1246
- }
1247
- fs.copyFileSync(dbPath, backupPath);
1248
- fs.writeFileSync(dbPath, body);
1249
- sendJSON(req, res, 200, { success: true, backupPath, size: body.length });
1250
- } catch (e) {
1251
- sendJSON(req, res, 500, { error: e.message });
1252
- }
1253
- });
1254
- return;
1255
- }
1256
-
1257
- if (pathOnly === '/api/debug/machines' && req.method === 'GET' && process.env.DEBUG) {
1258
- const toolSnap = {};
1259
- for (const [id, actor] of toolInstallMachine.getMachineActors()) {
1260
- const s = actor.getSnapshot();
1261
- toolSnap[id] = { state: s.value, context: s.context };
1262
- }
1263
- const execSnap = {};
1264
- const allExecConvIds = [...activeExecutions.keys()];
1265
- for (const id of allExecConvIds) {
1266
- const s = execMachine.snapshot(id);
1267
- if (s) execSnap[id] = { state: s.value, context: { pid: s.context.pid, sessionId: s.context.sessionId, queueLen: s.context.queue?.length } };
1268
- }
1269
- sendJSON(req, res, 200, { toolInstall: toolSnap, execution: execSnap });
1270
- return;
1271
- }
1272
-
1273
- const toolHandler = _toolRoutes._match(req.method, pathOnly);
1274
- if (toolHandler) { await toolHandler(req, res); return; }
1275
-
1276
- if (pathOnly === '/api/ws-stats' && req.method === 'GET') {
1277
- const stats = wsOptimizer.getStats();
1278
- sendJSON(req, res, 200, stats);
1279
- return;
1280
- }
1210
+ const debugHandler = _debugRoutes._match(req.method, pathOnly);
1211
+ if (debugHandler) { await debugHandler(req, res); return; }
1281
1212
 
1282
1213
  if (pathOnly === '/api/agents/search' && req.method === 'POST') {
1283
1214
  const body = await parseBody(req);
@@ -2918,6 +2849,7 @@ const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL,
2918
2849
  const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
2919
2850
  const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
2920
2851
  const _threadRoutes = registerThreadRoutes({ sendJSON, parseBody, queries });
2852
+ const _debugRoutes = registerDebugRoutes({ sendJSON, queries, activeExecutions, messageQueues, syncClients, wsOptimizer, _errLogPath });
2921
2853
 
2922
2854
  registerConvHandlers(wsRouter, {
2923
2855
  queries, activeExecutions, rateLimitState,
@@ -1156,6 +1156,12 @@
1156
1156
  .message { max-width: 95%; }
1157
1157
  .messages-wrapper { padding: 0.375rem 0.5rem; }
1158
1158
  .input-section { padding: 0.5rem; padding-bottom: calc(0.5rem + env(safe-area-inset-bottom)); }
1159
+ .sidebar-header-actions { gap: 0.25rem; }
1160
+ .sidebar-clone-btn { font-size: 0.7rem; padding: 0.25rem 0.375rem; }
1161
+ .conversation-item-export,
1162
+ .conversation-item-archive { display: none; }
1163
+ .conversation-header h2 { font-size: 1.125rem; }
1164
+ .shortcuts-panel { padding: 1rem; }
1159
1165
  }
1160
1166
 
1161
1167
  /* ===== SCROLLBAR STYLING ===== */
package/static/index.html CHANGED
@@ -30,7 +30,7 @@
30
30
 
31
31
  <div class="app-shell">
32
32
  <!-- ===== SIDEBAR ===== -->
33
- <aside class="sidebar" data-sidebar>
33
+ <aside class="sidebar" data-sidebar role="navigation" aria-label="Conversation history">
34
34
  <div class="sidebar-header">
35
35
  <h2>History</h2>
36
36
  <div class="sidebar-header-actions">
@@ -40,14 +40,14 @@
40
40
  </div>
41
41
  </div>
42
42
  <div class="sidebar-search-bar" id="sidebarSearchBar">
43
- <input type="text" class="sidebar-search-input" id="sidebarSearchInput" placeholder="Search conversations..." autocomplete="off" spellcheck="false">
43
+ <input type="text" class="sidebar-search-input" id="sidebarSearchInput" placeholder="Search conversations..." autocomplete="off" spellcheck="false" aria-label="Search conversations">
44
44
  </div>
45
45
  <div class="clone-input-bar" id="cloneInputBar" style="display:none;">
46
46
  <input type="text" class="clone-input" id="cloneRepoInput" placeholder="org/repo" autocomplete="off" spellcheck="false">
47
47
  <button class="clone-go-btn" id="cloneGoBtn" title="Clone">Go</button>
48
48
  <button class="clone-cancel-btn" id="cloneCancelBtn" title="Cancel">&times;</button>
49
49
  </div>
50
- <ul class="sidebar-list" data-conversation-list>
50
+ <ul class="sidebar-list" data-conversation-list role="listbox" aria-label="Conversations">
51
51
  <li class="sidebar-empty" data-conversation-empty>Loading...</li>
52
52
  </ul>
53
53
  <!-- PM2 Monitor Panel: hidden until active processes detected -->
@@ -63,7 +63,7 @@
63
63
  </aside>
64
64
 
65
65
  <!-- ===== MAIN PANEL ===== -->
66
- <main id="app" class="main-panel">
66
+ <main id="app" class="main-panel" role="main" aria-label="Conversation">
67
67
  <!-- Header bar -->
68
68
  <div class="main-header">
69
69
  <button class="sidebar-toggle-btn" data-sidebar-toggle title="Toggle sidebar (Ctrl+B)" aria-label="Toggle sidebar">