agentgui 1.0.761 → 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
@@ -46,6 +46,11 @@ lib/tool-manager.js Tool facade - re-exports from tool-version, tool-spawner,
46
46
  lib/tool-version.js Version detection for CLI tools and plugins (data-driven framework paths)
47
47
  lib/tool-spawner.js npm/bun install/update spawn with timeout and heartbeat
48
48
  lib/tool-provisioner.js Auto-provisioning and periodic update checking
49
+ lib/routes-speech.js Speech/TTS HTTP route handlers (stt, tts, voices, speech-status)
50
+ lib/routes-oauth.js OAuth HTTP route handlers (gemini-oauth/*, codex-oauth/*)
51
+ lib/routes-tools.js Tool management HTTP route handlers (list, install, update, history, refresh)
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)
49
54
  lib/ws-protocol.js WebSocket RPC router (WsRouter class)
50
55
  lib/ws-optimizer.js Per-client priority queue for WS event batching
51
56
  lib/ws-handlers-conv.js Conversation CRUD, chunks, cancel, steer, inject RPC handlers
@@ -156,6 +161,9 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
156
161
  - `GET /api/conversations/:id` - Get conversation with streaming status
157
162
  - `POST /api/conversations/:id` - Update conversation
158
163
  - `DELETE /api/conversations/:id` - Delete conversation
164
+ - `POST /api/conversations/:id/archive` - Archive conversation (soft-delete)
165
+ - `POST /api/conversations/:id/restore` - Restore archived conversation
166
+ - `GET /api/conversations/archived` - List archived conversations
159
167
  - `GET /api/conversations/:id/messages` - Get messages (query: limit, offset)
160
168
  - `POST /api/conversations/:id/messages` - Send message (body: content, agentId)
161
169
  - `POST /api/conversations/:id/stream` - Start streaming execution
@@ -268,12 +276,12 @@ Server broadcasts:
268
276
 
269
277
  **WSOptimizer** (`lib/ws-optimizer.js`): Per-client priority queue. High-priority events flush immediately; normal/low batch by latency tier (16ms excellent → 200ms bad). Rate limit: 100 msg/sec — overflow is re-queued (not dropped). No `lastKey` deduplication (was removed — caused valid event drops).
270
278
 
271
- ### WS RPC Methods (85 total)
279
+ ### WS RPC Methods (86 total)
272
280
 
273
281
  **agent:** `agent.auth`, `agent.authstat`, `agent.desc`, `agent.get`, `agent.ls`, `agent.models`, `agent.search`, `agent.subagents`, `agent.update`
274
282
  **auth:** `auth.configs`, `auth.save`
275
283
  **codex:** `codex.complete`, `codex.relay`, `codex.start`, `codex.status`
276
- **conv:** `conv.cancel`, `conv.chunks`, `conv.chunks.earlier`, `conv.del`, `conv.del.all`, `conv.export`, `conv.full`, `conv.get`, `conv.inject`, `conv.ls`, `conv.new`, `conv.prune`, `conv.run-script`, `conv.scripts`, `conv.search`, `conv.steer`, `conv.stop-script`, `conv.sync`, `conv.tags`, `conv.upd`
284
+ **conv:** `conv.cancel`, `conv.chunks`, `conv.chunks.earlier`, `conv.del`, `conv.del.all`, `conv.export`, `conv.full`, `conv.get`, `conv.import`, `conv.inject`, `conv.ls`, `conv.new`, `conv.prune`, `conv.run-script`, `conv.scripts`, `conv.search`, `conv.steer`, `conv.stop-script`, `conv.sync`, `conv.tags`, `conv.upd`
277
285
  **gemini:** `gemini.complete`, `gemini.relay`, `gemini.start`, `gemini.status`
278
286
  **git:** `git.check`, `git.push`
279
287
  **msg:** `msg.get`, `msg.ls`, `msg.ls.earlier`, `msg.send`, `msg.stream`
@@ -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.761",
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,