agentgui 1.0.798 → 1.0.800
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/speech-manager.js +31 -3
- package/lib/speech.js +67 -50
- package/lib/tool-manager.js +1 -0
- package/lib/tool-spawner.js +27 -0
- package/lib/tool-version.js +20 -4
- package/package.json +1 -16
- package/static/js/client.js +12 -20
package/lib/speech-manager.js
CHANGED
|
@@ -14,14 +14,38 @@ export function initSpeechManager({ broadcastSync, syncClients, queries }) {
|
|
|
14
14
|
_queries = queries;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function isSpeechInstalled() {
|
|
18
|
+
try {
|
|
19
|
+
const r = createRequire(import.meta.url);
|
|
20
|
+
r.resolve('webtalk/speech');
|
|
21
|
+
return true;
|
|
22
|
+
} catch (_) { return false; }
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export async function ensurePocketTtsSetup(onProgress) {
|
|
18
26
|
const r = createRequire(import.meta.url);
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
try {
|
|
28
|
+
const serverTTS = r('webtalk/server-tts');
|
|
29
|
+
return serverTTS.ensureInstalled(onProgress);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code === 'MODULE_NOT_FOUND' || err.code === 'SPEECH_NOT_INSTALLED') return false;
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
export async function getSpeech() {
|
|
24
|
-
if (!speechModule)
|
|
37
|
+
if (!speechModule) {
|
|
38
|
+
try {
|
|
39
|
+
speechModule = await import('./speech.js');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code === 'MODULE_NOT_FOUND' || err.code === 'SPEECH_NOT_INSTALLED') {
|
|
42
|
+
const e = new Error('speech_not_installed: Install Speech Engine from the Tools tab.');
|
|
43
|
+
e.code = 'SPEECH_NOT_INSTALLED';
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
25
49
|
return speechModule;
|
|
26
50
|
}
|
|
27
51
|
|
|
@@ -116,6 +140,10 @@ async function validateAndCleanupModels(modelsDir) {
|
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
export async function ensureModelsDownloaded() {
|
|
143
|
+
if (!isSpeechInstalled()) {
|
|
144
|
+
console.log('[MODELS] Speech Engine not installed, skipping model download.');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
119
147
|
if (modelDownloadState.downloading) {
|
|
120
148
|
return new Promise(resolve => { modelDownloadState.waiters.push(resolve); });
|
|
121
149
|
}
|
package/lib/speech.js
CHANGED
|
@@ -1,50 +1,67 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ttsMemCache.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
export
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
|
|
3
|
+
const _require = createRequire(import.meta.url);
|
|
4
|
+
const ttsMemCache = new Map();
|
|
5
|
+
const TTS_CACHE_MAX_BYTES = 10 * 1024 * 1024;
|
|
6
|
+
let ttsCacheBytes = 0;
|
|
7
|
+
let _speech = null;
|
|
8
|
+
|
|
9
|
+
function loadSpeech() {
|
|
10
|
+
if (_speech) return _speech;
|
|
11
|
+
try {
|
|
12
|
+
_speech = _require('webtalk/speech');
|
|
13
|
+
return _speech;
|
|
14
|
+
} catch (err) {
|
|
15
|
+
const e = new Error('speech_not_installed: Install Speech Engine from the Tools tab.');
|
|
16
|
+
e.code = 'SPEECH_NOT_INSTALLED';
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ttsCacheKey(text, voiceId) {
|
|
22
|
+
return (voiceId || 'default') + ':' + text;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ttsCacheGet(key) {
|
|
26
|
+
return ttsMemCache.get(key) || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ttsCacheSet(key, wav) {
|
|
30
|
+
if (ttsMemCache.has(key)) return;
|
|
31
|
+
const size = wav ? wav.length : 0;
|
|
32
|
+
while (ttsCacheBytes + size > TTS_CACHE_MAX_BYTES && ttsMemCache.size > 0) {
|
|
33
|
+
const oldest = ttsMemCache.keys().next().value;
|
|
34
|
+
const old = ttsMemCache.get(oldest);
|
|
35
|
+
ttsCacheBytes -= old ? old.length : 0;
|
|
36
|
+
ttsMemCache.delete(oldest);
|
|
37
|
+
}
|
|
38
|
+
ttsMemCache.set(key, wav);
|
|
39
|
+
ttsCacheBytes += size;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function transcribe(...args) { return loadSpeech().transcribe(...args); }
|
|
43
|
+
|
|
44
|
+
export async function synthesize(text, voiceId) {
|
|
45
|
+
const key = ttsCacheKey(text, voiceId);
|
|
46
|
+
const cached = ttsCacheGet(key);
|
|
47
|
+
if (cached) return cached;
|
|
48
|
+
const wav = await loadSpeech().synthesize(text, voiceId);
|
|
49
|
+
ttsCacheSet(key, wav);
|
|
50
|
+
return wav;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function synthesizeStream(...args) { return loadSpeech().synthesizeStream(...args); }
|
|
54
|
+
export function getSTT() { return loadSpeech().getSTT(); }
|
|
55
|
+
export function getStatus() {
|
|
56
|
+
try { return loadSpeech().getStatus(); }
|
|
57
|
+
catch (_) { return { sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false, speechNotInstalled: true }; }
|
|
58
|
+
}
|
|
59
|
+
export function getVoices() {
|
|
60
|
+
try { return loadSpeech().getVoices(); }
|
|
61
|
+
catch (_) { return []; }
|
|
62
|
+
}
|
|
63
|
+
export function preloadTTS() { try { loadSpeech().preloadTTS?.(); } catch (_) {} }
|
|
64
|
+
export function resetSTTError() { try { loadSpeech().resetSTTError?.(); } catch (_) {} }
|
|
65
|
+
export function clearCorruptedSTTCache() { try { return loadSpeech().clearCorruptedSTTCache?.(); } catch (_) {} }
|
|
66
|
+
export function getSttOptions() { try { return loadSpeech().getSttOptions?.(); } catch (_) { return {}; } }
|
|
67
|
+
export const VOICE_DIRS = (() => { try { return loadSpeech().VOICE_DIRS; } catch (_) { return []; } })();
|
package/lib/tool-manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createInstaller } from './tool-spawner.js';
|
|
|
3
3
|
import { autoProvision as _autoProvision, startPeriodicUpdateCheck as _startPeriodicUpdateCheck, stopPeriodicUpdateCheck } from './tool-provisioner.js';
|
|
4
4
|
|
|
5
5
|
const TOOLS = [
|
|
6
|
+
{ id: 'speech-engine', name: 'Speech Engine', pkg: 'webtalk', category: 'npm-local', description: 'STT/TTS support (Whisper + ONNX)' },
|
|
6
7
|
{ id: 'cli-claude', name: 'Claude Code', pkg: '@anthropic-ai/claude-code', category: 'cli' },
|
|
7
8
|
{ id: 'cli-opencode', name: 'OpenCode', pkg: 'opencode-ai', category: 'cli' },
|
|
8
9
|
{ id: 'cli-gemini', name: 'Gemini CLI', pkg: '@google/gemini-cli', category: 'cli' },
|
package/lib/tool-spawner.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
3
5
|
import { getCliVersion, getInstalledVersion, clearVersionCache, checkToolViaBunx } from './tool-version.js';
|
|
4
6
|
import * as toolInstallMachine from './tool-install-machine.js';
|
|
5
7
|
|
|
@@ -73,8 +75,33 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
73
75
|
});
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
const spawnNpmLocalInstall = (pkg, onProgress) => new Promise((resolve) => {
|
|
79
|
+
const cmd = isWindows ? 'npm.cmd' : 'npm';
|
|
80
|
+
let completed = false, stderr = '', stdout = '';
|
|
81
|
+
let proc;
|
|
82
|
+
const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
83
|
+
try {
|
|
84
|
+
proc = spawn(cmd, ['install', '--save', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true, cwd: rootDir });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return resolve({ success: false, error: `Failed to spawn npm install: ${err.message}` });
|
|
87
|
+
}
|
|
88
|
+
if (!proc) return resolve({ success: false, error: 'Failed to spawn npm process' });
|
|
89
|
+
const timer = setTimeout(() => { if (!completed) { completed = true; try { proc.kill('SIGKILL'); } catch (_) {} resolve({ success: false, error: 'Timeout (5min)' }); } }, 300000);
|
|
90
|
+
const onData = (d) => { if (onProgress) onProgress({ type: 'progress', data: d.toString() }); };
|
|
91
|
+
if (proc.stdout) proc.stdout.on('data', (d) => { stdout += d.toString(); onData(d); });
|
|
92
|
+
if (proc.stderr) proc.stderr.on('data', (d) => { stderr += d.toString(); onData(d); });
|
|
93
|
+
proc.on('close', (code) => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
if (completed) return;
|
|
96
|
+
completed = true;
|
|
97
|
+
resolve(code === 0 ? { success: true, error: null, pkg } : { success: false, error: (stdout + stderr).substring(0, 1000) || 'Failed' });
|
|
98
|
+
});
|
|
99
|
+
proc.on('error', (err) => { clearTimeout(timer); if (!completed) { completed = true; resolve({ success: false, error: err.message }); } });
|
|
100
|
+
});
|
|
101
|
+
|
|
76
102
|
function spawnForTool(tool, onProgress) {
|
|
77
103
|
const pkg = tool.installPkg || tool.pkg;
|
|
104
|
+
if (tool.category === 'npm-local') return spawnNpmLocalInstall(pkg, onProgress);
|
|
78
105
|
return tool.category === 'cli' ? spawnNpmInstall(pkg, onProgress) : spawnBunxProc(pkg, onProgress);
|
|
79
106
|
}
|
|
80
107
|
|
package/lib/tool-version.js
CHANGED
|
@@ -216,11 +216,26 @@ export function clearVersionCache() {
|
|
|
216
216
|
versionCache.clear();
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
const ROOT_DIR = path.dirname(new URL(import.meta.url).pathname).replace(/\/lib$/, '');
|
|
220
|
+
|
|
221
|
+
export function checkNpmLocalInstalled(pkg) {
|
|
222
|
+
return fs.existsSync(path.join(ROOT_DIR, 'node_modules', pkg, 'package.json'));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function getNpmLocalVersion(pkg) {
|
|
226
|
+
try {
|
|
227
|
+
const p = path.join(ROOT_DIR, 'node_modules', pkg, 'package.json');
|
|
228
|
+
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf8')).version;
|
|
229
|
+
} catch (_) {}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
export async function checkToolViaBunx(pkg, pluginId = null, category = 'plugin', frameWork = null, skipPublishedVersion = false, tools = []) {
|
|
220
234
|
try {
|
|
235
|
+
const isNpmLocal = category === 'npm-local';
|
|
221
236
|
const isCli = category === 'cli';
|
|
222
|
-
const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
|
|
223
|
-
const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork, tools);
|
|
237
|
+
const installed = isNpmLocal ? checkNpmLocalInstalled(pkg) : isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
|
|
238
|
+
const installedVersion = isNpmLocal ? getNpmLocalVersion(pkg) : isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork, tools);
|
|
224
239
|
let publishedVersion = null;
|
|
225
240
|
if (!skipPublishedVersion) {
|
|
226
241
|
publishedVersion = await fetchPublishedVersion(pkg);
|
|
@@ -230,9 +245,10 @@ export async function checkToolViaBunx(pkg, pluginId = null, category = 'plugin'
|
|
|
230
245
|
return { installed, isUpToDate, upgradeNeeded: needsUpdate, output: 'version-check', installedVersion, publishedVersion };
|
|
231
246
|
} catch (err) {
|
|
232
247
|
console.log(`[tool-manager] Error checking ${pkg}:`, err.message);
|
|
248
|
+
const isNpmLocal = category === 'npm-local';
|
|
233
249
|
const isCli = category === 'cli';
|
|
234
|
-
const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
|
|
235
|
-
const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork, tools);
|
|
250
|
+
const installed = isNpmLocal ? checkNpmLocalInstalled(pkg) : isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
|
|
251
|
+
const installedVersion = isNpmLocal ? getNpmLocalVersion(pkg) : isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork, tools);
|
|
236
252
|
return { installed, isUpToDate: false, upgradeNeeded: false, output: '', installedVersion, publishedVersion: null };
|
|
237
253
|
}
|
|
238
254
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.800",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "electron/main.js",
|
|
@@ -24,11 +24,6 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@agentclientprotocol/sdk": "^0.4.1",
|
|
27
|
-
"@anthropic-ai/claude-code": "^2.1.37",
|
|
28
|
-
"@google/gemini-cli": "latest",
|
|
29
|
-
"@huggingface/transformers": "^3.8.1",
|
|
30
|
-
"@kilocode/cli": "latest",
|
|
31
|
-
"audio-decode": "^2.2.3",
|
|
32
27
|
"better-sqlite3": "^12.6.2",
|
|
33
28
|
"busboy": "^1.6.0",
|
|
34
29
|
"execa": "^9.6.1",
|
|
@@ -38,13 +33,9 @@
|
|
|
38
33
|
"google-auth-library": "^10.5.0",
|
|
39
34
|
"lru-cache": "^11.2.7",
|
|
40
35
|
"msgpackr": "^1.11.8",
|
|
41
|
-
"onnxruntime-node": "1.21.0",
|
|
42
|
-
"opencode-ai": "^1.2.15",
|
|
43
36
|
"p-retry": "^7.1.1",
|
|
44
37
|
"pm2": "^5.4.3",
|
|
45
|
-
"puppeteer-core": "^24.37.5",
|
|
46
38
|
"webjsx": "^0.0.73",
|
|
47
|
-
"webtalk": "^1.0.31",
|
|
48
39
|
"ws": "^8.14.2",
|
|
49
40
|
"xstate": "^5.28.0",
|
|
50
41
|
"zod": "^4.3.6"
|
|
@@ -52,12 +43,6 @@
|
|
|
52
43
|
"optionalDependencies": {
|
|
53
44
|
"node-pty": "^1.0.0"
|
|
54
45
|
},
|
|
55
|
-
"overrides": {
|
|
56
|
-
"onnxruntime-web": "npm:empty-npm-package@1.0.0",
|
|
57
|
-
"sharp": "npm:empty-npm-package@1.0.0",
|
|
58
|
-
"onnxruntime-common": "1.21.0",
|
|
59
|
-
"onnxruntime-node": "1.21.0"
|
|
60
|
-
},
|
|
61
46
|
"devDependencies": {
|
|
62
47
|
"electron": "^35.0.0"
|
|
63
48
|
}
|
package/static/js/client.js
CHANGED
|
@@ -2173,16 +2173,17 @@ class AgentGUIClient {
|
|
|
2173
2173
|
blockFrag.appendChild(el);
|
|
2174
2174
|
}
|
|
2175
2175
|
blocksEl.appendChild(blockFrag);
|
|
2176
|
+
// Build tool-use element index once for O(1) lookups instead of O(n) querySelectorAll per chunk
|
|
2177
|
+
const toolUseIndex = new Map();
|
|
2178
|
+
blocksEl.querySelectorAll('.block-tool-use[data-tool-use-id]').forEach(el => toolUseIndex.set(el.dataset.toolUseId, el));
|
|
2176
2179
|
for (const chunk of deferred) {
|
|
2177
2180
|
const b = chunk.block;
|
|
2178
2181
|
if (b.type === 'tool_result') {
|
|
2179
|
-
const
|
|
2180
|
-
const toolUseEl = (tid ? blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`) : null)
|
|
2182
|
+
const toolUseEl = (b.tool_use_id && toolUseIndex.get(b.tool_use_id))
|
|
2181
2183
|
|| (blocksEl.lastElementChild?.classList.contains('block-tool-use') ? blocksEl.lastElementChild : null);
|
|
2182
2184
|
if (toolUseEl) this.renderer.mergeResultIntoToolUse(toolUseEl, b);
|
|
2183
2185
|
} else if (b.type === 'tool_status') {
|
|
2184
|
-
const
|
|
2185
|
-
const toolUseEl = tid && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`);
|
|
2186
|
+
const toolUseEl = b.tool_use_id && toolUseIndex.get(b.tool_use_id);
|
|
2186
2187
|
if (toolUseEl) {
|
|
2187
2188
|
const isError = b.status === 'failed';
|
|
2188
2189
|
const isDone = b.status === 'completed';
|
|
@@ -2845,10 +2846,9 @@ class AgentGUIClient {
|
|
|
2845
2846
|
if (cached && (Date.now() - cached.timestamp) < 300000) {
|
|
2846
2847
|
const outputEl = document.getElementById('output');
|
|
2847
2848
|
if (outputEl) {
|
|
2848
|
-
|
|
2849
|
-
while (cached.dom.firstChild)
|
|
2850
|
-
|
|
2851
|
-
}
|
|
2849
|
+
const children = [];
|
|
2850
|
+
while (cached.dom.firstChild) children.push(cached.dom.firstChild);
|
|
2851
|
+
outputEl.replaceChildren(...children);
|
|
2852
2852
|
window.ConversationState?.selectConversation(conversationId, 'dom_cache_load', 1);
|
|
2853
2853
|
this.state.currentConversation = cached.conversation;
|
|
2854
2854
|
window.dispatchEvent(new CustomEvent('conversation-changed', { detail: { conversationId, conversation: cached.conversation } }));
|
|
@@ -2921,23 +2921,15 @@ class AgentGUIClient {
|
|
|
2921
2921
|
const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this._convIsStreaming(conversationId);
|
|
2922
2922
|
this.applyAgentAndModelSelection(conversation, hasActivity);
|
|
2923
2923
|
|
|
2924
|
+
// Parse chunk data and fetch queue in parallel
|
|
2925
|
+
const queuePromise = window.wsClient.rpc('q.ls', { id: conversationId }).catch(() => ({ queue: [] }));
|
|
2924
2926
|
const chunks = (rawChunks || []).map(chunk => ({
|
|
2925
2927
|
...chunk,
|
|
2926
2928
|
block: typeof chunk.data === 'string' ? JSON.parse(chunk.data) : chunk.data
|
|
2927
2929
|
}));
|
|
2928
2930
|
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
try {
|
|
2932
|
-
const { queue } = await window.wsClient.rpc('q.ls', { id: conversationId });
|
|
2933
|
-
if (queue && queue.length > 0) {
|
|
2934
|
-
queuedMessageIds = new Set(queue.map(q => q.messageId));
|
|
2935
|
-
}
|
|
2936
|
-
} catch (e) {
|
|
2937
|
-
console.warn('Failed to fetch queue:', e.message);
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
// Filter out queued messages from user messages - they'll be rendered in queue indicator instead
|
|
2931
|
+
const { queue: queueResult } = await queuePromise;
|
|
2932
|
+
const queuedMessageIds = new Set((queueResult || []).map(q => q.messageId));
|
|
2941
2933
|
const userMessages = (allMessages || []).filter(m => m.role === 'user' && !queuedMessageIds.has(m.id));
|
|
2942
2934
|
const hasMoreChunks = totalChunks && chunks.length < totalChunks;
|
|
2943
2935
|
|