agentgui 1.0.917 → 1.0.918

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 (52) hide show
  1. package/database-schema.js +0 -58
  2. package/lib/db-queries-cleanup.js +0 -12
  3. package/lib/db-queries-del.js +0 -1
  4. package/lib/db-queries.js +0 -4
  5. package/lib/http-handler.js +1 -10
  6. package/lib/plugins/database-plugin.js +1 -1
  7. package/lib/process-message.js +2 -2
  8. package/lib/provider-config.js +0 -16
  9. package/lib/recovery.js +2 -12
  10. package/lib/routes-agent-actions.js +2 -58
  11. package/lib/routes-debug.js +1 -7
  12. package/lib/routes-registry.js +5 -17
  13. package/lib/server-startup.js +1 -59
  14. package/lib/stream-event-handler.js +1 -3
  15. package/lib/ws-handlers-session2.js +2 -23
  16. package/lib/ws-handlers-util.js +106 -175
  17. package/lib/ws-legacy-handlers.js +1 -104
  18. package/lib/ws-setup.js +1 -3
  19. package/package.json +1 -15
  20. package/server.js +9 -26
  21. package/test.js +1 -21
  22. package/ecosystem.config.cjs +0 -22
  23. package/lib/checkpoint-manager.js +0 -182
  24. package/lib/db-queries-tools.js +0 -131
  25. package/lib/db-queries-voice.js +0 -85
  26. package/lib/oauth-codex.js +0 -164
  27. package/lib/oauth-common.js +0 -92
  28. package/lib/oauth-gemini.js +0 -199
  29. package/lib/plugins/auth-plugin.js +0 -132
  30. package/lib/plugins/speech-plugin.js +0 -72
  31. package/lib/plugins/tools-plugin.js +0 -120
  32. package/lib/pm2-manager.js +0 -170
  33. package/lib/routes-oauth.js +0 -105
  34. package/lib/routes-speech.js +0 -173
  35. package/lib/routes-tools.js +0 -184
  36. package/lib/speech-manager.js +0 -200
  37. package/lib/speech.js +0 -50
  38. package/lib/tool-install-machine.js +0 -157
  39. package/lib/tool-manager.js +0 -98
  40. package/lib/tool-provisioner.js +0 -98
  41. package/lib/tool-spawner.js +0 -163
  42. package/lib/tool-version-check.js +0 -196
  43. package/lib/tool-version-fetch.js +0 -68
  44. package/lib/ws-handlers-oauth.js +0 -76
  45. package/static/js/agent-auth-oauth.js +0 -159
  46. package/static/js/pm2-monitor.js +0 -151
  47. package/static/js/stt-handler.js +0 -147
  48. package/static/js/tool-install-machine.js +0 -155
  49. package/static/js/tools-manager-ui.js +0 -124
  50. package/static/js/tools-manager.js +0 -172
  51. package/static/js/voice-machine.js +0 -145
  52. package/static/js/voice.js +0 -134
@@ -1,175 +1,106 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { execSync, spawnSync } from 'child_process';
5
- import * as toolInstallMachine from './tool-install-machine.js';
6
-
7
- function err(code, message) { const e = new Error(message); e.code = code; throw e; }
8
-
9
- export function register(router, deps) {
10
- const { queries, wsOptimizer, modelDownloadState, ensureModelsDownloaded,
11
- broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
12
- STARTUP_CWD, voiceCacheManager, toolManager, discoveredAgents } = deps;
13
-
14
- router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
15
-
16
- router.handle('folders', (p) => {
17
- const folderPath = p.path || STARTUP_CWD;
18
- try {
19
- const raw = folderPath.startsWith('~') ? folderPath.replace('~', os.homedir()) : folderPath;
20
- const entries = fs.readdirSync(path.resolve(raw), { withFileTypes: true });
21
- return { folders: entries.filter(e => e.isDirectory() && !e.name.startsWith('.')).map(e => ({ name: e.name })).sort((a, b) => a.name.localeCompare(b.name)) };
22
- } catch (e) { err(400, e.message); }
23
- });
24
-
25
- router.handle('clone', (p) => {
26
- const repo = (p.repo || '').trim();
27
- if (!repo || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
28
- err(400, 'Invalid repo format. Use org/repo or user/repo');
29
- }
30
- const cloneDir = STARTUP_CWD || os.homedir();
31
- const repoName = repo.split('/')[1];
32
- const targetPath = path.join(cloneDir, repoName);
33
- if (fs.existsSync(targetPath)) err(409, `Directory already exists: ${repoName}`);
34
- try {
35
- const isWindows = os.platform() === 'win32';
36
- execSync('git clone https://github.com/' + repo + '.git', {
37
- cwd: cloneDir, encoding: 'utf-8', timeout: 120000,
38
- stdio: ['pipe', 'pipe', 'pipe'],
39
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
40
- shell: isWindows
41
- });
42
- return { ok: true, repo, path: targetPath, name: repoName };
43
- } catch (e) { err(500, (e.stderr || e.message || 'Clone failed').trim()); }
44
- });
45
-
46
- router.handle('git.check', () => {
47
- try {
48
- const isWindows = os.platform() === 'win32';
49
- const devnull = isWindows ? '' : ' 2>/dev/null';
50
- const remoteUrl = execSync('git remote get-url origin' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows }).trim();
51
- const statusResult = execSync('git status --porcelain' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
52
- const hasChanges = statusResult.trim().length > 0;
53
- const unpushedResult = execSync('git rev-list --count --not --remotes' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
54
- const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
55
- const githubUser = process.env.GITHUB_USER;
56
- const ownsRemote = !remoteUrl.includes('github.com/') || (!!githubUser && remoteUrl.includes(githubUser));
57
- return { ownsRemote, hasChanges, hasUnpushed, remoteUrl };
58
- } catch {
59
- return { ownsRemote: false, hasChanges: false, hasUnpushed: false, remoteUrl: '' };
60
- }
61
- });
62
-
63
- router.handle('git.push', () => {
64
- try {
65
- const isWindows = os.platform() === 'win32';
66
- const cmd = isWindows
67
- ? 'git add -A & git commit -m "Auto-commit" & git push'
68
- : 'git add -A && git commit -m "Auto-commit" && git push';
69
- execSync(cmd, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
70
- return { success: true };
71
- } catch (e) { err(500, e.message); }
72
- });
73
-
74
- router.handle('speech.status', async () => {
75
- const modelState = { modelsDownloading: modelDownloadState.downloading, modelsComplete: modelDownloadState.complete, modelsError: modelDownloadState.error };
76
- try {
77
- const { getStatus } = await getSpeech();
78
- const base = getStatus();
79
- return { ...base, setupMessage: base.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request', ...modelState, modelsProgress: modelDownloadState.progress };
80
- } catch {
81
- return { sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false, setupMessage: 'Will setup on first TTS request', ...modelState };
82
- }
83
- });
84
-
85
- router.handle('speech.download', () => {
86
- if (modelDownloadState.complete) return { ok: true, modelsComplete: true, message: 'Models already ready' };
87
- if (!modelDownloadState.downloading) {
88
- modelDownloadState.error = null;
89
- ensureModelsDownloaded().then(ok => {
90
- broadcastSync({ type: 'model_download_progress', progress: { done: true, complete: ok, error: ok ? null : 'Download failed' } });
91
- }).catch(e => {
92
- broadcastSync({ type: 'model_download_progress', progress: { done: true, error: e.message } });
93
- });
94
- }
95
- return { ok: true, message: 'Starting model download' };
96
- });
97
-
98
- router.handle('voices', async () => {
99
- try {
100
- const { getVoices } = await getSpeech();
101
- return { ok: true, voices: getVoices() };
102
- } catch { return { ok: true, voices: [] }; }
103
- });
104
-
105
- router.handle('auth.configs', () => getProviderConfigs());
106
-
107
- router.handle('auth.save', (p) => {
108
- const { providerId, apiKey, defaultModel } = p;
109
- if (typeof providerId !== 'string' || !providerId.length || providerId.length > 100) err(400, 'Invalid providerId');
110
- if (typeof apiKey !== 'string' || !apiKey.length || apiKey.length > 10000) err(400, 'Invalid apiKey');
111
- if (defaultModel !== undefined && (typeof defaultModel !== 'string' || defaultModel.length > 200)) err(400, 'Invalid defaultModel');
112
- const configPath = saveProviderConfig(providerId, apiKey, defaultModel || '');
113
- return { success: true, path: configPath };
114
- });
115
-
116
- router.handle('import.claude', () => ({ imported: queries.importClaudeCodeConversations() }));
117
-
118
- router.handle('discover.claude', () => ({ discovered: queries.discoverClaudeCodeConversations() }));
119
-
120
- router.handle('ws.stats', () => wsOptimizer.getStats());
121
-
122
- router.handle('voice.cache', async (p) => {
123
- const { conversationId, text } = p;
124
- if (!conversationId || !text) err(400, 'Missing conversationId or text');
125
- try {
126
- const cached = queries.getVoiceCache(conversationId, text);
127
- if (cached && cached.audioBlob) {
128
- return { ok: true, cached: true, byteSize: cached.byteSize };
129
- }
130
- return { ok: true, cached: false };
131
- } catch (e) { err(500, e.message); }
132
- });
133
-
134
- router.handle('voice.generate', async (p) => {
135
- const { conversationId, text } = p;
136
- if (!conversationId || !text) err(400, 'Missing conversationId or text');
137
- try {
138
- const result = await voiceCacheManager.getOrGenerateCache(conversationId, text);
139
- return { ok: true, byteSize: result.byteSize, cached: true };
140
- } catch (e) { err(500, e.message); }
141
- });
142
-
143
- router.handle('tools.list', async () => {
144
- try {
145
- const tools = await toolManager.getAllToolsAsync();
146
- return { tools: tools.map((t) => {
147
- const machineState = toolInstallMachine.getState(t.id);
148
- return { id: t.id, name: t.name, pkg: t.pkg, category: t.category || 'plugin', installed: t.installed, status: t.installed ? (t.isUpToDate ? 'installed' : 'needs_update') : 'not_installed', isUpToDate: t.isUpToDate, upgradeNeeded: t.upgradeNeeded, hasUpdate: t.upgradeNeeded && t.installed, installedVersion: t.installedVersion, publishedVersion: t.publishedVersion, machineState };
149
- }) };
150
- } catch (e) { err(500, e.message); }
151
- });
152
-
153
- const SUB_AGENT_MAP = {
154
- 'opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }], 'cli-opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
155
- 'gemini': [{ id: 'gm-gc', name: 'GM Gemini' }], 'cli-gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
156
- 'kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }], 'cli-kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
157
- 'codex': [], 'cli-codex': []
158
- };
159
-
160
- router.handle('agent.subagents', async (p) => {
161
- if (!p.id) err(400, 'Missing agent id');
162
- if (p.id === 'claude-code' || p.id === 'cli-claude') {
163
- const spawnEnv = { ...process.env }; delete spawnEnv.CLAUDECODE;
164
- const result = spawnSync('claude', ['agents', 'list'], { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], env: spawnEnv });
165
- if (result.status !== 0 || !result.stdout) return { subAgents: [] };
166
- const agents = result.stdout.trim().split('\n').filter(l => l.trim()).map(l => l.match(/^ (\S+)\s+·/)).filter(Boolean).map(m => ({ id: m[1], name: m[1] }));
167
- console.log('[agent.subagents] claude agents list found:', agents.map(a => a.id).join(', '));
168
- return { subAgents: agents };
169
- }
170
- const subAgents = SUB_AGENT_MAP[p.id] || [];
171
- const tools = await toolManager.getAllToolsAsync();
172
- const installed = new Set(tools.filter(t => t.category === 'plugin' && t.installed).map(t => t.id));
173
- return { subAgents: subAgents.filter(sa => installed.has(sa.id)) };
174
- });
175
- }
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { execSync, spawnSync } from 'child_process';
5
+
6
+ function err(code, message) { const e = new Error(message); e.code = code; throw e; }
7
+
8
+ const SUB_AGENT_MAP = {
9
+ 'opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }], 'cli-opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
10
+ 'gemini': [{ id: 'gm-gc', name: 'GM Gemini' }], 'cli-gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
11
+ 'kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }], 'cli-kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
12
+ 'codex': [], 'cli-codex': []
13
+ };
14
+
15
+ export function register(router, deps) {
16
+ const { queries, wsOptimizer, broadcastSync, getProviderConfigs, saveProviderConfig, STARTUP_CWD, discoveredAgents } = deps;
17
+
18
+ router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
19
+
20
+ router.handle('folders', (p) => {
21
+ const folderPath = p.path || STARTUP_CWD;
22
+ try {
23
+ const raw = folderPath.startsWith('~') ? folderPath.replace('~', os.homedir()) : folderPath;
24
+ const entries = fs.readdirSync(path.resolve(raw), { withFileTypes: true });
25
+ return { folders: entries.filter(e => e.isDirectory() && !e.name.startsWith('.')).map(e => ({ name: e.name })).sort((a, b) => a.name.localeCompare(b.name)) };
26
+ } catch (e) { err(400, e.message); }
27
+ });
28
+
29
+ router.handle('clone', (p) => {
30
+ const repo = (p.repo || '').trim();
31
+ if (!repo || !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
32
+ err(400, 'Invalid repo format. Use org/repo or user/repo');
33
+ }
34
+ const cloneDir = STARTUP_CWD || os.homedir();
35
+ const repoName = repo.split('/')[1];
36
+ const targetPath = path.join(cloneDir, repoName);
37
+ if (fs.existsSync(targetPath)) err(409, `Directory already exists: ${repoName}`);
38
+ try {
39
+ const isWindows = os.platform() === 'win32';
40
+ execSync('git clone https://github.com/' + repo + '.git', {
41
+ cwd: cloneDir, encoding: 'utf-8', timeout: 120000,
42
+ stdio: ['pipe', 'pipe', 'pipe'],
43
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
44
+ shell: isWindows
45
+ });
46
+ return { ok: true, repo, path: targetPath, name: repoName };
47
+ } catch (e) { err(500, (e.stderr || e.message || 'Clone failed').trim()); }
48
+ });
49
+
50
+ router.handle('git.check', () => {
51
+ try {
52
+ const isWindows = os.platform() === 'win32';
53
+ const devnull = isWindows ? '' : ' 2>/dev/null';
54
+ const remoteUrl = execSync('git remote get-url origin' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows }).trim();
55
+ const statusResult = execSync('git status --porcelain' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
56
+ const hasChanges = statusResult.trim().length > 0;
57
+ const unpushedResult = execSync('git rev-list --count --not --remotes' + devnull, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
58
+ const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
59
+ const githubUser = process.env.GITHUB_USER;
60
+ const ownsRemote = !remoteUrl.includes('github.com/') || (!!githubUser && remoteUrl.includes(githubUser));
61
+ return { ownsRemote, hasChanges, hasUnpushed, remoteUrl };
62
+ } catch {
63
+ return { ownsRemote: false, hasChanges: false, hasUnpushed: false, remoteUrl: '' };
64
+ }
65
+ });
66
+
67
+ router.handle('git.push', () => {
68
+ try {
69
+ const isWindows = os.platform() === 'win32';
70
+ const cmd = isWindows
71
+ ? 'git add -A & git commit -m "Auto-commit" & git push'
72
+ : 'git add -A && git commit -m "Auto-commit" && git push';
73
+ execSync(cmd, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
74
+ return { success: true };
75
+ } catch (e) { err(500, e.message); }
76
+ });
77
+
78
+ router.handle('auth.configs', () => getProviderConfigs());
79
+
80
+ router.handle('auth.save', (p) => {
81
+ const { providerId, apiKey, defaultModel } = p;
82
+ if (typeof providerId !== 'string' || !providerId.length || providerId.length > 100) err(400, 'Invalid providerId');
83
+ if (typeof apiKey !== 'string' || !apiKey.length || apiKey.length > 10000) err(400, 'Invalid apiKey');
84
+ if (defaultModel !== undefined && (typeof defaultModel !== 'string' || defaultModel.length > 200)) err(400, 'Invalid defaultModel');
85
+ const configPath = saveProviderConfig(providerId, apiKey, defaultModel || '');
86
+ return { success: true, path: configPath };
87
+ });
88
+
89
+ router.handle('import.claude', () => ({ imported: queries.importClaudeCodeConversations() }));
90
+
91
+ router.handle('discover.claude', () => ({ discovered: queries.discoverClaudeCodeConversations() }));
92
+
93
+ router.handle('ws.stats', () => wsOptimizer.getStats());
94
+
95
+ router.handle('agent.subagents', async (p) => {
96
+ if (!p.id) err(400, 'Missing agent id');
97
+ if (p.id === 'claude-code' || p.id === 'cli-claude') {
98
+ const spawnEnv = { ...process.env }; delete spawnEnv.CLAUDECODE;
99
+ const result = spawnSync('claude', ['agents', 'list'], { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], env: spawnEnv });
100
+ if (result.status !== 0 || !result.stdout) return { subAgents: [] };
101
+ const agents = result.stdout.trim().split('\n').filter(l => l.trim()).map(l => l.match(/^ (\S+)\s+·/)).filter(Boolean).map(m => ({ id: m[1], name: m[1] }));
102
+ return { subAgents: agents };
103
+ }
104
+ return { subAgents: SUB_AGENT_MAP[p.id] || [] };
105
+ });
106
+ }
@@ -1,9 +1,4 @@
1
- import { spawn } from 'child_process';
2
- import { createRequire } from 'module';
3
-
4
- const _req = createRequire(import.meta.url);
5
-
6
- export function registerLegacyHandler(wsRouter, { subscriptionIndex, execMachine, activeExecutions, messageQueues, checkpointManager, queries, pm2Manager, pm2Subscribers, getSeq, sendWs, debugLog }) {
1
+ export function registerLegacyHandler(wsRouter, { subscriptionIndex, execMachine, activeExecutions, messageQueues, queries, getSeq, sendWs, debugLog }) {
7
2
  wsRouter.onLegacy((data, ws) => {
8
3
  try {
9
4
  if (data.type === 'subscribe') {
@@ -29,19 +24,6 @@ export function registerLegacyHandler(wsRouter, { subscriptionIndex, execMachine
29
24
  const queueLength = execMachine.getQueue(data.conversationId).length || messageQueues.get(data.conversationId)?.length || 0;
30
25
  sendWs(ws, { type: 'streaming_start', sessionId, conversationId: data.conversationId, agentId: conv?.agentType || conv?.agentId || 'claude-code', queueLength, resumed: true, seq: getSeq(), timestamp: Date.now() });
31
26
  }
32
- if (data.conversationId && checkpointManager.hasPendingCheckpoint(data.conversationId)) {
33
- const checkpoint = checkpointManager.getPendingCheckpoint(data.conversationId);
34
- if (checkpoint) {
35
- debugLog(`[checkpoint] Injecting ${checkpoint.events.length} events to client for ${data.conversationId}`);
36
- const latestSession = queries.getLatestSession(data.conversationId);
37
- if (latestSession) {
38
- sendWs(ws, { type: 'streaming_resumed', sessionId: latestSession.id, conversationId: data.conversationId, resumeFrom: checkpoint.sessionId, eventCount: checkpoint.events.length, chunkCount: checkpoint.chunks.length, timestamp: Date.now() });
39
- checkpointManager.injectCheckpointEvents(latestSession.id, checkpoint, (evt) => {
40
- sendWs(ws, { ...evt, sessionId: latestSession.id, conversationId: data.conversationId });
41
- });
42
- }
43
- }
44
- }
45
27
  } else if (data.type === 'unsubscribe') {
46
28
  if (data.sessionId) {
47
29
  ws.subscriptions.delete(data.sessionId);
@@ -57,97 +39,12 @@ export function registerLegacyHandler(wsRouter, { subscriptionIndex, execMachine
57
39
  debugLog(`[WebSocket] Client ${ws.clientId} unsubscribed from ${data.sessionId || data.conversationId}`);
58
40
  } else if (data.type === 'get_subscriptions') {
59
41
  sendWs(ws, { type: 'subscriptions', subscriptions: Array.from(ws.subscriptions), timestamp: Date.now() });
60
- } else if (data.type === 'set_voice') {
61
- ws.ttsVoiceId = data.voiceId || 'default';
62
42
  } else if (data.type === 'latency_report') {
63
43
  ws.latencyTier = data.quality || 'good';
64
44
  ws.latencyAvg = data.avg || 0;
65
45
  ws.latencyTrend = data.trend || 'stable';
66
46
  } else if (data.type === 'ping') {
67
47
  sendWs(ws, { type: 'pong', requestId: data.requestId, timestamp: Date.now() });
68
- } else if (data.type === 'terminal_start') {
69
- if (ws.terminalProc) { try { ws.terminalProc.kill(); } catch (_) {} }
70
- try {
71
- const pty = _req('node-pty');
72
- const shell = process.env.SHELL || '/bin/bash';
73
- const cwd = data.cwd || process.env.STARTUP_CWD || process.env.HOME || '/';
74
- const proc = pty.spawn(shell, [], { name: 'xterm-256color', cols: data.cols || 80, rows: data.rows || 24, cwd, env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' } });
75
- ws.terminalProc = proc;
76
- ws.terminalPty = true;
77
- proc.on('data', (chunk) => { if (ws.readyState === 1) sendWs(ws, { type: 'terminal_output', data: Buffer.from(chunk).toString('base64'), encoding: 'base64' }); });
78
- proc.on('exit', (code) => { if (ws.readyState === 1) sendWs(ws, { type: 'terminal_exit', code }); ws.terminalProc = null; });
79
- proc.on('error', (err) => { console.error('[TERMINAL] PTY error (contained):', err.message); if (ws.readyState === 1) sendWs(ws, { type: 'terminal_exit', code: 1, error: err.message }); ws.terminalProc = null; });
80
- sendWs(ws, { type: 'terminal_started', timestamp: Date.now() });
81
- } catch (_e) {
82
- console.error('[TERMINAL] Failed to spawn PTY, falling back to pipes:', _e.message);
83
- const shell = process.env.SHELL || '/bin/bash';
84
- const cwd = data.cwd || process.env.STARTUP_CWD || process.env.HOME || '/';
85
- const proc = spawn(shell, ['-i'], { cwd, env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' }, stdio: ['pipe', 'pipe', 'pipe'] });
86
- ws.terminalProc = proc;
87
- ws.terminalPty = false;
88
- proc.stdout.on('data', (chunk) => { if (ws.readyState === 1) sendWs(ws, { type: 'terminal_output', data: chunk.toString('base64'), encoding: 'base64' }); });
89
- proc.stderr.on('data', (chunk) => { if (ws.readyState === 1) sendWs(ws, { type: 'terminal_output', data: chunk.toString('base64'), encoding: 'base64' }); });
90
- proc.on('exit', (code) => { if (ws.readyState === 1) sendWs(ws, { type: 'terminal_exit', code }); ws.terminalProc = null; });
91
- proc.on('error', (err) => { console.error('[TERMINAL] Spawn error (contained):', err.message); if (ws.readyState === 1) sendWs(ws, { type: 'terminal_exit', code: 1, error: err.message }); ws.terminalProc = null; });
92
- proc.stdin.on('error', () => {});
93
- proc.stdout.on('error', () => {});
94
- proc.stderr.on('error', () => {});
95
- sendWs(ws, { type: 'terminal_started', timestamp: Date.now() });
96
- }
97
- } else if (data.type === 'terminal_input') {
98
- if (ws.terminalProc) {
99
- try {
100
- const input = Buffer.from(data.data, 'base64');
101
- if (ws.terminalPty) { ws.terminalProc.write(input); }
102
- else if (ws.terminalProc.stdin && ws.terminalProc.stdin.writable) { ws.terminalProc.stdin.write(input); }
103
- } catch (_) {}
104
- }
105
- } else if (data.type === 'terminal_resize') {
106
- if (ws.terminalProc && ws.terminalPty) {
107
- try {
108
- const { cols, rows } = data;
109
- if (cols && rows && typeof ws.terminalProc.resize === 'function') { ws.terminalProc.resize(cols, rows); }
110
- } catch (_) {}
111
- }
112
- } else if (data.type === 'terminal_stop') {
113
- if (ws.terminalProc) { try { ws.terminalProc.kill(); } catch (_) {} ws.terminalProc = null; }
114
- } else if (data.type === 'pm2_list') {
115
- if (!pm2Manager.connected) {
116
- if (ws.readyState === 1) sendWs(ws, { type: 'pm2_unavailable', reason: 'PM2 not connected', timestamp: Date.now() });
117
- } else {
118
- pm2Manager.listProcesses().then(processes => {
119
- if (ws.readyState === 1) {
120
- const hasActive = processes.some(p => ['online', 'launching', 'stopping', 'waiting restart'].includes(p.status));
121
- sendWs(ws, { type: 'pm2_list_response', processes, hasActive });
122
- }
123
- }).catch(() => { if (ws.readyState === 1) sendWs(ws, { type: 'pm2_unavailable', reason: 'list failed', timestamp: Date.now() }); });
124
- }
125
- } else if (data.type === 'pm2_start_monitoring') {
126
- pm2Subscribers.add(ws);
127
- ws.pm2Subscribed = true;
128
- if (!pm2Manager.connected) {
129
- if (ws.readyState === 1) sendWs(ws, { type: 'pm2_unavailable', reason: 'PM2 not connected', timestamp: Date.now() });
130
- } else {
131
- sendWs(ws, { type: 'pm2_monitoring_started' });
132
- }
133
- } else if (data.type === 'pm2_stop_monitoring') {
134
- pm2Subscribers.delete(ws);
135
- ws.pm2Subscribed = false;
136
- sendWs(ws, { type: 'pm2_monitoring_stopped' });
137
- } else if (data.type === 'pm2_start') {
138
- pm2Manager.startProcess(data.name).then(result => { sendWs(ws, { type: 'pm2_start_response', name: data.name, ...result }); });
139
- } else if (data.type === 'pm2_stop') {
140
- pm2Manager.stopProcess(data.name).then(result => { sendWs(ws, { type: 'pm2_stop_response', name: data.name, ...result }); });
141
- } else if (data.type === 'pm2_restart') {
142
- pm2Manager.restartProcess(data.name).then(result => { sendWs(ws, { type: 'pm2_restart_response', name: data.name, ...result }); });
143
- } else if (data.type === 'pm2_delete') {
144
- pm2Manager.deleteProcess(data.name).then(result => { sendWs(ws, { type: 'pm2_delete_response', name: data.name, ...result }); });
145
- } else if (data.type === 'pm2_logs') {
146
- pm2Manager.getLogs(data.name, { lines: data.lines || 100 }).then(result => { sendWs(ws, { type: 'pm2_logs_response', name: data.name, ...result }); });
147
- } else if (data.type === 'pm2_flush_logs') {
148
- pm2Manager.flushLogs(data.name).then(result => { sendWs(ws, { type: 'pm2_flush_logs_response', name: data.name, ...result }); });
149
- } else if (data.type === 'pm2_ping') {
150
- pm2Manager.ping().then(result => { sendWs(ws, { type: 'pm2_ping_response', ...result }); });
151
48
  }
152
49
  } catch (err) { console.error('[WS-LEGACY] Handler error (contained):', err.message); }
153
50
  });
package/lib/ws-setup.js CHANGED
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { WebSocketServer } from 'ws';
4
4
  import { registerLegacyHandler } from './ws-legacy-handlers.js';
5
5
 
6
- export function createWsSetup(server, { BASE_URL, watch, staticDir, _assetCache, htmlState, sendWs, wsRouter, debugLog, subscriptionIndex, syncClients, pm2Subscribers, wsOptimizer, legacyDeps }) {
6
+ export function createWsSetup(server, { BASE_URL, watch, staticDir, _assetCache, htmlState, sendWs, wsRouter, debugLog, subscriptionIndex, syncClients, wsOptimizer, legacyDeps }) {
7
7
  const hotReloadClients = [];
8
8
 
9
9
  const wss = new WebSocketServer({ server, perMessageDeflate: false });
@@ -31,14 +31,12 @@ export function createWsSetup(server, { BASE_URL, watch, staticDir, _assetCache,
31
31
  ws.on('message', (msg) => { try { wsRouter.onMessage(ws, msg); } catch (e) { console.error('[WS] Message handler error (contained):', e.message); } });
32
32
  ws.on('pong', () => { ws.isAlive = true; });
33
33
  ws.on('close', () => {
34
- if (ws.terminalProc) { try { ws.terminalProc.kill(); } catch (_) {} ws.terminalProc = null; }
35
34
  syncClients.delete(ws);
36
35
  wsOptimizer.removeClient(ws);
37
36
  for (const sub of ws.subscriptions) {
38
37
  const idx = subscriptionIndex.get(sub);
39
38
  if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(sub); }
40
39
  }
41
- if (ws.pm2Subscribed) pm2Subscribers.delete(ws);
42
40
  debugLog(`[WebSocket] Client ${ws.clientId} disconnected`);
43
41
  });
44
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.917",
3
+ "version": "1.0.918",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -26,10 +26,8 @@
26
26
  "@agentclientprotocol/sdk": "^0.4.1",
27
27
  "@anthropic-ai/claude-code": "^2.1.37",
28
28
  "@google/gemini-cli": "latest",
29
- "@huggingface/transformers": "^3.8.1",
30
29
  "@kilocode/cli": "latest",
31
30
  "@lanmower/ccf": "^1.0.4",
32
- "audio-decode": "^2.2.3",
33
31
  "better-sqlite3": "^12.6.2",
34
32
  "busboy": "^1.6.0",
35
33
  "ccfollow": "^1.0.7",
@@ -37,13 +35,10 @@
37
35
  "express": "^5.2.1",
38
36
  "form-data": "^4.0.5",
39
37
  "fsbrowse": "latest",
40
- "google-auth-library": "^10.5.0",
41
38
  "lru-cache": "^11.2.7",
42
39
  "msgpackr": "^1.11.8",
43
- "onnxruntime-node": "1.21.0",
44
40
  "opencode-ai": "^1.2.15",
45
41
  "p-retry": "^7.1.1",
46
- "pm2": "^5.4.3",
47
42
  "puppeteer-core": "^24.37.5",
48
43
  "webjsx": "^0.0.73",
49
44
  "webtalk": "^1.0.31",
@@ -51,15 +46,6 @@
51
46
  "xstate": "^5.28.0",
52
47
  "zod": "^4.3.6"
53
48
  },
54
- "optionalDependencies": {
55
- "node-pty": "^1.0.0"
56
- },
57
- "overrides": {
58
- "onnxruntime-web": "npm:empty-npm-package@1.0.0",
59
- "sharp": "npm:empty-npm-package@1.0.0",
60
- "onnxruntime-common": "1.21.0",
61
- "onnxruntime-node": "1.21.0"
62
- },
63
49
  "devDependencies": {
64
50
  "electron": "^35.0.0",
65
51
  "playwright": "^1.59.1",
package/server.js CHANGED
@@ -9,11 +9,8 @@ import { queries } from './database.js';
9
9
  import { runClaudeWithStreaming } from './lib/claude-runner-run.js';
10
10
  import { initializeDescriptors, getAgentDescriptor } from './lib/agent-descriptors.js';
11
11
  import { discoverExternalACPServers, initializeAgentDiscovery } from './lib/agent-discovery.js';
12
- import { startGeminiOAuth, exchangeGeminiOAuthCode, handleGeminiOAuthCallback, getGeminiOAuthStatus, getGeminiOAuthState } from './lib/oauth-gemini.js';
13
- import { initSpeechManager, getSpeech, voiceCacheManager, modelDownloadState, ensureModelsDownloaded, eagerTTS } from './lib/speech-manager.js';
14
12
  import { createRegistry } from './lib/routes-registry.js';
15
13
  import { BROADCAST_TYPES } from './lib/broadcast.js';
16
- import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
17
14
  import { WSOptimizer } from './lib/ws-optimizer.js';
18
15
  import { WsRouter } from './lib/ws-protocol.js';
19
16
  import { encode as wsEncode } from './lib/codec.js';
@@ -25,12 +22,8 @@ import { createAutoImport, createDbRecovery, createPluginLoader } from './lib/se
25
22
  const sendWs = (ws, obj) => { if (ws.readyState === 1) ws.send(wsEncode(obj)); };
26
23
  import { startAll as startACPTools, stopAll as stopACPTools, getStatus as getACPStatus, getPort as getACPPort, ensureRunning, queryModels as queryACPModels, touch as touchACP } from './lib/acp-sdk-manager.js';
27
24
  import * as execMachine from './lib/execution-machine.js';
28
- import * as toolInstallMachine from './lib/tool-install-machine.js';
29
25
  import { _assetCache, htmlState, generateETag, warmAssetCache, serveFile as _serveFile } from './lib/asset-server.js';
30
26
  import { installGMAgentConfigs } from './lib/gm-agent-configs.js';
31
- import * as toolManager from './lib/tool-manager.js';
32
- import { pm2Manager } from './lib/pm2-manager.js';
33
- import CheckpointManager from './lib/checkpoint-manager.js';
34
27
  import { createBroadcast } from './lib/broadcast.js';
35
28
  import { createRecovery } from './lib/recovery.js';
36
29
  import { parseRateLimitResetTime } from './lib/process-message-rate-limit.js';
@@ -42,9 +35,7 @@ import { logError, errLogPath, makeCleanupExecution, makeGetModelsForAgent } fro
42
35
 
43
36
 
44
37
  process.on('uncaughtException', (err, origin) => { console.error('[FATAL] Uncaught exception:', err.message, '| origin:', origin); console.error(err.stack); });
45
-
46
38
  process.on('unhandledRejection', (reason) => { console.error('[FATAL] Unhandled rejection:', reason instanceof Error ? reason.message : reason); if (reason instanceof Error) console.error(reason.stack); });
47
-
48
39
  process.on('SIGHUP', () => { console.log('[SIGNAL] SIGHUP received (ignored - uncrashable)'); });
49
40
  process.on('beforeExit', (code) => { console.log('[PROCESS] beforeExit with code:', code); });
50
41
  process.on('exit', (code) => { console.log('[PROCESS] exit with code:', code); });
@@ -55,7 +46,6 @@ const messageQueues = new Map();
55
46
  const rateLimitState = new Map();
56
47
  let _jsonlWatcher = null;
57
48
  const activeProcessesByRunId = new Map();
58
- const checkpointManager = new CheckpointManager(queries);
59
49
  const STUCK_AGENT_THRESHOLD_MS = 1800000;
60
50
  const NO_PID_GRACE_PERIOD_MS = 60000;
61
51
 
@@ -98,12 +88,11 @@ const _assetDeps = { compressAndSend, acceptsEncoding, watch, BASE_URL, PKG_VERS
98
88
  function serveFile(filePath, res, req) { return _serveFile(filePath, res, req, _assetDeps); }
99
89
 
100
90
  const _routes = {};
101
- const server = http.createServer(createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, serveFile, staticDir, messageQueues, getWss: () => wss, activeExecutions, getACPStatus, discoveredAgents, PKG_VERSION, RATE_LIMIT_MAX, rateLimitMap: _rateLimitMap, routes: _routes, handleGeminiOAuthCallback, handleCodexOAuthCallback, PORT }));
91
+ const server = http.createServer(createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, serveFile, staticDir, messageQueues, getWss: () => wss, activeExecutions, getACPStatus, discoveredAgents, PKG_VERSION, RATE_LIMIT_MAX, rateLimitMap: _rateLimitMap, routes: _routes, PORT }));
102
92
 
103
93
  let broadcastSeq = 0;
104
94
  const syncClients = new Set();
105
95
  const subscriptionIndex = new Map();
106
- const pm2Subscribers = new Set();
107
96
 
108
97
  const wsOptimizer = new WSOptimizer();
109
98
 
@@ -120,24 +109,23 @@ const { scheduleRetry, drainMessageQueue } = createMessageQueue({ queries, messa
120
109
 
121
110
  const { processMessageWithStreaming } = createProcessMessage({
122
111
  queries, activeExecutions, rateLimitState, execMachine,
123
- broadcastSync, runClaudeWithStreaming, cleanupExecution, checkpointManager,
112
+ broadcastSync, runClaudeWithStreaming, cleanupExecution,
124
113
  discoveredAgents, STARTUP_CWD, buildSystemPrompt,
125
- parseRateLimitResetTime, eagerTTS, touchACP,
114
+ parseRateLimitResetTime, touchACP,
126
115
  getJsonlWatcher: () => _jsonlWatcher,
127
116
  debugLog, logError,
128
117
  scheduleRetry, drainMessageQueue, createEventHandler
129
118
  });
130
119
 
131
120
  const wsRouter = new WsRouter();
132
- createRegistry(wsRouter, { queries, sendJSON, parseBody, broadcastSync, debugLog, PORT, BASE_URL, rootDir, STARTUP_CWD, PKG_VERSION, processMessageWithStreaming, activeExecutions, activeProcessesByRunId, activeScripts, messageQueues, rateLimitState, cleanupExecution, discoveredAgents, getACPStatus, modelCache, getModelsForAgent, logError, toolManager, syncClients, wsOptimizer, errLogPath, getJsonlWatcher: () => getJsonlWatcher(), routes: _routes });
121
+ createRegistry(wsRouter, { queries, sendJSON, parseBody, broadcastSync, debugLog, PORT, BASE_URL, rootDir, STARTUP_CWD, PKG_VERSION, processMessageWithStreaming, activeExecutions, activeProcessesByRunId, activeScripts, messageQueues, rateLimitState, cleanupExecution, discoveredAgents, getACPStatus, modelCache, getModelsForAgent, logError, syncClients, wsOptimizer, errLogPath, getJsonlWatcher: () => getJsonlWatcher(), routes: _routes });
133
122
 
134
123
 
135
124
  const { wss, hotReloadClients } = createWsSetup(server, {
136
125
  BASE_URL, watch, staticDir, _assetCache, htmlState, sendWs, wsRouter, debugLog,
137
- subscriptionIndex, syncClients, pm2Subscribers, wsOptimizer,
126
+ subscriptionIndex, syncClients, wsOptimizer,
138
127
  legacyDeps: {
139
- subscriptionIndex, execMachine, activeExecutions, messageQueues,
140
- checkpointManager, queries, pm2Manager, pm2Subscribers,
128
+ subscriptionIndex, execMachine, activeExecutions, messageQueues, queries,
141
129
  getSeq: () => ++broadcastSeq, sendWs, debugLog
142
130
  }
143
131
  });
@@ -147,7 +135,6 @@ const { killActiveExecutions, recoverStaleSessions, resumeInterruptedStreams, is
147
135
  processMessageWithStreaming,
148
136
  queries,
149
137
  broadcastSync,
150
- checkpointManager,
151
138
  drainMessageQueue,
152
139
  stuckThresholdMs: STUCK_AGENT_THRESHOLD_MS,
153
140
  noPidGracePeriodMs: NO_PID_GRACE_PERIOD_MS
@@ -157,7 +144,6 @@ process.on('SIGTERM', () => {
157
144
  console.log('[SIGNAL] SIGTERM received - graceful shutdown');
158
145
  killActiveExecutions();
159
146
  const _jw = getJsonlWatcher(); if (_jw) try { _jw.stop(); } catch (_) {}
160
- try { pm2Manager.disconnect(); } catch (_) {}
161
147
  stopACPTools().catch(() => {}).finally(() => {
162
148
  try { wss.close(() => server.close(() => process.exit(0))); } catch (_) { process.exit(0); }
163
149
  setTimeout(() => process.exit(1), 5000);
@@ -180,9 +166,7 @@ const onServerListenStart = () => {
180
166
  server.on('error', (err) => {
181
167
  if (err.code === 'EADDRINUSE') {
182
168
  console.error(`Port ${PORT} already in use. Waiting 3 seconds before retry...`);
183
- setTimeout(() => {
184
- server.listen(PORT, onServerListenStart);
185
- }, 3000);
169
+ setTimeout(() => { server.listen(PORT, onServerListenStart); }, 3000);
186
170
  } else {
187
171
  console.error('[SERVER] Error (contained):', err.message);
188
172
  }
@@ -195,11 +179,10 @@ const { loadPluginExtensions } = createPluginLoader({ pluginsDir: path.join(__di
195
179
  setInterval(performDbRecovery, 300000);
196
180
 
197
181
  const { onServerReady, getJsonlWatcher } = createOnServerReady({
198
- queries, broadcastSync, warmAssetCache, staticDir, toolManager, discoveredAgents,
182
+ queries, broadcastSync, warmAssetCache, staticDir, discoveredAgents,
199
183
  PORT, BASE_URL, watch, setWatcher: (w) => { _jsonlWatcher = w; }, resumeInterruptedStreams, activeExecutions,
200
184
  debugLog, installGMAgentConfigs, startACPTools, getACPStatus, execMachine,
201
- toolInstallMachine, getSpeech, ensureModelsDownloaded, performAutoImport,
202
- performAgentHealthCheck, pm2Manager, pm2Subscribers, recoverStaleSessions
185
+ performAutoImport, performAgentHealthCheck, recoverStaleSessions
203
186
  });
204
187
 
205
188
  server.listen(PORT, onServerListenStart);