agentgui 1.0.534 → 1.0.536
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.js +38 -0
- package/lib/acp-http-client.js +125 -0
- package/lib/acp-manager.js +20 -89
- package/lib/acp-process-lifecycle.js +65 -0
- package/lib/ws-handlers-conv.js +6 -0
- package/package.json +1 -1
- package/server.js +19 -33
- package/static/index.html +1 -0
- package/static/js/conversations.js +35 -0
package/database.js
CHANGED
|
@@ -1038,6 +1038,44 @@ export const queries = {
|
|
|
1038
1038
|
}
|
|
1039
1039
|
},
|
|
1040
1040
|
|
|
1041
|
+
deleteAllConversations() {
|
|
1042
|
+
try {
|
|
1043
|
+
const conversations = prep('SELECT id, claudeSessionId FROM conversations').all();
|
|
1044
|
+
|
|
1045
|
+
for (const conv of conversations) {
|
|
1046
|
+
if (conv.claudeSessionId) {
|
|
1047
|
+
this.deleteClaudeSessionFile(conv.claudeSessionId);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const deleteAllStmt = db.transaction(() => {
|
|
1052
|
+
const allSessionIds = prep('SELECT id FROM sessions').all().map(r => r.id);
|
|
1053
|
+
|
|
1054
|
+
prep('DELETE FROM stream_updates');
|
|
1055
|
+
prep('DELETE FROM chunks');
|
|
1056
|
+
prep('DELETE FROM events');
|
|
1057
|
+
|
|
1058
|
+
if (allSessionIds.length > 0) {
|
|
1059
|
+
const placeholders = allSessionIds.map(() => '?').join(',');
|
|
1060
|
+
db.prepare(`DELETE FROM stream_updates WHERE sessionId IN (${placeholders})`).run(...allSessionIds);
|
|
1061
|
+
db.prepare(`DELETE FROM chunks WHERE sessionId IN (${placeholders})`).run(...allSessionIds);
|
|
1062
|
+
db.prepare(`DELETE FROM events WHERE sessionId IN (${placeholders})`).run(...allSessionIds);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
prep('DELETE FROM sessions');
|
|
1066
|
+
prep('DELETE FROM messages');
|
|
1067
|
+
prep('DELETE FROM conversations');
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
deleteAllStmt();
|
|
1071
|
+
console.log('[deleteAllConversations] Deleted all conversations and associated Claude Code files');
|
|
1072
|
+
return true;
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
console.error('[deleteAllConversations] Error deleting all conversations:', err.message);
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
|
|
1041
1079
|
cleanup() {
|
|
1042
1080
|
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
1043
1081
|
const now = Date.now();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP HTTP Client with comprehensive request/response logging
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function logACPCall(method, url, requestData, responseData, error = null) {
|
|
6
|
+
const timestamp = new Date().toISOString();
|
|
7
|
+
const logEntry = {
|
|
8
|
+
timestamp,
|
|
9
|
+
method,
|
|
10
|
+
url,
|
|
11
|
+
request: requestData,
|
|
12
|
+
response: responseData,
|
|
13
|
+
error: error ? error.message : null
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
console.log('[ACP-HTTP]', JSON.stringify(logEntry, null, 2));
|
|
17
|
+
return logEntry;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchACPProvider(baseUrl, port) {
|
|
21
|
+
const url = baseUrl + ':' + port + '/provider';
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
console.log('[ACP-HTTP] → GET ' + url);
|
|
26
|
+
|
|
27
|
+
const response = await fetch(url, {
|
|
28
|
+
method: 'GET',
|
|
29
|
+
headers: { 'Accept': 'application/json' },
|
|
30
|
+
signal: AbortSignal.timeout(3000)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const data = response.ok ? await response.json() : null;
|
|
34
|
+
const duration = Date.now() - startTime;
|
|
35
|
+
|
|
36
|
+
logACPCall('GET', url, {
|
|
37
|
+
headers: { 'Accept': 'application/json' },
|
|
38
|
+
timeout: 3000
|
|
39
|
+
}, {
|
|
40
|
+
status: response.status,
|
|
41
|
+
statusText: response.statusText,
|
|
42
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
43
|
+
body: data,
|
|
44
|
+
duration_ms: duration
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { ok: response.ok, status: response.status, data };
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logACPCall('GET', url, { headers: { 'Accept': 'application/json' } }, null, error);
|
|
50
|
+
return { ok: false, status: 0, data: null, error: error.message };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function fetchACPAgents(baseUrl) {
|
|
55
|
+
const endpoint = baseUrl.endsWith('/') ? baseUrl + 'agents/search' : baseUrl + '/agents/search';
|
|
56
|
+
const requestBody = {};
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
console.log('[ACP-HTTP] → POST ' + endpoint);
|
|
61
|
+
console.log('[ACP-HTTP] Request body: ' + JSON.stringify(requestBody));
|
|
62
|
+
|
|
63
|
+
const response = await fetch(endpoint, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'Accept': 'application/json'
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify(requestBody),
|
|
70
|
+
signal: AbortSignal.timeout(5000)
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const data = response.ok ? await response.json() : null;
|
|
74
|
+
const duration = Date.now() - startTime;
|
|
75
|
+
|
|
76
|
+
logACPCall('POST', endpoint, {
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'Accept': 'application/json'
|
|
80
|
+
},
|
|
81
|
+
body: requestBody,
|
|
82
|
+
timeout: 5000
|
|
83
|
+
}, {
|
|
84
|
+
status: response.status,
|
|
85
|
+
statusText: response.statusText,
|
|
86
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
87
|
+
body: data,
|
|
88
|
+
duration_ms: duration
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return { ok: response.ok, status: response.status, data };
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logACPCall('POST', endpoint, { body: requestBody }, null, error);
|
|
94
|
+
return { ok: false, status: 0, data: null, error: error.message };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function extractCompleteAgentData(agent) {
|
|
99
|
+
return {
|
|
100
|
+
id: agent.agent_id || agent.id,
|
|
101
|
+
name: agent.metadata?.ref?.name || agent.name || 'Unknown Agent',
|
|
102
|
+
metadata: {
|
|
103
|
+
ref: {
|
|
104
|
+
name: agent.metadata?.ref?.name,
|
|
105
|
+
version: agent.metadata?.ref?.version,
|
|
106
|
+
url: agent.metadata?.ref?.url,
|
|
107
|
+
tags: agent.metadata?.ref?.tags
|
|
108
|
+
},
|
|
109
|
+
description: agent.metadata?.description,
|
|
110
|
+
author: agent.metadata?.author,
|
|
111
|
+
license: agent.metadata?.license
|
|
112
|
+
},
|
|
113
|
+
specs: agent.specs ? {
|
|
114
|
+
capabilities: agent.specs.capabilities,
|
|
115
|
+
input_schema: agent.specs.input_schema || agent.specs.input,
|
|
116
|
+
output_schema: agent.specs.output_schema || agent.specs.output,
|
|
117
|
+
thread_state_schema: agent.specs.thread_state_schema || agent.specs.thread_state,
|
|
118
|
+
config_schema: agent.specs.config_schema || agent.specs.config,
|
|
119
|
+
custom_streaming_update_schema: agent.specs.custom_streaming_update_schema || agent.specs.custom_streaming_update
|
|
120
|
+
} : null,
|
|
121
|
+
custom_data: agent.custom_data,
|
|
122
|
+
icon: agent.metadata?.ref?.name?.charAt(0) || 'A',
|
|
123
|
+
protocol: 'acp'
|
|
124
|
+
};
|
|
125
|
+
}
|
package/lib/acp-manager.js
CHANGED
|
@@ -1,74 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const projectRoot = path.resolve(__dirname, '..');
|
|
9
|
-
const isWindows = os.platform() === 'win32';
|
|
1
|
+
import { startProcess as startProc, scheduleRestart as scheduleRestart, MAX_RESTARTS, RESTART_WINDOW_MS, IDLE_TIMEOUT_MS } from './acp-process-lifecycle.js';
|
|
10
2
|
|
|
11
3
|
const ACP_TOOLS = [
|
|
12
4
|
{ id: 'opencode', cmd: 'opencode', args: ['acp'], port: 18100, npxPkg: 'opencode-ai' },
|
|
13
5
|
{ id: 'kilo', cmd: 'kilo', args: ['acp'], port: 18101, npxPkg: '@kilocode/cli' },
|
|
14
6
|
];
|
|
15
7
|
|
|
16
|
-
const MAX_RESTARTS = 10;
|
|
17
|
-
const RESTART_WINDOW_MS = 300000;
|
|
18
8
|
const HEALTH_INTERVAL_MS = 30000;
|
|
19
9
|
const STARTUP_GRACE_MS = 5000;
|
|
20
|
-
const IDLE_TIMEOUT_MS = 120000;
|
|
21
10
|
const processes = new Map();
|
|
22
11
|
let healthTimer = null;
|
|
23
12
|
let shuttingDown = false;
|
|
24
13
|
|
|
25
14
|
function log(msg) { console.log('[ACP] ' + msg); }
|
|
26
15
|
|
|
27
|
-
function resolveBinary(cmd) {
|
|
28
|
-
const ext = isWindows ? '.cmd' : '';
|
|
29
|
-
const localBin = path.join(projectRoot, 'node_modules', '.bin', cmd + ext);
|
|
30
|
-
if (fs.existsSync(localBin)) return localBin;
|
|
31
|
-
return cmd;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
16
|
function startProcess(tool) {
|
|
35
17
|
if (shuttingDown) return null;
|
|
36
18
|
const existing = processes.get(tool.id);
|
|
37
19
|
if (existing?.process && !existing.process.killed) return existing;
|
|
38
20
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const opts = { stdio: ['pipe', 'pipe', 'pipe'], cwd: process.cwd() };
|
|
42
|
-
if (isWindows) opts.shell = true;
|
|
43
|
-
|
|
44
|
-
let proc;
|
|
45
|
-
try { proc = spawn(bin, args, opts); }
|
|
46
|
-
catch (err) { log(tool.id + ' spawn failed: ' + err.message); return null; }
|
|
47
|
-
|
|
48
|
-
const entry = {
|
|
49
|
-
id: tool.id, port: tool.port, process: proc, pid: proc.pid,
|
|
50
|
-
startedAt: Date.now(), restarts: [], healthy: false, lastHealthCheck: 0,
|
|
51
|
-
lastUsed: Date.now(), idleTimer: null,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
proc.stdout.on('data', () => {});
|
|
55
|
-
proc.stderr.on('data', (d) => {
|
|
56
|
-
const t = d.toString().trim();
|
|
57
|
-
if (t) log(tool.id + ': ' + t.substring(0, 200));
|
|
58
|
-
});
|
|
59
|
-
proc.stdout.on('error', () => {});
|
|
60
|
-
proc.stderr.on('error', () => {});
|
|
61
|
-
proc.on('error', (err) => { log(tool.id + ' error: ' + err.message); entry.healthy = false; });
|
|
21
|
+
const entry = startProc(tool, log);
|
|
22
|
+
if (!entry) return null;
|
|
62
23
|
|
|
63
|
-
|
|
24
|
+
entry.process.on('close', (code) => {
|
|
64
25
|
entry.healthy = false;
|
|
65
26
|
if (shuttingDown || entry._stopping) return;
|
|
66
27
|
log(tool.id + ' exited code ' + code);
|
|
67
|
-
scheduleRestart(tool, entry.restarts);
|
|
28
|
+
scheduleRestart(tool, entry.restarts, log, startProcess, () => shuttingDown);
|
|
68
29
|
});
|
|
69
30
|
|
|
70
31
|
processes.set(tool.id, entry);
|
|
71
|
-
log(tool.id + ' started port ' + tool.port + ' pid ' +
|
|
32
|
+
log(tool.id + ' started port ' + tool.port + ' pid ' + entry.process.pid);
|
|
72
33
|
setTimeout(() => checkHealth(tool.id), STARTUP_GRACE_MS);
|
|
73
34
|
resetIdleTimer(tool.id);
|
|
74
35
|
return entry;
|
|
@@ -93,34 +54,19 @@ function stopTool(toolId) {
|
|
|
93
54
|
processes.delete(toolId);
|
|
94
55
|
}
|
|
95
56
|
|
|
96
|
-
function scheduleRestart(tool, prevRestarts = []) {
|
|
97
|
-
if (shuttingDown) return;
|
|
98
|
-
const now = Date.now();
|
|
99
|
-
const recent = prevRestarts.filter(t => now - t < RESTART_WINDOW_MS);
|
|
100
|
-
if (recent.length >= MAX_RESTARTS) {
|
|
101
|
-
log(tool.id + ' exceeded restart limit, giving up');
|
|
102
|
-
processes.delete(tool.id);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const delay = Math.min(1000 * Math.pow(2, recent.length), 30000);
|
|
106
|
-
log(tool.id + ' restarting in ' + delay + 'ms');
|
|
107
|
-
setTimeout(() => {
|
|
108
|
-
if (shuttingDown) return;
|
|
109
|
-
const entry = startProcess(tool);
|
|
110
|
-
if (entry) entry.restarts = [...recent, Date.now()];
|
|
111
|
-
}, delay);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
57
|
async function checkHealth(toolId) {
|
|
115
58
|
const entry = processes.get(toolId);
|
|
116
59
|
if (!entry || shuttingDown) return;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
} catch (_) { entry.healthy = false; }
|
|
60
|
+
|
|
61
|
+
const { fetchACPProvider } = await import('./acp-http-client.js');
|
|
62
|
+
const result = await fetchACPProvider('http://127.0.0.1', entry.port);
|
|
63
|
+
|
|
64
|
+
entry.healthy = result.ok;
|
|
123
65
|
entry.lastHealthCheck = Date.now();
|
|
66
|
+
|
|
67
|
+
if (result.data) {
|
|
68
|
+
entry.providerInfo = result.data;
|
|
69
|
+
}
|
|
124
70
|
}
|
|
125
71
|
|
|
126
72
|
export async function ensureRunning(agentId) {
|
|
@@ -177,6 +123,7 @@ export function getStatus() {
|
|
|
177
123
|
id: tool.id, port: tool.port, running: !!e, healthy: e?.healthy || false,
|
|
178
124
|
pid: e?.pid, uptime: e ? Date.now() - e.startedAt : 0,
|
|
179
125
|
restartCount: e?.restarts.length || 0, idleMs: e ? Date.now() - e.lastUsed : 0,
|
|
126
|
+
providerInfo: e?.providerInfo || null,
|
|
180
127
|
};
|
|
181
128
|
});
|
|
182
129
|
}
|
|
@@ -201,33 +148,17 @@ export async function restart(agentId) {
|
|
|
201
148
|
}
|
|
202
149
|
|
|
203
150
|
export async function queryModels(agentId) {
|
|
204
|
-
const port =
|
|
151
|
+
const port = getPort(agentId);
|
|
205
152
|
if (!port) return [];
|
|
206
153
|
try {
|
|
207
|
-
const res = await fetch('http://127.0.0.1:' + port + '/
|
|
208
|
-
signal: AbortSignal.timeout(5000), headers: { 'Accept': 'application/json' }
|
|
209
|
-
});
|
|
154
|
+
const res = await fetch('http://127.0.0.1:' + port + '/models');
|
|
210
155
|
if (!res.ok) return [];
|
|
211
156
|
const data = await res.json();
|
|
212
|
-
|
|
213
|
-
const providers = (data.all || []).filter(p => connected.has(p.id));
|
|
214
|
-
const seen = new Map();
|
|
215
|
-
for (const prov of providers) {
|
|
216
|
-
for (const m of Object.values(prov.models || {})) {
|
|
217
|
-
if (!seen.has(m.id)) {
|
|
218
|
-
seen.set(m.id, { id: m.id, label: m.name || m.id, provider: prov.name || prov.id });
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return Array.from(seen.values());
|
|
157
|
+
return data.models || [];
|
|
223
158
|
} catch (_) { return []; }
|
|
224
159
|
}
|
|
225
160
|
|
|
226
161
|
export function isAvailable(agentId) {
|
|
227
162
|
const tool = ACP_TOOLS.find(t => t.id === agentId);
|
|
228
|
-
|
|
229
|
-
const bin = resolveBinary(tool.cmd);
|
|
230
|
-
return bin !== tool.cmd || fs.existsSync(bin);
|
|
163
|
+
return !!tool;
|
|
231
164
|
}
|
|
232
|
-
|
|
233
|
-
export const ACP_TOOL_CONFIGS = ACP_TOOLS;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const isWindows = os.platform() === 'win32';
|
|
10
|
+
|
|
11
|
+
export const MAX_RESTARTS = 10;
|
|
12
|
+
export const RESTART_WINDOW_MS = 300000;
|
|
13
|
+
export const IDLE_TIMEOUT_MS = 120000;
|
|
14
|
+
|
|
15
|
+
export function resolveBinary(cmd) {
|
|
16
|
+
const ext = isWindows ? '.cmd' : '';
|
|
17
|
+
const localBin = path.join(projectRoot, 'node_modules', '.bin', cmd + ext);
|
|
18
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
19
|
+
return cmd;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function startProcess(tool, log) {
|
|
23
|
+
const bin = resolveBinary(tool.cmd);
|
|
24
|
+
const args = [...tool.args, '--port', String(tool.port)];
|
|
25
|
+
const opts = { stdio: ['pipe', 'pipe', 'pipe'], cwd: process.cwd() };
|
|
26
|
+
if (isWindows) opts.shell = true;
|
|
27
|
+
|
|
28
|
+
let proc;
|
|
29
|
+
try { proc = spawn(bin, args, opts); }
|
|
30
|
+
catch (err) { log(tool.id + ' spawn failed: ' + err.message); return null; }
|
|
31
|
+
|
|
32
|
+
const entry = {
|
|
33
|
+
id: tool.id, port: tool.port, process: proc, pid: proc.pid,
|
|
34
|
+
startedAt: Date.now(), restarts: [], healthy: false, lastHealthCheck: 0,
|
|
35
|
+
lastUsed: Date.now(), idleTimer: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
proc.stdout.on('data', () => {});
|
|
39
|
+
proc.stderr.on('data', (d) => {
|
|
40
|
+
const t = d.toString().trim();
|
|
41
|
+
if (t) log(tool.id + ': ' + t.substring(0, 200));
|
|
42
|
+
});
|
|
43
|
+
proc.stdout.on('error', () => {});
|
|
44
|
+
proc.stderr.on('error', () => {});
|
|
45
|
+
proc.on('error', (err) => { log(tool.id + ' error: ' + err.message); entry.healthy = false; });
|
|
46
|
+
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function scheduleRestart(tool, prevRestarts, log, startProcessFn, shuttingDown) {
|
|
51
|
+
if (shuttingDown()) return;
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const recent = prevRestarts.filter(t => now - t < RESTART_WINDOW_MS);
|
|
54
|
+
if (recent.length >= MAX_RESTARTS) {
|
|
55
|
+
log(tool.id + ' exceeded restart limit, giving up');
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const delay = Math.min(1000 * Math.pow(2, recent.length), 30000);
|
|
59
|
+
log(tool.id + ' restarting in ' + delay + 'ms');
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
if (shuttingDown()) return;
|
|
62
|
+
const entry = startProcessFn(tool);
|
|
63
|
+
if (entry) entry.restarts = [...recent, Date.now()];
|
|
64
|
+
}, delay);
|
|
65
|
+
}
|
package/lib/ws-handlers-conv.js
CHANGED
|
@@ -43,6 +43,12 @@ export function register(router, deps) {
|
|
|
43
43
|
return { deleted: true };
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
router.handle('conv.del.all', (p) => {
|
|
47
|
+
if (!queries.deleteAllConversations()) fail(500, 'Failed to delete all conversations');
|
|
48
|
+
broadcastSync({ type: 'all_conversations_deleted', timestamp: Date.now() });
|
|
49
|
+
return { deleted: true, message: 'All conversations deleted' };
|
|
50
|
+
});
|
|
51
|
+
|
|
46
52
|
router.handle('conv.full', (p) => {
|
|
47
53
|
const conv = queries.getConversation(p.id);
|
|
48
54
|
if (!conv) notFound();
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -397,41 +397,27 @@ function findCommand(cmd) {
|
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
async function queryACPServerAgents(baseUrl) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (!response.ok) {
|
|
412
|
-
console.error(`Failed to query ACP agents from ${baseUrl}: ${response.status}`);
|
|
413
|
-
return [];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const data = await response.json();
|
|
417
|
-
if (!data.agents || !Array.isArray(data.agents)) {
|
|
418
|
-
console.error(`Invalid agents response from ${baseUrl}`);
|
|
419
|
-
return [];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Convert ACP agent format to our internal format
|
|
423
|
-
return data.agents.map(agent => ({
|
|
424
|
-
id: agent.agent_id || agent.id,
|
|
425
|
-
name: agent.metadata?.ref?.name || agent.name || 'Unknown Agent',
|
|
426
|
-
icon: agent.metadata?.ref?.name?.charAt(0) || 'A',
|
|
427
|
-
path: baseUrl,
|
|
428
|
-
protocol: 'acp',
|
|
429
|
-
description: agent.metadata?.description || '',
|
|
430
|
-
}));
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error(`Error querying ACP server ${baseUrl}:`, error.message);
|
|
400
|
+
const { fetchACPAgents, extractCompleteAgentData } = await import('./lib/acp-http-client.js');
|
|
401
|
+
|
|
402
|
+
const result = await fetchACPAgents(baseUrl);
|
|
403
|
+
|
|
404
|
+
if (!result.ok) {
|
|
405
|
+
console.error(`Failed to query ACP agents from ${baseUrl}: ${result.status} ${result.error || ''}`);
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!result.data?.agents || !Array.isArray(result.data.agents)) {
|
|
410
|
+
console.error(`Invalid agents response from ${baseUrl}`);
|
|
433
411
|
return [];
|
|
434
412
|
}
|
|
413
|
+
|
|
414
|
+
return result.data.agents.map(agent => {
|
|
415
|
+
const complete = extractCompleteAgentData(agent);
|
|
416
|
+
return {
|
|
417
|
+
...complete,
|
|
418
|
+
path: baseUrl
|
|
419
|
+
};
|
|
420
|
+
});
|
|
435
421
|
}
|
|
436
422
|
|
|
437
423
|
function discoverAgents() {
|
package/static/index.html
CHANGED
|
@@ -3043,6 +3043,7 @@
|
|
|
3043
3043
|
<div class="sidebar-header">
|
|
3044
3044
|
<h2>History</h2>
|
|
3045
3045
|
<div class="sidebar-header-actions">
|
|
3046
|
+
<button id="deleteAllConversationsBtn" class="sidebar-clone-btn" data-delete-all-conversations title="Delete all conversations and Claude Code artifacts">Clear All</button>
|
|
3046
3047
|
<button id="cloneRepoBtn" class="sidebar-clone-btn" data-clone-repo title="Clone a GitHub repo">Clone</button>
|
|
3047
3048
|
<button id="newConversationBtn" class="sidebar-new-btn" data-new-conversation title="Start new conversation">+ New</button>
|
|
3048
3049
|
</div>
|
|
@@ -47,6 +47,7 @@ class ConversationManager {
|
|
|
47
47
|
this.setupWebSocketListener();
|
|
48
48
|
this.setupFolderBrowser();
|
|
49
49
|
this.setupCloneUI();
|
|
50
|
+
this.setupDeleteAllButton();
|
|
50
51
|
|
|
51
52
|
this._pollInterval = setInterval(() => this.loadConversations(), 30000);
|
|
52
53
|
|
|
@@ -245,6 +246,40 @@ class ConversationManager {
|
|
|
245
246
|
}));
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
setupDeleteAllButton() {
|
|
250
|
+
this.deleteAllBtn = document.getElementById('deleteAllConversationsBtn');
|
|
251
|
+
if (!this.deleteAllBtn) return;
|
|
252
|
+
this.deleteAllBtn.addEventListener('click', () => this.confirmDeleteAll());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async confirmDeleteAll() {
|
|
256
|
+
if (this.conversations.length === 0) {
|
|
257
|
+
window.UIDialog.alert('No conversations to delete', 'Information');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const confirmed = await window.UIDialog.confirm(
|
|
262
|
+
`Delete all ${this.conversations.length} conversation(s) and associated Claude Code artifacts?\n\nThis action cannot be undone.`,
|
|
263
|
+
'Delete All Conversations'
|
|
264
|
+
);
|
|
265
|
+
if (!confirmed) return;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
this.deleteAllBtn.disabled = true;
|
|
269
|
+
await window.wsClient.rpc('conv.del.all', {});
|
|
270
|
+
console.log('[ConversationManager] Deleted all conversations');
|
|
271
|
+
this.conversations = [];
|
|
272
|
+
this.activeId = null;
|
|
273
|
+
window.dispatchEvent(new CustomEvent('conversation-deselected'));
|
|
274
|
+
this.render();
|
|
275
|
+
} catch (err) {
|
|
276
|
+
console.error('[ConversationManager] Delete all error:', err);
|
|
277
|
+
window.UIDialog.alert('Failed to delete all conversations: ' + (err.message || 'Unknown error'), 'Error');
|
|
278
|
+
} finally {
|
|
279
|
+
this.deleteAllBtn.disabled = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
248
283
|
setupCloneUI() {
|
|
249
284
|
this.cloneBtn = document.getElementById('cloneRepoBtn');
|
|
250
285
|
this.cloneBar = document.getElementById('cloneInputBar');
|