agentgui 1.0.762 → 1.0.763

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.762",
3
+ "version": "1.0.763",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -23,6 +23,7 @@ import { register as 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';
@@ -1927,161 +1928,8 @@ const server = http.createServer(async (req, res) => {
1927
1928
  const utilHandler = _utilRoutes._match(req.method, pathOnly);
1928
1929
  if (utilHandler) { await utilHandler(req, res); return; }
1929
1930
 
1930
- // THREAD API ENDPOINTS (ACP v0.2.3)
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
- }
1931
+ const threadHandler = _threadRoutes._match(req.method, pathOnly);
1932
+ if (threadHandler) { await threadHandler(req, res); return; }
2085
1933
 
2086
1934
  if (routePath.startsWith('/api/image/')) {
2087
1935
  const imagePath = routePath.slice('/api/image/'.length);
@@ -3072,6 +2920,7 @@ const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync,
3072
2920
  const _oauthRoutes = registerOAuthRoutes({ sendJSON, parseBody, PORT, BASE_URL, rootDir });
3073
2921
  const _utilRoutes = registerUtilRoutes({ sendJSON, parseBody, queries, STARTUP_CWD, PKG_VERSION });
3074
2922
  const _toolRoutes = registerToolRoutes({ sendJSON, parseBody, queries, broadcastSync, logError, toolManager });
2923
+ const _threadRoutes = registerThreadRoutes({ sendJSON, parseBody, queries });
3075
2924
 
3076
2925
  registerConvHandlers(wsRouter, {
3077
2926
  queries, activeExecutions, rateLimitState,