agentgui 1.0.762 → 1.0.764
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/CLAUDE.md +1 -0
- package/lib/routes-threads.js +99 -0
- package/package.json +1 -1
- package/server.js +28 -181
- package/static/js/websocket-manager.js +3 -1
package/CLAUDE.md
CHANGED
|
@@ -50,6 +50,7 @@ lib/routes-speech.js Speech/TTS HTTP route handlers (stt, tts, voices, speech-
|
|
|
50
50
|
lib/routes-oauth.js OAuth HTTP route handlers (gemini-oauth/*, codex-oauth/*)
|
|
51
51
|
lib/routes-tools.js Tool management HTTP route handlers (list, install, update, history, refresh)
|
|
52
52
|
lib/routes-util.js Utility HTTP route handlers (clone, folders, git, home, version, import)
|
|
53
|
+
lib/routes-threads.js Thread CRUD HTTP route handlers (ACP v0.2.3 thread API)
|
|
53
54
|
lib/ws-protocol.js WebSocket RPC router (WsRouter class)
|
|
54
55
|
lib/ws-optimizer.js Per-client priority queue for WS event batching
|
|
55
56
|
lib/ws-handlers-conv.js Conversation CRUD, chunks, cancel, steer, inject RPC handlers
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function register(deps) {
|
|
2
|
+
const { sendJSON, parseBody, queries } = deps;
|
|
3
|
+
const routes = {};
|
|
4
|
+
|
|
5
|
+
routes['POST /api/threads'] = async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const body = await parseBody(req);
|
|
8
|
+
sendJSON(req, res, 201, queries.createThread(body.metadata || {}));
|
|
9
|
+
} catch (err) {
|
|
10
|
+
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
routes['POST /api/threads/search'] = async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
sendJSON(req, res, 200, queries.searchThreads(await parseBody(req)));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
routes['_match'] = (method, pathOnly) => {
|
|
23
|
+
const key = `${method} ${pathOnly}`;
|
|
24
|
+
if (routes[key]) return routes[key];
|
|
25
|
+
let m;
|
|
26
|
+
if ((m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})$/))) {
|
|
27
|
+
if (method === 'GET') return (req, res) => handleGetThread(req, res, m[1]);
|
|
28
|
+
if (method === 'PATCH') return (req, res) => handlePatchThread(req, res, m[1]);
|
|
29
|
+
if (method === 'DELETE') return (req, res) => handleDeleteThread(req, res, m[1]);
|
|
30
|
+
}
|
|
31
|
+
if (method === 'GET' && (m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/history$/)))
|
|
32
|
+
return (req, res) => handleThreadHistory(req, res, m[1]);
|
|
33
|
+
if (method === 'POST' && (m = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/copy$/)))
|
|
34
|
+
return (req, res) => handleThreadCopy(req, res, m[1]);
|
|
35
|
+
if (method === 'POST' && pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/stream$/))
|
|
36
|
+
return (req, res) => { res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' })); };
|
|
37
|
+
if (method === 'GET' && pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/([a-f0-9-]{36})\/stream$/))
|
|
38
|
+
return (req, res) => { res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' })); };
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async function handleGetThread(req, res, threadId) {
|
|
43
|
+
try {
|
|
44
|
+
const thread = queries.getThread(threadId);
|
|
45
|
+
if (!thread) sendJSON(req, res, 404, { error: 'Thread not found', type: 'not_found' });
|
|
46
|
+
else sendJSON(req, res, 200, thread);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handlePatchThread(req, res, threadId) {
|
|
53
|
+
try {
|
|
54
|
+
sendJSON(req, res, 200, queries.patchThread(threadId, await parseBody(req)));
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const code = err.message.includes('not found') ? 404 : 422;
|
|
57
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'validation_error' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handleDeleteThread(req, res, threadId) {
|
|
62
|
+
try {
|
|
63
|
+
queries.deleteThread(threadId);
|
|
64
|
+
res.writeHead(204); res.end();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.message.includes('not found')) sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
67
|
+
else if (err.message.includes('pending runs')) sendJSON(req, res, 409, { error: err.message, type: 'conflict' });
|
|
68
|
+
else sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function handleThreadHistory(req, res, threadId) {
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
75
|
+
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
76
|
+
const before = url.searchParams.get('before') || null;
|
|
77
|
+
let offset = before ? parseInt(before, 10) : 0;
|
|
78
|
+
const result = queries.getThreadHistory(threadId, limit, offset);
|
|
79
|
+
sendJSON(req, res, 200, { states: result.states, next_cursor: result.hasMore ? String(offset + limit) : null });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const code = err.message.includes('not found') ? 404 : 422;
|
|
82
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'validation_error' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleThreadCopy(req, res, sourceThreadId) {
|
|
87
|
+
try {
|
|
88
|
+
const body = await parseBody(req);
|
|
89
|
+
const newThread = queries.copyThread(sourceThreadId);
|
|
90
|
+
if (body.metadata) sendJSON(req, res, 201, queries.patchThread(newThread.thread_id, { metadata: body.metadata }));
|
|
91
|
+
else sendJSON(req, res, 201, newThread);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const code = err.message.includes('not found') ? 404 : 500;
|
|
94
|
+
sendJSON(req, res, code, { error: err.message, type: code === 404 ? 'not_found' : 'internal_error' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return routes;
|
|
99
|
+
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -23,6 +23,7 @@ import { register as registerSpeechRoutes } from './lib/routes-speech.js';
|
|
|
23
23
|
import { register as registerOAuthRoutes } from './lib/routes-oauth.js';
|
|
24
24
|
import { register as registerUtilRoutes } from './lib/routes-util.js';
|
|
25
25
|
import { register as registerToolRoutes } from './lib/routes-tools.js';
|
|
26
|
+
import { register as registerThreadRoutes } from './lib/routes-threads.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';
|
|
@@ -526,6 +527,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
526
527
|
}
|
|
527
528
|
|
|
528
529
|
if (pathOnly === '/api/health' && req.method === 'GET') {
|
|
530
|
+
let dbStatus = { ok: true };
|
|
531
|
+
try { queries._db.prepare('SELECT 1').get(); } catch (e) { dbStatus = { ok: false, error: e.message }; }
|
|
532
|
+
const queueSizes = {};
|
|
533
|
+
for (const [k, v] of messageQueues) queueSizes[k] = v.length;
|
|
529
534
|
sendJSON(req, res, 200, {
|
|
530
535
|
status: 'ok',
|
|
531
536
|
version: PKG_VERSION,
|
|
@@ -534,7 +539,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
534
539
|
activeExecutions: activeExecutions.size,
|
|
535
540
|
wsClients: wss.clients.size,
|
|
536
541
|
memory: process.memoryUsage(),
|
|
537
|
-
acp: getACPStatus()
|
|
542
|
+
acp: getACPStatus(),
|
|
543
|
+
db: dbStatus,
|
|
544
|
+
queueSizes
|
|
538
545
|
});
|
|
539
546
|
return;
|
|
540
547
|
}
|
|
@@ -1188,7 +1195,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1188
1195
|
}
|
|
1189
1196
|
|
|
1190
1197
|
if (pathOnly === '/api/agents' && req.method === 'GET') {
|
|
1191
|
-
|
|
1198
|
+
debugLog(`[API /api/agents] Returning ${discoveredAgents.length} agents`);
|
|
1192
1199
|
sendJSON(req, res, 200, { agents: discoveredAgents });
|
|
1193
1200
|
return;
|
|
1194
1201
|
}
|
|
@@ -1198,21 +1205,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
1198
1205
|
return;
|
|
1199
1206
|
}
|
|
1200
1207
|
|
|
1201
|
-
if (pathOnly === '/api/health' && req.method === 'GET') {
|
|
1202
|
-
let dbStatus = { ok: true };
|
|
1203
|
-
try { queries._db.prepare('SELECT 1').get(); } catch (e) { dbStatus = { ok: false, error: e.message }; }
|
|
1204
|
-
const queueSizes = {};
|
|
1205
|
-
for (const [k, v] of messageQueues) queueSizes[k] = v.length;
|
|
1206
|
-
sendJSON(req, res, 200, {
|
|
1207
|
-
uptime: process.uptime(),
|
|
1208
|
-
db: dbStatus,
|
|
1209
|
-
activeExecutionCount: activeExecutions.size,
|
|
1210
|
-
queueSizes,
|
|
1211
|
-
wsClientCount: syncClients.size,
|
|
1212
|
-
memory: process.memoryUsage()
|
|
1213
|
-
});
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
1208
|
|
|
1217
1209
|
if (pathOnly === '/api/debug' && req.method === 'GET') {
|
|
1218
1210
|
const execSnap = {};
|
|
@@ -1927,161 +1919,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1927
1919
|
const utilHandler = _utilRoutes._match(req.method, pathOnly);
|
|
1928
1920
|
if (utilHandler) { await utilHandler(req, res); return; }
|
|
1929
1921
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
// POST /threads - Create empty thread
|
|
1934
|
-
if (pathOnly === '/api/threads' && req.method === 'POST') {
|
|
1935
|
-
console.log('[ACP] POST /api/threads HIT');
|
|
1936
|
-
try {
|
|
1937
|
-
const body = await parseBody(req);
|
|
1938
|
-
const metadata = body.metadata || {};
|
|
1939
|
-
const thread = queries.createThread(metadata);
|
|
1940
|
-
sendJSON(req, res, 201, thread);
|
|
1941
|
-
} catch (err) {
|
|
1942
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1943
|
-
}
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
// POST /threads/search - Search threads
|
|
1948
|
-
if (pathOnly === '/api/threads/search' && req.method === 'POST') {
|
|
1949
|
-
try {
|
|
1950
|
-
const body = await parseBody(req);
|
|
1951
|
-
const result = queries.searchThreads(body);
|
|
1952
|
-
sendJSON(req, res, 200, result);
|
|
1953
|
-
} catch (err) {
|
|
1954
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1955
|
-
}
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
// GET /threads/{thread_id} - Get thread by ID
|
|
1960
|
-
const acpThreadMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})$/);
|
|
1961
|
-
if (acpThreadMatch && req.method === 'GET') {
|
|
1962
|
-
const threadId = acpThreadMatch[1];
|
|
1963
|
-
try {
|
|
1964
|
-
const thread = queries.getThread(threadId);
|
|
1965
|
-
if (!thread) {
|
|
1966
|
-
sendJSON(req, res, 404, { error: 'Thread not found', type: 'not_found' });
|
|
1967
|
-
} else {
|
|
1968
|
-
sendJSON(req, res, 200, thread);
|
|
1969
|
-
}
|
|
1970
|
-
} catch (err) {
|
|
1971
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
1972
|
-
}
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
// PATCH /threads/{thread_id} - Update thread metadata
|
|
1977
|
-
if (acpThreadMatch && req.method === 'PATCH') {
|
|
1978
|
-
const threadId = acpThreadMatch[1];
|
|
1979
|
-
try {
|
|
1980
|
-
const body = await parseBody(req);
|
|
1981
|
-
const thread = queries.patchThread(threadId, body);
|
|
1982
|
-
sendJSON(req, res, 200, thread);
|
|
1983
|
-
} catch (err) {
|
|
1984
|
-
if (err.message.includes('not found')) {
|
|
1985
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
1986
|
-
} else {
|
|
1987
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
return;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
// DELETE /threads/{thread_id} - Delete thread (fail if pending runs exist)
|
|
1994
|
-
if (acpThreadMatch && req.method === 'DELETE') {
|
|
1995
|
-
const threadId = acpThreadMatch[1];
|
|
1996
|
-
try {
|
|
1997
|
-
queries.deleteThread(threadId);
|
|
1998
|
-
res.writeHead(204);
|
|
1999
|
-
res.end();
|
|
2000
|
-
} catch (err) {
|
|
2001
|
-
if (err.message.includes('not found')) {
|
|
2002
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2003
|
-
} else if (err.message.includes('pending runs')) {
|
|
2004
|
-
sendJSON(req, res, 409, { error: err.message, type: 'conflict' });
|
|
2005
|
-
} else {
|
|
2006
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// GET /threads/{thread_id}/history - Get thread state history with pagination
|
|
2013
|
-
const threadHistoryMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/history$/);
|
|
2014
|
-
if (threadHistoryMatch && req.method === 'GET') {
|
|
2015
|
-
const threadId = threadHistoryMatch[1];
|
|
2016
|
-
try {
|
|
2017
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
2018
|
-
const limit = parseInt(url.searchParams.get('limit') || '50', 10);
|
|
2019
|
-
const before = url.searchParams.get('before') || null;
|
|
2020
|
-
|
|
2021
|
-
// Convert 'before' cursor to offset if needed
|
|
2022
|
-
let offset = 0;
|
|
2023
|
-
if (before) {
|
|
2024
|
-
// Simple cursor-based pagination: before is an offset value
|
|
2025
|
-
offset = parseInt(before, 10);
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
|
-
const result = queries.getThreadHistory(threadId, limit, offset);
|
|
2029
|
-
|
|
2030
|
-
// Generate next_cursor if there are more results
|
|
2031
|
-
const response = {
|
|
2032
|
-
states: result.states,
|
|
2033
|
-
next_cursor: result.hasMore ? String(offset + limit) : null
|
|
2034
|
-
};
|
|
2035
|
-
|
|
2036
|
-
sendJSON(req, res, 200, response);
|
|
2037
|
-
} catch (err) {
|
|
2038
|
-
if (err.message.includes('not found')) {
|
|
2039
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2040
|
-
} else {
|
|
2041
|
-
sendJSON(req, res, 422, { error: err.message, type: 'validation_error' });
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
// POST /threads/{thread_id}/copy - Copy thread with all states/checkpoints
|
|
2048
|
-
const threadCopyMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/copy$/);
|
|
2049
|
-
if (threadCopyMatch && req.method === 'POST') {
|
|
2050
|
-
const sourceThreadId = threadCopyMatch[1];
|
|
2051
|
-
try {
|
|
2052
|
-
const body = await parseBody(req);
|
|
2053
|
-
const newThread = queries.copyThread(sourceThreadId);
|
|
2054
|
-
|
|
2055
|
-
// Update metadata if provided
|
|
2056
|
-
if (body.metadata) {
|
|
2057
|
-
const updated = queries.patchThread(newThread.thread_id, { metadata: body.metadata });
|
|
2058
|
-
sendJSON(req, res, 201, updated);
|
|
2059
|
-
} else {
|
|
2060
|
-
sendJSON(req, res, 201, newThread);
|
|
2061
|
-
}
|
|
2062
|
-
} catch (err) {
|
|
2063
|
-
if (err.message.includes('not found')) {
|
|
2064
|
-
sendJSON(req, res, 404, { error: err.message, type: 'not_found' });
|
|
2065
|
-
} else {
|
|
2066
|
-
sendJSON(req, res, 500, { error: err.message, type: 'internal_error' });
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// POST /threads/{thread_id}/runs/stream - SSE removed, use WebSocket
|
|
2073
|
-
const threadRunsStreamMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/stream$/);
|
|
2074
|
-
if (threadRunsStreamMatch && req.method === 'POST') {
|
|
2075
|
-
res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' }));
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
// GET /threads/{thread_id}/runs/{run_id}/stream - SSE removed, use WebSocket
|
|
2080
|
-
const threadRunStreamMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})\/runs\/([a-f0-9-]{36})\/stream$/);
|
|
2081
|
-
if (threadRunStreamMatch && req.method === 'GET') {
|
|
2082
|
-
res.writeHead(410); res.end(JSON.stringify({ error: 'SSE removed, use WebSocket' }));
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
1922
|
+
const threadHandler = _threadRoutes._match(req.method, pathOnly);
|
|
1923
|
+
if (threadHandler) { await threadHandler(req, res); return; }
|
|
2085
1924
|
|
|
2086
1925
|
if (routePath.startsWith('/api/image/')) {
|
|
2087
1926
|
const imagePath = routePath.slice('/api/image/'.length);
|
|
@@ -2245,7 +2084,8 @@ function serveFile(filePath, res, req) {
|
|
|
2245
2084
|
fs.readFile(filePath, (err2, data) => {
|
|
2246
2085
|
if (err2) { res.writeHead(500); res.end('Server error'); return; }
|
|
2247
2086
|
let content = data.toString();
|
|
2248
|
-
const
|
|
2087
|
+
const wsToken = process.env.PASSWORD ? `window.__WS_TOKEN='${process.env.PASSWORD.replace(/'/g, "\\'")}';` : '';
|
|
2088
|
+
const baseTag = `<script>window.__BASE_URL='${BASE_URL}';window.__SERVER_VERSION='${PKG_VERSION}';${wsToken}</script>`;
|
|
2249
2089
|
content = content.replace('<head>', `<head>\n <base href="${BASE_URL}/">\n ` + baseTag);
|
|
2250
2090
|
content = content.replace(/(href|src)="vendor\//g, `$1="${BASE_URL}/vendor/`);
|
|
2251
2091
|
content = content.replace(/(src)="\/gm\/js\//g, `$1="${BASE_URL}/js/`);
|
|
@@ -2973,12 +2813,18 @@ const subscriptionIndex = new Map();
|
|
|
2973
2813
|
const pm2Subscribers = new Set();
|
|
2974
2814
|
|
|
2975
2815
|
wss.on('connection', (ws, req) => {
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2816
|
+
const _pwd = process.env.PASSWORD;
|
|
2817
|
+
if (_pwd) {
|
|
2818
|
+
const url = new URL(req.url, 'http://localhost');
|
|
2819
|
+
const token = url.searchParams.get('token');
|
|
2820
|
+
if (token !== _pwd) { ws.close(4001, 'Unauthorized'); return; }
|
|
2821
|
+
}
|
|
2822
|
+
const wsPath = req.url.split('?')[0];
|
|
2823
|
+
const wsRoute = wsPath.startsWith(BASE_URL) ? wsPath.slice(BASE_URL.length) : wsPath;
|
|
2824
|
+
if (wsRoute === '/hot-reload') {
|
|
2979
2825
|
hotReloadClients.push(ws);
|
|
2980
2826
|
ws.on('close', () => { const i = hotReloadClients.indexOf(ws); if (i > -1) hotReloadClients.splice(i, 1); });
|
|
2981
|
-
} else if (
|
|
2827
|
+
} else if (wsRoute === '/sync') {
|
|
2982
2828
|
syncClients.add(ws);
|
|
2983
2829
|
ws.isAlive = true;
|
|
2984
2830
|
ws.subscriptions = new Set();
|
|
@@ -3009,7 +2855,7 @@ wss.on('connection', (ws, req) => {
|
|
|
3009
2855
|
if (ws.pm2Subscribed) {
|
|
3010
2856
|
pm2Subscribers.delete(ws);
|
|
3011
2857
|
}
|
|
3012
|
-
|
|
2858
|
+
debugLog(`[WebSocket] Client ${ws.clientId} disconnected`);
|
|
3013
2859
|
});
|
|
3014
2860
|
}
|
|
3015
2861
|
});
|
|
@@ -3072,6 +2918,7 @@ const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync,
|
|
|
3072
2918
|
const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
|
|
3073
2919
|
const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
|
|
3074
2920
|
const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
|
|
2921
|
+
const _threadRoutes = registerThreadRoutes({ sendJSON, parseBody, queries });
|
|
3075
2922
|
|
|
3076
2923
|
registerConvHandlers(wsRouter, {
|
|
3077
2924
|
queries, activeExecutions, rateLimitState,
|
|
@@ -3086,13 +2933,13 @@ registerMsgHandlers(wsRouter, {
|
|
|
3086
2933
|
|
|
3087
2934
|
registerQueueHandlers(wsRouter, { queries, messageQueues, broadcastSync });
|
|
3088
2935
|
|
|
3089
|
-
|
|
2936
|
+
debugLog('[INIT] registerSessionHandlers, agents: ' + discoveredAgents.length);
|
|
3090
2937
|
registerSessionHandlers(wsRouter, {
|
|
3091
2938
|
db: queries, discoveredAgents, modelCache,
|
|
3092
2939
|
getAgentDescriptor, activeScripts, broadcastSync,
|
|
3093
2940
|
startGeminiOAuth: (req) => startGeminiOAuth(req, { PORT, BASE_URL, rootDir }), geminiOAuthState: getGeminiOAuthState
|
|
3094
2941
|
});
|
|
3095
|
-
|
|
2942
|
+
debugLog('[INIT] registerSessionHandlers completed');
|
|
3096
2943
|
|
|
3097
2944
|
registerRunHandlers(wsRouter, {
|
|
3098
2945
|
queries, discoveredAgents, activeExecutions, activeProcessesByRunId,
|
|
@@ -3165,7 +3012,7 @@ wsRouter.onLegacy((data, ws) => {
|
|
|
3165
3012
|
if (data.conversationId && checkpointManager.hasPendingCheckpoint(data.conversationId)) {
|
|
3166
3013
|
const checkpoint = checkpointManager.getPendingCheckpoint(data.conversationId);
|
|
3167
3014
|
if (checkpoint) {
|
|
3168
|
-
|
|
3015
|
+
debugLog(`[checkpoint] Injecting ${checkpoint.events.length} events to client for ${data.conversationId}`);
|
|
3169
3016
|
|
|
3170
3017
|
const latestSession = queries.getLatestSession(data.conversationId);
|
|
3171
3018
|
if (latestSession) {
|
|
@@ -113,7 +113,9 @@ class WebSocketManager {
|
|
|
113
113
|
getWebSocketURL() {
|
|
114
114
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
115
115
|
const baseURL = window.__BASE_URL || '/gm';
|
|
116
|
-
|
|
116
|
+
let url = `${protocol}//${window.location.host}${baseURL}/sync`;
|
|
117
|
+
if (window.__WS_TOKEN) url += `?token=${encodeURIComponent(window.__WS_TOKEN)}`;
|
|
118
|
+
return url;
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
async connect() {
|