agentgui 1.0.401 → 1.0.402
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/lib/ws-handlers-util.js +50 -2
- package/package.json +4 -1
- package/server.js +3 -1
- package/static/app.js +3 -14
- package/static/js/agent-auth.js +54 -59
- package/static/js/client.js +53 -113
- package/static/js/script-runner.js +22 -43
- package/static/js/ws-client.js +3 -0
package/lib/ws-handlers-util.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
4
|
+
import { execSync, spawn } from 'child_process';
|
|
5
5
|
|
|
6
6
|
function err(code, message) { const e = new Error(message); e.code = code; throw e; }
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ export function register(router, deps) {
|
|
|
9
9
|
const { queries, wsOptimizer, modelDownloadState, ensureModelsDownloaded,
|
|
10
10
|
broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
|
|
11
11
|
startGeminiOAuth, exchangeGeminiOAuthCode, geminiOAuthState,
|
|
12
|
-
STARTUP_CWD } = deps;
|
|
12
|
+
STARTUP_CWD, activeScripts } = deps;
|
|
13
13
|
|
|
14
14
|
router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
|
|
15
15
|
|
|
@@ -183,4 +183,52 @@ export function register(router, deps) {
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
router.handle('ws.stats', () => wsOptimizer.getStats());
|
|
186
|
+
|
|
187
|
+
router.handle('conv.scripts', (p) => {
|
|
188
|
+
const conv = queries.getConversation(p.id);
|
|
189
|
+
if (!conv) err(404, 'Not found');
|
|
190
|
+
const wd = conv.workingDirectory || STARTUP_CWD;
|
|
191
|
+
let hasStart = false, hasDev = false;
|
|
192
|
+
try {
|
|
193
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(wd, 'package.json'), 'utf-8'));
|
|
194
|
+
const scripts = pkg.scripts || {};
|
|
195
|
+
hasStart = !!scripts.start;
|
|
196
|
+
hasDev = !!scripts.dev;
|
|
197
|
+
} catch {}
|
|
198
|
+
const running = activeScripts.has(p.id);
|
|
199
|
+
const runningScript = running ? activeScripts.get(p.id).script : null;
|
|
200
|
+
return { hasStart, hasDev, running, runningScript };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
router.handle('conv.run-script', (p) => {
|
|
204
|
+
const conv = queries.getConversation(p.id);
|
|
205
|
+
if (!conv) err(404, 'Not found');
|
|
206
|
+
if (activeScripts.has(p.id)) err(409, 'Script already running');
|
|
207
|
+
const script = p.script;
|
|
208
|
+
if (script !== 'start' && script !== 'dev') err(400, 'Invalid script');
|
|
209
|
+
const wd = conv.workingDirectory || STARTUP_CWD;
|
|
210
|
+
try {
|
|
211
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(wd, 'package.json'), 'utf-8'));
|
|
212
|
+
if (!pkg.scripts || !pkg.scripts[script]) err(400, `Script "${script}" not found`);
|
|
213
|
+
} catch (e) { if (e.code) throw e; err(400, 'No package.json'); }
|
|
214
|
+
const childEnv = { ...process.env, FORCE_COLOR: '1' };
|
|
215
|
+
delete childEnv.PORT; delete childEnv.BASE_URL; delete childEnv.HOT_RELOAD;
|
|
216
|
+
const isWindows = os.platform() === 'win32';
|
|
217
|
+
const child = spawn('npm', ['run', script], { cwd: wd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: childEnv, shell: isWindows });
|
|
218
|
+
activeScripts.set(p.id, { process: child, script, startTime: Date.now() });
|
|
219
|
+
broadcastSync({ type: 'script_started', conversationId: p.id, script, timestamp: Date.now() });
|
|
220
|
+
const onData = (stream) => (chunk) => broadcastSync({ type: 'script_output', conversationId: p.id, data: chunk.toString(), stream, timestamp: Date.now() });
|
|
221
|
+
child.stdout.on('data', onData('stdout'));
|
|
222
|
+
child.stderr.on('data', onData('stderr'));
|
|
223
|
+
child.on('error', (e) => { activeScripts.delete(p.id); broadcastSync({ type: 'script_stopped', conversationId: p.id, code: 1, error: e.message, timestamp: Date.now() }); });
|
|
224
|
+
child.on('close', (code) => { activeScripts.delete(p.id); broadcastSync({ type: 'script_stopped', conversationId: p.id, code: code || 0, timestamp: Date.now() }); });
|
|
225
|
+
return { ok: true, script, pid: child.pid };
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
router.handle('conv.stop-script', (p) => {
|
|
229
|
+
const entry = activeScripts.get(p.id);
|
|
230
|
+
if (!entry) err(404, 'No running script');
|
|
231
|
+
try { process.kill(-entry.process.pid, 'SIGTERM'); } catch { try { entry.process.kill('SIGTERM'); } catch {} }
|
|
232
|
+
return { ok: true };
|
|
233
|
+
});
|
|
186
234
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.402",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@anthropic-ai/claude-code": "^2.1.37",
|
|
25
|
+
"@google/gemini-cli": "latest",
|
|
26
|
+
"@kilocode/cli": "latest",
|
|
27
|
+
"opencode-ai": "latest",
|
|
25
28
|
"@huggingface/transformers": "^3.8.1",
|
|
26
29
|
"audio-decode": "^2.2.3",
|
|
27
30
|
"better-sqlite3": "^12.6.2",
|
package/server.js
CHANGED
|
@@ -311,6 +311,8 @@ expressApp.use(BASE_URL + '/files/:conversationId', (req, res, next) => {
|
|
|
311
311
|
|
|
312
312
|
function findCommand(cmd) {
|
|
313
313
|
const isWindows = os.platform() === 'win32';
|
|
314
|
+
const localBin = path.join(path.dirname(fileURLToPath(import.meta.url)), 'node_modules', '.bin', isWindows ? cmd + '.cmd' : cmd);
|
|
315
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
314
316
|
try {
|
|
315
317
|
if (isWindows) {
|
|
316
318
|
const result = execSync(`where ${cmd}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
@@ -3892,7 +3894,7 @@ registerUtilHandlers(wsRouter, {
|
|
|
3892
3894
|
broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
|
|
3893
3895
|
startGeminiOAuth, exchangeGeminiOAuthCode,
|
|
3894
3896
|
geminiOAuthState: () => geminiOAuthState,
|
|
3895
|
-
STARTUP_CWD
|
|
3897
|
+
STARTUP_CWD, activeScripts
|
|
3896
3898
|
});
|
|
3897
3899
|
|
|
3898
3900
|
wsRouter.onLegacy((data, ws) => {
|
package/static/app.js
CHANGED
|
@@ -131,8 +131,7 @@ class GMGUIApp {
|
|
|
131
131
|
|
|
132
132
|
async fetchMessages(conversationId) {
|
|
133
133
|
try {
|
|
134
|
-
const
|
|
135
|
-
const data = await res.json();
|
|
134
|
+
const data = await window.wsClient.rpc('msg.ls', { id: conversationId });
|
|
136
135
|
return data.messages || [];
|
|
137
136
|
} catch (e) {
|
|
138
137
|
console.error('[APP] Error fetching messages:', e);
|
|
@@ -289,18 +288,8 @@ class GMGUIApp {
|
|
|
289
288
|
if (!content || !this.currentConversation || !this.selectedAgent) return;
|
|
290
289
|
|
|
291
290
|
try {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
headers: { 'Content-Type': 'application/json' },
|
|
295
|
-
body: JSON.stringify({
|
|
296
|
-
content,
|
|
297
|
-
agentId: this.selectedAgent
|
|
298
|
-
})
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (res.ok) {
|
|
302
|
-
input.value = '';
|
|
303
|
-
}
|
|
291
|
+
await window.wsClient.rpc('msg.send', { id: this.currentConversation, content, agentId: this.selectedAgent });
|
|
292
|
+
input.value = '';
|
|
304
293
|
} catch (e) {
|
|
305
294
|
console.error('[APP] Error sending message:', e);
|
|
306
295
|
}
|
package/static/js/agent-auth.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
(function() {
|
|
2
|
-
var BASE = window.__BASE_URL || '';
|
|
3
2
|
var btn = document.getElementById('agentAuthBtn');
|
|
4
3
|
var dropdown = document.getElementById('agentAuthDropdown');
|
|
5
4
|
var agents = [], providers = {}, authRunning = false, editingProvider = null;
|
|
@@ -20,13 +19,13 @@
|
|
|
20
19
|
function refresh() { fetchAuthStatus(); fetchProviderConfigs(); }
|
|
21
20
|
|
|
22
21
|
function fetchAuthStatus() {
|
|
23
|
-
|
|
22
|
+
window.wsClient.rpc('agent.authstat')
|
|
24
23
|
.then(function(data) { agents = data.agents || []; updateButton(); renderDropdown(); })
|
|
25
24
|
.catch(function() {});
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function fetchProviderConfigs() {
|
|
29
|
-
|
|
28
|
+
window.wsClient.rpc('auth.configs')
|
|
30
29
|
.then(function(data) { providers = data || {}; updateButton(); renderDropdown(); })
|
|
31
30
|
.catch(function() {});
|
|
32
31
|
}
|
|
@@ -114,12 +113,10 @@
|
|
|
114
113
|
function toggleEdit(pid) { editingProvider = editingProvider === pid ? null : pid; renderDropdown(); }
|
|
115
114
|
|
|
116
115
|
function saveProviderKey(providerId, apiKey) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (data.success) { editingProvider = null; fetchProviderConfigs(); }
|
|
122
|
-
}).catch(function() { editingProvider = null; renderDropdown(); });
|
|
116
|
+
window.wsClient.rpc('auth.save', { providerId: providerId, apiKey: apiKey, defaultModel: '' })
|
|
117
|
+
.then(function(data) {
|
|
118
|
+
if (data.success) { editingProvider = null; fetchProviderConfigs(); }
|
|
119
|
+
}).catch(function() { editingProvider = null; renderDropdown(); });
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
function toggleDropdown(e) {
|
|
@@ -201,64 +198,62 @@
|
|
|
201
198
|
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
|
|
202
199
|
if (errorEl) errorEl.style.display = 'none';
|
|
203
200
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
201
|
+
window.wsClient.rpc('gemini.complete', { url: url })
|
|
202
|
+
.then(function(data) {
|
|
203
|
+
if (data.success) {
|
|
204
|
+
cleanupOAuthPolling();
|
|
205
|
+
authRunning = false;
|
|
206
|
+
removeOAuthModal();
|
|
207
|
+
refresh();
|
|
208
|
+
} else {
|
|
209
|
+
if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
|
|
210
|
+
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
211
|
+
}
|
|
212
|
+
}).catch(function(e) {
|
|
213
|
+
if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
|
|
215
214
|
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
216
|
-
}
|
|
217
|
-
}).catch(function(e) {
|
|
218
|
-
if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
|
|
219
|
-
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
220
|
-
});
|
|
215
|
+
});
|
|
221
216
|
}
|
|
222
217
|
|
|
223
218
|
function triggerAuth(agentId) {
|
|
224
219
|
if (authRunning) return;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
showOAuthWaitingModal();
|
|
236
|
-
cleanupOAuthPolling();
|
|
237
|
-
oauthPollInterval = setInterval(function() {
|
|
238
|
-
fetch(BASE + '/api/gemini-oauth/status').then(function(r) { return r.json(); }).then(function(status) {
|
|
239
|
-
if (status.status === 'success') {
|
|
240
|
-
cleanupOAuthPolling();
|
|
241
|
-
authRunning = false;
|
|
242
|
-
removeOAuthModal();
|
|
243
|
-
refresh();
|
|
244
|
-
} else if (status.status === 'error') {
|
|
245
|
-
cleanupOAuthPolling();
|
|
246
|
-
authRunning = false;
|
|
247
|
-
removeOAuthModal();
|
|
248
|
-
}
|
|
249
|
-
}).catch(function() {});
|
|
250
|
-
}, 1500);
|
|
251
|
-
oauthFallbackTimer = setTimeout(function() {
|
|
252
|
-
if (authRunning) showOAuthPasteFallback();
|
|
253
|
-
}, 30000);
|
|
254
|
-
oauthPollTimeout = setTimeout(function() {
|
|
220
|
+
window.wsClient.rpc('agent.auth', { id: agentId })
|
|
221
|
+
.then(function(data) {
|
|
222
|
+
if (data.ok) {
|
|
223
|
+
authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
224
|
+
var term = getTerminal();
|
|
225
|
+
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
|
|
226
|
+
if (data.authUrl) {
|
|
227
|
+
window.open(data.authUrl, '_blank');
|
|
228
|
+
if (agentId === 'gemini') {
|
|
229
|
+
showOAuthWaitingModal();
|
|
255
230
|
cleanupOAuthPolling();
|
|
256
|
-
|
|
257
|
-
|
|
231
|
+
oauthPollInterval = setInterval(function() {
|
|
232
|
+
window.wsClient.rpc('gemini.status')
|
|
233
|
+
.then(function(status) {
|
|
234
|
+
if (status.status === 'success') {
|
|
235
|
+
cleanupOAuthPolling();
|
|
236
|
+
authRunning = false;
|
|
237
|
+
removeOAuthModal();
|
|
238
|
+
refresh();
|
|
239
|
+
} else if (status.status === 'error') {
|
|
240
|
+
cleanupOAuthPolling();
|
|
241
|
+
authRunning = false;
|
|
242
|
+
removeOAuthModal();
|
|
243
|
+
}
|
|
244
|
+
}).catch(function() {});
|
|
245
|
+
}, 1500);
|
|
246
|
+
oauthFallbackTimer = setTimeout(function() {
|
|
247
|
+
if (authRunning) showOAuthPasteFallback();
|
|
248
|
+
}, 30000);
|
|
249
|
+
oauthPollTimeout = setTimeout(function() {
|
|
250
|
+
cleanupOAuthPolling();
|
|
251
|
+
if (authRunning) { authRunning = false; removeOAuthModal(); }
|
|
252
|
+
}, 5 * 60 * 1000);
|
|
253
|
+
}
|
|
258
254
|
}
|
|
259
255
|
}
|
|
260
|
-
}
|
|
261
|
-
}).catch(function() {});
|
|
256
|
+
}).catch(function() {});
|
|
262
257
|
}
|
|
263
258
|
|
|
264
259
|
function onWsMessage(e) {
|
package/static/js/client.js
CHANGED
|
@@ -14,9 +14,10 @@ class AgentGUIClient {
|
|
|
14
14
|
...config
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// Initialize components
|
|
17
|
+
// Initialize components - reuse global wsManager/wsClient if available
|
|
18
18
|
this.renderer = new StreamingRenderer(config.renderer || {});
|
|
19
|
-
this.wsManager = new WebSocketManager(config.websocket || {});
|
|
19
|
+
this.wsManager = window.wsManager || new WebSocketManager(config.websocket || {});
|
|
20
|
+
if (!window.wsManager) window.wsManager = this.wsManager;
|
|
20
21
|
this.eventProcessor = new EventProcessor(config.eventProcessor || {});
|
|
21
22
|
|
|
22
23
|
// Application state
|
|
@@ -881,8 +882,7 @@ class AgentGUIClient {
|
|
|
881
882
|
|
|
882
883
|
async _promptPushIfWeOwnRemote() {
|
|
883
884
|
try {
|
|
884
|
-
const
|
|
885
|
-
const { ownsRemote, hasChanges, hasUnpushed, remoteUrl } = await result.json();
|
|
885
|
+
const { ownsRemote, hasChanges, hasUnpushed, remoteUrl } = await window.wsClient.rpc('git.check');
|
|
886
886
|
if (ownsRemote && (hasChanges || hasUnpushed)) {
|
|
887
887
|
const conv = this.state.currentConversation;
|
|
888
888
|
if (conv) {
|
|
@@ -987,8 +987,7 @@ class AgentGUIClient {
|
|
|
987
987
|
if (!outputEl) return;
|
|
988
988
|
|
|
989
989
|
try {
|
|
990
|
-
const
|
|
991
|
-
const { queue } = await response.json();
|
|
990
|
+
const { queue } = await window.wsClient.rpc('q.ls', { id: conversationId });
|
|
992
991
|
|
|
993
992
|
let queueEl = outputEl.querySelector('.queue-indicator');
|
|
994
993
|
if (!queue || queue.length === 0) {
|
|
@@ -1015,7 +1014,7 @@ class AgentGUIClient {
|
|
|
1015
1014
|
const index = parseInt(e.target.dataset.index);
|
|
1016
1015
|
const msgId = queue[index].messageId;
|
|
1017
1016
|
if (await window.UIDialog.confirm('Delete this queued message?', 'Delete Message')) {
|
|
1018
|
-
await
|
|
1017
|
+
await window.wsClient.rpc('q.del', { id: conversationId, messageId: msgId });
|
|
1019
1018
|
}
|
|
1020
1019
|
});
|
|
1021
1020
|
});
|
|
@@ -1026,11 +1025,7 @@ class AgentGUIClient {
|
|
|
1026
1025
|
const q = queue[index];
|
|
1027
1026
|
const newContent = await window.UIDialog.prompt('Edit message:', q.content, 'Edit Queued Message');
|
|
1028
1027
|
if (newContent !== null && newContent !== q.content) {
|
|
1029
|
-
|
|
1030
|
-
method: 'PATCH',
|
|
1031
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1032
|
-
body: JSON.stringify({ content: newContent })
|
|
1033
|
-
});
|
|
1028
|
+
window.wsClient.rpc('q.upd', { id: conversationId, messageId: q.messageId, content: newContent });
|
|
1034
1029
|
}
|
|
1035
1030
|
});
|
|
1036
1031
|
});
|
|
@@ -1281,12 +1276,7 @@ class AgentGUIClient {
|
|
|
1281
1276
|
} else {
|
|
1282
1277
|
const body = { agentId, title: savedPrompt.substring(0, 50) };
|
|
1283
1278
|
if (model) body.model = model;
|
|
1284
|
-
const
|
|
1285
|
-
method: 'POST',
|
|
1286
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1287
|
-
body: JSON.stringify(body)
|
|
1288
|
-
});
|
|
1289
|
-
const { conversation } = await response.json();
|
|
1279
|
+
const { conversation } = await window.wsClient.rpc('conv.new', body);
|
|
1290
1280
|
this.state.currentConversation = conversation;
|
|
1291
1281
|
this.lockAgentAndModel(agentId, model);
|
|
1292
1282
|
|
|
@@ -1351,10 +1341,7 @@ class AgentGUIClient {
|
|
|
1351
1341
|
if (lastSeq < 0) return;
|
|
1352
1342
|
|
|
1353
1343
|
try {
|
|
1354
|
-
const
|
|
1355
|
-
const resp = await fetch(url);
|
|
1356
|
-
if (!resp.ok) return;
|
|
1357
|
-
const { chunks: rawChunks } = await resp.json();
|
|
1344
|
+
const { chunks: rawChunks } = await window.wsClient.rpc('sess.chunks', { id: sessionId, sinceSeq: lastSeq });
|
|
1358
1345
|
if (!rawChunks || rawChunks.length === 0) return;
|
|
1359
1346
|
|
|
1360
1347
|
const chunks = rawChunks.map(c => ({
|
|
@@ -1545,43 +1532,29 @@ class AgentGUIClient {
|
|
|
1545
1532
|
this.wsManager.sendMessage({ type: 'subscribe', conversationId });
|
|
1546
1533
|
}
|
|
1547
1534
|
|
|
1548
|
-
const streamBody = { content: prompt, agentId };
|
|
1535
|
+
const streamBody = { id: conversationId, content: prompt, agentId };
|
|
1549
1536
|
if (model) streamBody.model = model;
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1568
|
-
body: JSON.stringify(createBody)
|
|
1569
|
-
});
|
|
1570
|
-
if (!createResp.ok) throw new Error(`Failed to recreate conversation: HTTP ${createResp.status}`);
|
|
1571
|
-
const { conversation: newConv } = await createResp.json();
|
|
1572
|
-
this.state.currentConversation = newConv;
|
|
1573
|
-
if (window.conversationManager) {
|
|
1574
|
-
window.conversationManager.loadConversations();
|
|
1575
|
-
window.conversationManager.select(newConv.id);
|
|
1537
|
+
let result;
|
|
1538
|
+
try {
|
|
1539
|
+
result = await window.wsClient.rpc('msg.stream', streamBody);
|
|
1540
|
+
} catch (e) {
|
|
1541
|
+
if (e.code === 404) {
|
|
1542
|
+
console.warn('Conversation not found, recreating:', conversationId);
|
|
1543
|
+
const conv = this.state.currentConversation;
|
|
1544
|
+
const createBody = { agentId, title: conv?.title || prompt.substring(0, 50), workingDirectory: conv?.workingDirectory || null };
|
|
1545
|
+
if (model) createBody.model = model;
|
|
1546
|
+
const { conversation: newConv } = await window.wsClient.rpc('conv.new', createBody);
|
|
1547
|
+
this.state.currentConversation = newConv;
|
|
1548
|
+
if (window.conversationManager) {
|
|
1549
|
+
window.conversationManager.loadConversations();
|
|
1550
|
+
window.conversationManager.select(newConv.id);
|
|
1551
|
+
}
|
|
1552
|
+
this.updateUrlForConversation(newConv.id);
|
|
1553
|
+
return this.streamToConversation(newConv.id, prompt, agentId, model);
|
|
1576
1554
|
}
|
|
1577
|
-
|
|
1578
|
-
return this.streamToConversation(newConv.id, prompt, agentId, model);
|
|
1555
|
+
throw e;
|
|
1579
1556
|
}
|
|
1580
1557
|
|
|
1581
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1582
|
-
|
|
1583
|
-
const result = await response.json();
|
|
1584
|
-
|
|
1585
1558
|
if (result.queued) {
|
|
1586
1559
|
console.log('Message queued, position:', result.queuePosition);
|
|
1587
1560
|
return;
|
|
@@ -1607,26 +1580,8 @@ class AgentGUIClient {
|
|
|
1607
1580
|
async fetchChunks(conversationId, since = 0) {
|
|
1608
1581
|
if (!conversationId) return [];
|
|
1609
1582
|
|
|
1610
|
-
if (this.chunkPollState.abortController) {
|
|
1611
|
-
this.chunkPollState.abortController.abort();
|
|
1612
|
-
}
|
|
1613
|
-
this.chunkPollState.abortController = new AbortController();
|
|
1614
|
-
const signal = this.chunkPollState.abortController.signal;
|
|
1615
|
-
|
|
1616
1583
|
try {
|
|
1617
|
-
const
|
|
1618
|
-
if (since > 0) {
|
|
1619
|
-
params.append('since', since.toString());
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
const url = `${window.__BASE_URL}/api/conversations/${conversationId}/chunks?${params.toString()}`;
|
|
1623
|
-
const response = await fetch(url, { signal });
|
|
1624
|
-
|
|
1625
|
-
if (!response.ok) {
|
|
1626
|
-
throw new Error(`HTTP ${response.status}`);
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
const data = await response.json();
|
|
1584
|
+
const data = await window.wsClient.rpc('conv.chunks', { id: conversationId, since: since > 0 ? since : 0 });
|
|
1630
1585
|
if (!data.ok || !Array.isArray(data.chunks)) {
|
|
1631
1586
|
throw new Error('Invalid chunks response');
|
|
1632
1587
|
}
|
|
@@ -1663,9 +1618,8 @@ class AgentGUIClient {
|
|
|
1663
1618
|
|
|
1664
1619
|
const checkSessionStatus = async () => {
|
|
1665
1620
|
if (!this.state.currentSession?.id) return false;
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
const { session } = await sessionResponse.json();
|
|
1621
|
+
let session;
|
|
1622
|
+
try { ({ session } = await window.wsClient.rpc('sess.get', { id: this.state.currentSession.id })); } catch { return false; }
|
|
1669
1623
|
if (session && (session.status === 'complete' || session.status === 'error')) {
|
|
1670
1624
|
if (session.status === 'complete') {
|
|
1671
1625
|
this.handleStreamingComplete({ sessionId: session.id, conversationId, timestamp: Date.now() });
|
|
@@ -1867,8 +1821,7 @@ class AgentGUIClient {
|
|
|
1867
1821
|
async loadAgents() {
|
|
1868
1822
|
return this._dedupedFetch('loadAgents', async () => {
|
|
1869
1823
|
try {
|
|
1870
|
-
const
|
|
1871
|
-
const { agents } = await response.json();
|
|
1824
|
+
const { agents } = await window.wsClient.rpc('agent.ls');
|
|
1872
1825
|
this.state.agents = agents;
|
|
1873
1826
|
|
|
1874
1827
|
if (this.ui.agentSelector) {
|
|
@@ -1891,8 +1844,7 @@ class AgentGUIClient {
|
|
|
1891
1844
|
|
|
1892
1845
|
async checkSpeechStatus() {
|
|
1893
1846
|
try {
|
|
1894
|
-
const
|
|
1895
|
-
const status = await response.json();
|
|
1847
|
+
const status = await window.wsClient.rpc('speech.status');
|
|
1896
1848
|
if (status.modelsComplete) {
|
|
1897
1849
|
this._modelDownloadProgress = { done: true, complete: true };
|
|
1898
1850
|
this._modelDownloadInProgress = false;
|
|
@@ -1918,8 +1870,7 @@ class AgentGUIClient {
|
|
|
1918
1870
|
return;
|
|
1919
1871
|
}
|
|
1920
1872
|
try {
|
|
1921
|
-
const
|
|
1922
|
-
const { models } = await response.json();
|
|
1873
|
+
const { models } = await window.wsClient.rpc('agent.models', { id: agentId });
|
|
1923
1874
|
this._modelCache.set(agentId, models || []);
|
|
1924
1875
|
this._populateModelSelector(models || []);
|
|
1925
1876
|
} catch (error) {
|
|
@@ -1990,8 +1941,7 @@ class AgentGUIClient {
|
|
|
1990
1941
|
async loadConversations() {
|
|
1991
1942
|
return this._dedupedFetch('loadConversations', async () => {
|
|
1992
1943
|
try {
|
|
1993
|
-
const
|
|
1994
|
-
const { conversations } = await response.json();
|
|
1944
|
+
const { conversations } = await window.wsClient.rpc('conv.ls');
|
|
1995
1945
|
this.state.conversations = conversations;
|
|
1996
1946
|
return conversations;
|
|
1997
1947
|
} catch (error) {
|
|
@@ -2180,17 +2130,7 @@ class AgentGUIClient {
|
|
|
2180
2130
|
if (workingDirectory) body.workingDirectory = workingDirectory;
|
|
2181
2131
|
if (model) body.model = model;
|
|
2182
2132
|
|
|
2183
|
-
const
|
|
2184
|
-
method: 'POST',
|
|
2185
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2186
|
-
body: JSON.stringify(body)
|
|
2187
|
-
});
|
|
2188
|
-
|
|
2189
|
-
if (!response.ok) {
|
|
2190
|
-
throw new Error(`Failed to create conversation: ${response.status}`);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
const { conversation } = await response.json();
|
|
2133
|
+
const { conversation } = await window.wsClient.rpc('conv.new', body);
|
|
2194
2134
|
|
|
2195
2135
|
await this.loadConversations();
|
|
2196
2136
|
|
|
@@ -2282,20 +2222,22 @@ class AgentGUIClient {
|
|
|
2282
2222
|
|
|
2283
2223
|
this._showSkeletonLoading(conversationId);
|
|
2284
2224
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
if (
|
|
2290
|
-
|
|
2225
|
+
let fullData;
|
|
2226
|
+
try {
|
|
2227
|
+
fullData = await window.wsClient.rpc('conv.full', { id: conversationId });
|
|
2228
|
+
} catch (e) {
|
|
2229
|
+
if (e.code === 404) {
|
|
2230
|
+
console.warn('Conversation no longer exists:', conversationId);
|
|
2231
|
+
this.state.currentConversation = null;
|
|
2232
|
+
if (window.conversationManager) window.conversationManager.loadConversations();
|
|
2233
|
+
const outputEl = document.getElementById('output');
|
|
2234
|
+
if (outputEl) outputEl.innerHTML = '<p class="text-secondary" style="padding:2rem;text-align:center">Conversation not found. It may have been lost during a server restart.</p>';
|
|
2235
|
+
this.enableControls();
|
|
2236
|
+
return;
|
|
2291
2237
|
}
|
|
2292
|
-
|
|
2293
|
-
if (outputEl) outputEl.innerHTML = '<p class="text-secondary" style="padding:2rem;text-align:center">Conversation not found. It may have been lost during a server restart.</p>';
|
|
2294
|
-
this.enableControls();
|
|
2295
|
-
return;
|
|
2238
|
+
throw e;
|
|
2296
2239
|
}
|
|
2297
|
-
|
|
2298
|
-
const { conversation, isActivelyStreaming, latestSession, chunks: rawChunks, totalChunks, messages: allMessages } = await resp.json();
|
|
2240
|
+
const { conversation, isActivelyStreaming, latestSession, chunks: rawChunks, totalChunks, messages: allMessages } = fullData;
|
|
2299
2241
|
|
|
2300
2242
|
this.state.currentConversation = conversation;
|
|
2301
2243
|
const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this.state.streamingConversations.has(conversationId);
|
|
@@ -2337,11 +2279,9 @@ class AgentGUIClient {
|
|
|
2337
2279
|
loadMoreBtn.disabled = true;
|
|
2338
2280
|
loadMoreBtn.textContent = 'Loading...';
|
|
2339
2281
|
try {
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
await this.loadConversationMessages(conversationId);
|
|
2344
|
-
}
|
|
2282
|
+
await window.wsClient.rpc('conv.full', { id: conversationId, allChunks: true });
|
|
2283
|
+
this.invalidateCache(conversationId);
|
|
2284
|
+
await this.loadConversationMessages(conversationId);
|
|
2345
2285
|
} catch (e) {
|
|
2346
2286
|
loadMoreBtn.textContent = 'Failed to load. Try again.';
|
|
2347
2287
|
loadMoreBtn.disabled = false;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
(function() {
|
|
2
|
-
const BASE = window.__BASE_URL || '';
|
|
3
2
|
let currentConversationId = null;
|
|
4
3
|
let currentWorkingDirectory = null;
|
|
5
4
|
let scriptState = { running: false, script: null, hasStart: false, hasDev: false };
|
|
@@ -59,25 +58,18 @@
|
|
|
59
58
|
|
|
60
59
|
function fetchConversationAndCheckScripts() {
|
|
61
60
|
if (!currentConversationId) return;
|
|
62
|
-
|
|
63
|
-
fetch(BASE + '/api/conversations/' + currentConversationId)
|
|
64
|
-
.then(function(r) { return r.json(); })
|
|
61
|
+
window.wsClient.rpc('conv.get', { id: currentConversationId })
|
|
65
62
|
.then(function(data) {
|
|
66
63
|
currentWorkingDirectory = data.conversation?.workingDirectory || null;
|
|
67
|
-
if (currentWorkingDirectory)
|
|
68
|
-
showTerminalTab();
|
|
69
|
-
}
|
|
64
|
+
if (currentWorkingDirectory) showTerminalTab();
|
|
70
65
|
checkScripts();
|
|
71
66
|
})
|
|
72
|
-
.catch(function() {
|
|
73
|
-
checkScripts();
|
|
74
|
-
});
|
|
67
|
+
.catch(function() { checkScripts(); });
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
function checkScripts() {
|
|
78
71
|
if (!currentConversationId) return;
|
|
79
|
-
|
|
80
|
-
.then(function(r) { return r.json(); })
|
|
72
|
+
window.wsClient.rpc('conv.scripts', { id: currentConversationId })
|
|
81
73
|
.then(function(data) {
|
|
82
74
|
scriptState.hasStart = data.hasStart;
|
|
83
75
|
scriptState.hasDev = data.hasDev;
|
|
@@ -115,42 +107,29 @@
|
|
|
115
107
|
|
|
116
108
|
function runScript(script) {
|
|
117
109
|
if (!currentConversationId || scriptState.running) return;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
var term = getTerminal();
|
|
134
|
-
if (term) {
|
|
135
|
-
term.clear();
|
|
136
|
-
term.writeln('\x1b[36m[running npm run ' + script + ']\x1b[0m\r\n');
|
|
110
|
+
window.wsClient.rpc('conv.run-script', { id: currentConversationId, script: script })
|
|
111
|
+
.then(function(data) {
|
|
112
|
+
if (data.ok) {
|
|
113
|
+
scriptState.running = true;
|
|
114
|
+
scriptState.script = script;
|
|
115
|
+
hasTerminalContent = false;
|
|
116
|
+
updateButtons();
|
|
117
|
+
showTerminalTab();
|
|
118
|
+
switchToTerminalView();
|
|
119
|
+
var term = getTerminal();
|
|
120
|
+
if (term) {
|
|
121
|
+
term.clear();
|
|
122
|
+
term.writeln('\x1b[36m[running npm run ' + script + ']\x1b[0m\r\n');
|
|
123
|
+
}
|
|
137
124
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.catch(function(err) {
|
|
141
|
-
console.error('Failed to start script:', err);
|
|
142
|
-
});
|
|
125
|
+
})
|
|
126
|
+
.catch(function(err) { console.error('Failed to start script:', err); });
|
|
143
127
|
}
|
|
144
128
|
|
|
145
129
|
function stopScript() {
|
|
146
130
|
if (!currentConversationId) return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
headers: { 'Content-Type': 'application/json' },
|
|
150
|
-
body: '{}'
|
|
151
|
-
}).catch(function(err) {
|
|
152
|
-
console.error('Failed to stop script:', err);
|
|
153
|
-
});
|
|
131
|
+
window.wsClient.rpc('conv.stop-script', { id: currentConversationId })
|
|
132
|
+
.catch(function(err) { console.error('Failed to stop script:', err); });
|
|
154
133
|
}
|
|
155
134
|
|
|
156
135
|
function showTerminalTab() {
|