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.
- package/database-schema.js +0 -58
- package/lib/db-queries-cleanup.js +0 -12
- package/lib/db-queries-del.js +0 -1
- package/lib/db-queries.js +0 -4
- package/lib/http-handler.js +1 -10
- package/lib/plugins/database-plugin.js +1 -1
- package/lib/process-message.js +2 -2
- package/lib/provider-config.js +0 -16
- package/lib/recovery.js +2 -12
- package/lib/routes-agent-actions.js +2 -58
- package/lib/routes-debug.js +1 -7
- package/lib/routes-registry.js +5 -17
- package/lib/server-startup.js +1 -59
- package/lib/stream-event-handler.js +1 -3
- package/lib/ws-handlers-session2.js +2 -23
- package/lib/ws-handlers-util.js +106 -175
- package/lib/ws-legacy-handlers.js +1 -104
- package/lib/ws-setup.js +1 -3
- package/package.json +1 -15
- package/server.js +9 -26
- package/test.js +1 -21
- package/ecosystem.config.cjs +0 -22
- package/lib/checkpoint-manager.js +0 -182
- package/lib/db-queries-tools.js +0 -131
- package/lib/db-queries-voice.js +0 -85
- package/lib/oauth-codex.js +0 -164
- package/lib/oauth-common.js +0 -92
- package/lib/oauth-gemini.js +0 -199
- package/lib/plugins/auth-plugin.js +0 -132
- package/lib/plugins/speech-plugin.js +0 -72
- package/lib/plugins/tools-plugin.js +0 -120
- package/lib/pm2-manager.js +0 -170
- package/lib/routes-oauth.js +0 -105
- package/lib/routes-speech.js +0 -173
- package/lib/routes-tools.js +0 -184
- package/lib/speech-manager.js +0 -200
- package/lib/speech.js +0 -50
- package/lib/tool-install-machine.js +0 -157
- package/lib/tool-manager.js +0 -98
- package/lib/tool-provisioner.js +0 -98
- package/lib/tool-spawner.js +0 -163
- package/lib/tool-version-check.js +0 -196
- package/lib/tool-version-fetch.js +0 -68
- package/lib/ws-handlers-oauth.js +0 -76
- package/static/js/agent-auth-oauth.js +0 -159
- package/static/js/pm2-monitor.js +0 -151
- package/static/js/stt-handler.js +0 -147
- package/static/js/tool-install-machine.js +0 -155
- package/static/js/tools-manager-ui.js +0 -124
- package/static/js/tools-manager.js +0 -172
- package/static/js/voice-machine.js +0 -145
- package/static/js/voice.js +0 -134
package/lib/ws-handlers-util.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
112
|
+
broadcastSync, runClaudeWithStreaming, cleanupExecution,
|
|
124
113
|
discoveredAgents, STARTUP_CWD, buildSystemPrompt,
|
|
125
|
-
parseRateLimitResetTime,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
202
|
-
performAgentHealthCheck, pm2Manager, pm2Subscribers, recoverStaleSessions
|
|
185
|
+
performAutoImport, performAgentHealthCheck, recoverStaleSessions
|
|
203
186
|
});
|
|
204
187
|
|
|
205
188
|
server.listen(PORT, onServerListenStart);
|