agentgui 1.0.235 → 1.0.237
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/bin/gmgui.cjs +15 -5
- package/lib/claude-runner.js +72 -7
- package/lib/speech.js +1 -4
- package/package.json +1 -1
- package/server.js +23 -18
- package/static/js/conversations.js +25 -10
- package/static/js/streaming-renderer.js +33 -18
- package/static/js/voice.js +36 -14
- package/telemetry-id +1 -0
package/bin/gmgui.cjs
CHANGED
|
@@ -16,19 +16,29 @@ async function gmgui(args = []) {
|
|
|
16
16
|
const installer = 'npm';
|
|
17
17
|
|
|
18
18
|
// Ensure dependencies are installed only if node_modules is missing
|
|
19
|
-
// Skip this for bunx which
|
|
19
|
+
// Skip this for bunx/npx which manage dependencies independently
|
|
20
20
|
const nodeModulesPath = path.join(projectRoot, 'node_modules');
|
|
21
|
-
const
|
|
21
|
+
const execPath = process.env.npm_execpath || '';
|
|
22
|
+
const isBunx = execPath.includes('bun') || process.env.BUN_INSTALL;
|
|
23
|
+
const isNpx = execPath.includes('npx') || process.env._.includes('npx');
|
|
24
|
+
|
|
25
|
+
// Also skip if running from temp/cache directory (bunx/npm cache)
|
|
26
|
+
const isFromCache = projectRoot.includes('node_modules') &&
|
|
27
|
+
(projectRoot.includes('.bun') || projectRoot.includes('_npx') || projectRoot.includes('npm-cache'));
|
|
22
28
|
|
|
23
|
-
if (!isBunx && !fs.existsSync(nodeModulesPath)) {
|
|
29
|
+
if (!isBunx && !isNpx && !isFromCache && !fs.existsSync(nodeModulesPath)) {
|
|
24
30
|
console.log(`Installing dependencies with ${installer}...`);
|
|
25
31
|
const installResult = spawnSync(installer, ['install'], {
|
|
26
32
|
cwd: projectRoot,
|
|
27
|
-
stdio: 'inherit'
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
shell: true
|
|
28
35
|
});
|
|
29
|
-
if (installResult.status !== 0) {
|
|
36
|
+
if (installResult.status !== 0 && installResult.status !== null) {
|
|
30
37
|
throw new Error(`${installer} install failed with code ${installResult.status}`);
|
|
31
38
|
}
|
|
39
|
+
if (installResult.error) {
|
|
40
|
+
throw new Error(`${installer} install failed: ${installResult.error.message}`);
|
|
41
|
+
}
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
const port = process.env.PORT || 3000;
|
package/lib/claude-runner.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
|
|
3
|
+
const isWindows = process.platform === 'win32';
|
|
4
|
+
|
|
5
|
+
function getSpawnOptions(cwd, additionalOptions = {}) {
|
|
6
|
+
const options = { cwd, ...additionalOptions };
|
|
7
|
+
if (isWindows) {
|
|
8
|
+
options.shell = true;
|
|
9
|
+
}
|
|
10
|
+
return options;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
/**
|
|
4
14
|
* Agent Framework
|
|
5
15
|
* Extensible registry for AI agent CLI integrations
|
|
@@ -51,7 +61,7 @@ class AgentRunner {
|
|
|
51
61
|
} = config;
|
|
52
62
|
|
|
53
63
|
const args = this.buildArgs(prompt, config);
|
|
54
|
-
const proc = spawn(this.command, args,
|
|
64
|
+
const proc = spawn(this.command, args, getSpawnOptions(cwd));
|
|
55
65
|
|
|
56
66
|
if (config.onPid) {
|
|
57
67
|
try { config.onPid(proc.pid); } catch (e) {}
|
|
@@ -211,7 +221,7 @@ class AgentRunner {
|
|
|
211
221
|
const baseArgs = this.requiresAdapter && this.adapterCommand ? this.adapterArgs : this.buildArgs(prompt, config);
|
|
212
222
|
const args = [...baseArgs];
|
|
213
223
|
|
|
214
|
-
const proc = spawn(cmd, args,
|
|
224
|
+
const proc = spawn(cmd, args, getSpawnOptions(cwd));
|
|
215
225
|
|
|
216
226
|
if (config.onPid) {
|
|
217
227
|
try { config.onPid(proc.pid); } catch (e) {}
|
|
@@ -474,13 +484,15 @@ class AgentRegistry {
|
|
|
474
484
|
|
|
475
485
|
listACPAvailable() {
|
|
476
486
|
const { spawnSync } = require('child_process');
|
|
487
|
+
const isWindows = process.platform === 'win32';
|
|
477
488
|
return this.list().filter(agent => {
|
|
478
489
|
try {
|
|
479
|
-
const
|
|
490
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
491
|
+
const which = spawnSync(whichCmd, [agent.command], { encoding: 'utf-8', timeout: 3000 });
|
|
480
492
|
if (which.status !== 0) return false;
|
|
481
|
-
const binPath = (which.stdout || '').trim();
|
|
493
|
+
const binPath = (which.stdout || '').trim().split('\n')[0].trim();
|
|
482
494
|
if (!binPath) return false;
|
|
483
|
-
const check = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 10000 });
|
|
495
|
+
const check = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 10000, shell: isWindows });
|
|
484
496
|
return check.status === 0 && (check.stdout || '').trim().length > 0;
|
|
485
497
|
} catch {
|
|
486
498
|
return false;
|
|
@@ -561,11 +573,33 @@ registry.register({
|
|
|
561
573
|
|
|
562
574
|
// Agent message chunk (text response)
|
|
563
575
|
if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
|
|
576
|
+
let contentBlock;
|
|
577
|
+
|
|
578
|
+
// Handle different content formats
|
|
579
|
+
if (typeof update.content === 'string') {
|
|
580
|
+
contentBlock = { type: 'text', text: update.content };
|
|
581
|
+
} else if (update.content.type === 'text' && update.content.text) {
|
|
582
|
+
contentBlock = update.content;
|
|
583
|
+
} else if (update.content.text) {
|
|
584
|
+
contentBlock = { type: 'text', text: update.content.text };
|
|
585
|
+
} else if (update.content.content) {
|
|
586
|
+
const inner = update.content.content;
|
|
587
|
+
if (typeof inner === 'string') {
|
|
588
|
+
contentBlock = { type: 'text', text: inner };
|
|
589
|
+
} else if (inner.type === 'text' && inner.text) {
|
|
590
|
+
contentBlock = inner;
|
|
591
|
+
} else {
|
|
592
|
+
contentBlock = { type: 'text', text: JSON.stringify(inner) };
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
contentBlock = { type: 'text', text: JSON.stringify(update.content) };
|
|
596
|
+
}
|
|
597
|
+
|
|
564
598
|
return {
|
|
565
599
|
type: 'assistant',
|
|
566
600
|
message: {
|
|
567
601
|
role: 'assistant',
|
|
568
|
-
content: [
|
|
602
|
+
content: [contentBlock]
|
|
569
603
|
},
|
|
570
604
|
session_id: params.sessionId
|
|
571
605
|
};
|
|
@@ -705,11 +739,33 @@ function createACPProtocolHandler() {
|
|
|
705
739
|
|
|
706
740
|
// Agent message chunk (text response)
|
|
707
741
|
if (update.sessionUpdate === 'agent_message_chunk' && update.content) {
|
|
742
|
+
let contentBlock;
|
|
743
|
+
|
|
744
|
+
// Handle different content formats
|
|
745
|
+
if (typeof update.content === 'string') {
|
|
746
|
+
contentBlock = { type: 'text', text: update.content };
|
|
747
|
+
} else if (update.content.type === 'text' && update.content.text) {
|
|
748
|
+
contentBlock = update.content;
|
|
749
|
+
} else if (update.content.text) {
|
|
750
|
+
contentBlock = { type: 'text', text: update.content.text };
|
|
751
|
+
} else if (update.content.content) {
|
|
752
|
+
const inner = update.content.content;
|
|
753
|
+
if (typeof inner === 'string') {
|
|
754
|
+
contentBlock = { type: 'text', text: inner };
|
|
755
|
+
} else if (inner.type === 'text' && inner.text) {
|
|
756
|
+
contentBlock = inner;
|
|
757
|
+
} else {
|
|
758
|
+
contentBlock = { type: 'text', text: JSON.stringify(inner) };
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
contentBlock = { type: 'text', text: JSON.stringify(update.content) };
|
|
762
|
+
}
|
|
763
|
+
|
|
708
764
|
return {
|
|
709
765
|
type: 'assistant',
|
|
710
766
|
message: {
|
|
711
767
|
role: 'assistant',
|
|
712
|
-
content: [
|
|
768
|
+
content: [contentBlock]
|
|
713
769
|
},
|
|
714
770
|
session_id: params.sessionId
|
|
715
771
|
};
|
|
@@ -800,6 +856,15 @@ function createACPProtocolHandler() {
|
|
|
800
856
|
};
|
|
801
857
|
}
|
|
802
858
|
|
|
859
|
+
// Plan update
|
|
860
|
+
if (update.sessionUpdate === 'plan') {
|
|
861
|
+
return {
|
|
862
|
+
type: 'plan',
|
|
863
|
+
entries: update.entries || [],
|
|
864
|
+
session_id: params.sessionId
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
803
868
|
return null;
|
|
804
869
|
}
|
|
805
870
|
|
package/lib/speech.js
CHANGED
|
@@ -89,10 +89,7 @@ function synthesize(text, voiceId) {
|
|
|
89
89
|
function synthesizeStream(text, voiceId) {
|
|
90
90
|
if (needsPatch && voiceId && PREDEFINED_IDS.has(voiceId)) {
|
|
91
91
|
return (async function* () {
|
|
92
|
-
|
|
93
|
-
for (const sentence of sentences) {
|
|
94
|
-
yield await synthesizeDirect(sentence, voiceId);
|
|
95
|
-
}
|
|
92
|
+
yield await synthesizeDirect(text, voiceId);
|
|
96
93
|
})();
|
|
97
94
|
}
|
|
98
95
|
return serverTTS.synthesizeStream(text, voiceId, EXTRA_VOICE_DIRS);
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -120,19 +120,16 @@ function flushTTSaccumulator(key, conversationId, sessionId) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
if (voices.size === 0) return;
|
|
123
|
-
const
|
|
123
|
+
const cacheKey = speech.ttsCacheKey(text, vid);
|
|
124
124
|
for (const vid of voices) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
pushTTSAudio(cacheKey, cached, conversationId, sessionId, vid);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
speech.synthesize(sentence, vid).then(wav => {
|
|
133
|
-
pushTTSAudio(cacheKey, wav, conversationId, sessionId, vid);
|
|
134
|
-
}).catch(() => {});
|
|
125
|
+
const cached = speech.ttsCacheGet(cacheKey);
|
|
126
|
+
if (cached) {
|
|
127
|
+
pushTTSAudio(cacheKey, cached, conversationId, sessionId, vid);
|
|
128
|
+
continue;
|
|
135
129
|
}
|
|
130
|
+
speech.synthesize(text, vid).then(wav => {
|
|
131
|
+
pushTTSAudio(cacheKey, wav, conversationId, sessionId, vid);
|
|
132
|
+
}).catch(() => {});
|
|
136
133
|
}
|
|
137
134
|
}).catch(() => {});
|
|
138
135
|
}
|
|
@@ -1263,7 +1260,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1263
1260
|
delete childEnv.PORT;
|
|
1264
1261
|
delete childEnv.BASE_URL;
|
|
1265
1262
|
delete childEnv.HOT_RELOAD;
|
|
1266
|
-
const
|
|
1263
|
+
const isWindows = os.platform() === 'win32';
|
|
1264
|
+
const child = spawn('npm', ['run', script], { cwd: wd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: childEnv, shell: isWindows });
|
|
1267
1265
|
activeScripts.set(conversationId, { process: child, script, startTime: Date.now() });
|
|
1268
1266
|
broadcastSync({ type: 'script_started', conversationId, script, timestamp: Date.now() });
|
|
1269
1267
|
|
|
@@ -1511,7 +1509,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1511
1509
|
|
|
1512
1510
|
const child = spawn(authCmd.cmd, authCmd.args, {
|
|
1513
1511
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1514
|
-
env: { ...process.env, FORCE_COLOR: '1' }
|
|
1512
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
1513
|
+
shell: os.platform() === 'win32'
|
|
1515
1514
|
});
|
|
1516
1515
|
activeScripts.set(conversationId, { process: child, script: 'auth-' + agentId, startTime: Date.now() });
|
|
1517
1516
|
broadcastSync({ type: 'script_started', conversationId, script: 'auth-' + agentId, agentId, timestamp: Date.now() });
|
|
@@ -1724,12 +1723,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
1724
1723
|
return;
|
|
1725
1724
|
}
|
|
1726
1725
|
try {
|
|
1727
|
-
|
|
1726
|
+
const isWindows = os.platform() === 'win32';
|
|
1727
|
+
execSync('git clone https://github.com/' + repo + '.git', {
|
|
1728
1728
|
cwd: cloneDir,
|
|
1729
1729
|
encoding: 'utf-8',
|
|
1730
1730
|
timeout: 120000,
|
|
1731
1731
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1732
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }
|
|
1732
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
1733
|
+
shell: isWindows
|
|
1733
1734
|
});
|
|
1734
1735
|
sendJSON(req, res, 200, { ok: true, repo, path: targetPath, name: repoName });
|
|
1735
1736
|
} catch (err) {
|
|
@@ -1759,7 +1760,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1759
1760
|
|
|
1760
1761
|
if (pathOnly === '/api/git/check-remote-ownership' && req.method === 'GET') {
|
|
1761
1762
|
try {
|
|
1762
|
-
const
|
|
1763
|
+
const isWindows = os.platform() === 'win32';
|
|
1764
|
+
const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
1763
1765
|
const remoteUrl = result.trim();
|
|
1764
1766
|
const statusResult = execSync('git status --porcelain', { encoding: 'utf-8', cwd: STARTUP_CWD });
|
|
1765
1767
|
const hasChanges = statusResult.trim().length > 0;
|
|
@@ -1773,7 +1775,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1773
1775
|
|
|
1774
1776
|
if (pathOnly === '/api/git/push' && req.method === 'POST') {
|
|
1775
1777
|
try {
|
|
1776
|
-
|
|
1778
|
+
const isWindows = os.platform() === 'win32';
|
|
1779
|
+
execSync('git add -A && git commit -m "Auto-commit" && git push', { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
1777
1780
|
sendJSON(req, res, 200, { success: true });
|
|
1778
1781
|
} catch (err) {
|
|
1779
1782
|
sendJSON(req, res, 500, { error: err.message });
|
|
@@ -1787,7 +1790,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
1787
1790
|
const expandedPath = decodedPath.startsWith('~') ?
|
|
1788
1791
|
decodedPath.replace('~', os.homedir()) : decodedPath;
|
|
1789
1792
|
const normalizedPath = path.normalize(expandedPath);
|
|
1790
|
-
|
|
1793
|
+
const isWindows = os.platform() === 'win32';
|
|
1794
|
+
const isAbsolute = isWindows ? /^[A-Za-z]:[\\\/]/.test(normalizedPath) : normalizedPath.startsWith('/');
|
|
1795
|
+
if (!isAbsolute || normalizedPath.includes('..')) {
|
|
1791
1796
|
res.writeHead(403); res.end('Forbidden'); return;
|
|
1792
1797
|
}
|
|
1793
1798
|
try {
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
* Includes folder browser for selecting working directory on new conversation
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
function pathSplit(p) {
|
|
8
|
+
return p.split(/[\/\\]/).filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function pathBasename(p) {
|
|
12
|
+
const parts = pathSplit(p);
|
|
13
|
+
return parts.length ? parts.pop() : '';
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
class ConversationManager {
|
|
8
17
|
constructor() {
|
|
9
18
|
this.conversations = [];
|
|
@@ -176,7 +185,8 @@ class ConversationManager {
|
|
|
176
185
|
li.innerHTML = `<span class="folder-list-item-icon">📁</span><span class="folder-list-item-name">${this.escapeHtml(folder.name)}</span>`;
|
|
177
186
|
li.addEventListener('click', () => {
|
|
178
187
|
const expandedBase = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
179
|
-
const
|
|
188
|
+
const separator = expandedBase.includes('\\') ? '\\' : '/';
|
|
189
|
+
const newPath = expandedBase + separator + folder.name;
|
|
180
190
|
this.loadFolders(newPath);
|
|
181
191
|
});
|
|
182
192
|
this.folderBrowser.listEl.appendChild(li);
|
|
@@ -189,26 +199,31 @@ class ConversationManager {
|
|
|
189
199
|
|
|
190
200
|
getParentPath(dirPath) {
|
|
191
201
|
const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
192
|
-
const parts = expanded
|
|
193
|
-
if (parts.length <= 1)
|
|
202
|
+
const parts = pathSplit(expanded);
|
|
203
|
+
if (parts.length <= 1) {
|
|
204
|
+
const separator = expanded.includes('\\') ? '\\' : '/';
|
|
205
|
+
return separator;
|
|
206
|
+
}
|
|
194
207
|
parts.pop();
|
|
195
|
-
|
|
208
|
+
const separator = expanded.includes('\\') ? '\\' : '/';
|
|
209
|
+
return separator + parts.join(separator);
|
|
196
210
|
}
|
|
197
211
|
|
|
198
212
|
renderBreadcrumb(dirPath) {
|
|
199
213
|
if (!this.folderBrowser.breadcrumbEl) return;
|
|
200
214
|
|
|
201
215
|
const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
|
|
202
|
-
const parts = expanded
|
|
216
|
+
const parts = pathSplit(expanded);
|
|
217
|
+
const separator = expanded.includes('\\') ? '\\' : '/';
|
|
203
218
|
|
|
204
219
|
let html = '';
|
|
205
|
-
html +=
|
|
220
|
+
html += `<span class="folder-breadcrumb-segment" data-path="${separator}">${separator} </span>`;
|
|
206
221
|
|
|
207
222
|
let accumulated = '';
|
|
208
223
|
for (let i = 0; i < parts.length; i++) {
|
|
209
|
-
accumulated +=
|
|
224
|
+
accumulated += separator + parts[i];
|
|
210
225
|
const isLast = i === parts.length - 1;
|
|
211
|
-
html +=
|
|
226
|
+
html += `<span class="folder-breadcrumb-separator">${separator}</span>`;
|
|
212
227
|
html += `<span class="folder-breadcrumb-segment${isLast ? '' : ''}" data-path="${this.escapeHtml(accumulated)}">${this.escapeHtml(parts[i])}</span>`;
|
|
213
228
|
}
|
|
214
229
|
|
|
@@ -227,7 +242,7 @@ class ConversationManager {
|
|
|
227
242
|
const expanded = currentPath === '~' ? this.folderBrowser.homePath : currentPath;
|
|
228
243
|
this.closeFolderBrowser();
|
|
229
244
|
|
|
230
|
-
const dirName = expanded
|
|
245
|
+
const dirName = pathBasename(expanded) || 'root';
|
|
231
246
|
window.dispatchEvent(new CustomEvent('create-new-conversation', {
|
|
232
247
|
detail: { workingDirectory: expanded, title: dirName }
|
|
233
248
|
}));
|
|
@@ -398,7 +413,7 @@ class ConversationManager {
|
|
|
398
413
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
399
414
|
const agent = this.getAgentDisplayName(conv.agentType);
|
|
400
415
|
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
401
|
-
const wd = conv.workingDirectory ? conv.workingDirectory
|
|
416
|
+
const wd = conv.workingDirectory ? pathBasename(conv.workingDirectory) : '';
|
|
402
417
|
const metaParts = [agent + modelLabel, timestamp];
|
|
403
418
|
if (wd) metaParts.push(wd);
|
|
404
419
|
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
* for Claude Code streaming execution display
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
function pathSplit(p) {
|
|
8
|
+
return p.split(/[\/\\]/).filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function pathBasename(p) {
|
|
12
|
+
const parts = pathSplit(p);
|
|
13
|
+
return parts.length ? parts.pop() : '';
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
class StreamingRenderer {
|
|
8
17
|
constructor(config = {}) {
|
|
9
18
|
// Configuration
|
|
@@ -523,7 +532,7 @@ class StreamingRenderer {
|
|
|
523
532
|
*/
|
|
524
533
|
renderFilePath(filePath) {
|
|
525
534
|
if (!filePath) return '';
|
|
526
|
-
const parts = filePath
|
|
535
|
+
const parts = pathSplit(filePath);
|
|
527
536
|
const fileName = parts.pop();
|
|
528
537
|
const dir = parts.join('/');
|
|
529
538
|
return `<div class="tool-param-file"><span class="file-icon">📄</span>${dir ? `<span class="file-dir">${this.escapeHtml(dir)}/</span>` : ''}<span class="file-name">${this.escapeHtml(fileName)}</span></div>`;
|
|
@@ -660,18 +669,16 @@ class StreamingRenderer {
|
|
|
660
669
|
getToolUseTitle(toolName, input) {
|
|
661
670
|
const normalizedName = toolName.replace(/^mcp__[^_]+__/, '');
|
|
662
671
|
if (normalizedName === 'Edit' && input.file_path) {
|
|
663
|
-
const parts = input.file_path
|
|
672
|
+
const parts = pathSplit(input.file_path);
|
|
664
673
|
const fileName = parts.pop();
|
|
665
674
|
const dir = parts.slice(-2).join('/');
|
|
666
675
|
return dir ? `${dir}/${fileName}` : fileName;
|
|
667
676
|
}
|
|
668
677
|
if (normalizedName === 'Read' && input.file_path) {
|
|
669
|
-
|
|
670
|
-
return parts.pop();
|
|
678
|
+
return pathBasename(input.file_path);
|
|
671
679
|
}
|
|
672
680
|
if (normalizedName === 'Write' && input.file_path) {
|
|
673
|
-
|
|
674
|
-
return parts.pop();
|
|
681
|
+
return pathBasename(input.file_path);
|
|
675
682
|
}
|
|
676
683
|
if (normalizedName === 'Bash' || normalizedName === 'bash') {
|
|
677
684
|
const cmd = input.command || input.commands || '';
|
|
@@ -684,7 +691,7 @@ class StreamingRenderer {
|
|
|
684
691
|
try { return new URL(input.url).hostname; } catch (e) { return input.url.substring(0, 40); }
|
|
685
692
|
}
|
|
686
693
|
if (normalizedName === 'WebSearch' && input.query) return input.query.substring(0, 50);
|
|
687
|
-
if (input.file_path) return input.file_path
|
|
694
|
+
if (input.file_path) return pathBasename(input.file_path);
|
|
688
695
|
if (input.command) {
|
|
689
696
|
const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command);
|
|
690
697
|
return c.length > 50 ? c.substring(0, 47) + '...' : c;
|
|
@@ -748,11 +755,14 @@ class StreamingRenderer {
|
|
|
748
755
|
}
|
|
749
756
|
|
|
750
757
|
const lines = trimmed.split('\n');
|
|
751
|
-
const allFilePaths = lines.length > 1 && lines.every(l =>
|
|
758
|
+
const allFilePaths = lines.length > 1 && lines.every(l => {
|
|
759
|
+
const t = l.trim();
|
|
760
|
+
return t === '' || t.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(t);
|
|
761
|
+
});
|
|
752
762
|
if (allFilePaths && lines.filter(l => l.trim()).length > 0) {
|
|
753
763
|
const fileHtml = lines.filter(l => l.trim()).map(l => {
|
|
754
764
|
const p = l.trim();
|
|
755
|
-
const parts = p
|
|
765
|
+
const parts = pathSplit(p);
|
|
756
766
|
const name = parts.pop();
|
|
757
767
|
const dir = parts.join('/');
|
|
758
768
|
return `<div style="display:flex;align-items:center;gap:0.375rem;padding:0.1875rem 0;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${this.escapeHtml(dir)}/</span><span style="font-weight:600">${this.escapeHtml(name)}</span></div>`;
|
|
@@ -785,7 +795,8 @@ class StreamingRenderer {
|
|
|
785
795
|
const lines = data.split('\n').length;
|
|
786
796
|
return `<div style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;background:var(--color-bg-code);color:#d1d5db;padding:0.5rem;border-radius:0.375rem;line-height:1.5">${this.escapeHtml(data.substring(0, 1000))}${data.length > 1000 ? '\n... (' + (data.length - 1000) + ' more chars, ' + lines + ' lines)' : ''}</div>`;
|
|
787
797
|
}
|
|
788
|
-
|
|
798
|
+
const looksLikePath = data.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(data);
|
|
799
|
+
if (looksLikePath && !data.includes(' ') && data.includes('.')) return this.renderFilePath(data);
|
|
789
800
|
return `<span style="color:var(--color-text-primary)">${this.escapeHtml(data)}</span>`;
|
|
790
801
|
}
|
|
791
802
|
|
|
@@ -986,11 +997,14 @@ class StreamingRenderer {
|
|
|
986
997
|
return html;
|
|
987
998
|
}
|
|
988
999
|
|
|
989
|
-
const allFilePaths = lines.length > 1 && lines.every(l =>
|
|
1000
|
+
const allFilePaths = lines.length > 1 && lines.every(l => {
|
|
1001
|
+
const t = l.trim();
|
|
1002
|
+
return t === '' || t.startsWith('/') || /^[A-Za-z]:[\\\/]/.test(t);
|
|
1003
|
+
});
|
|
990
1004
|
if (allFilePaths && lines.filter(l => l.trim()).length > 0) {
|
|
991
1005
|
const fileHtml = lines.filter(l => l.trim()).map(l => {
|
|
992
1006
|
const p = l.trim();
|
|
993
|
-
const parts = p
|
|
1007
|
+
const parts = pathSplit(p);
|
|
994
1008
|
const name = parts.pop();
|
|
995
1009
|
const dir = parts.join('/');
|
|
996
1010
|
return `<div style="display:flex;align-items:center;gap:0.375rem;padding:0.1875rem 0;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
|
|
@@ -1103,15 +1117,15 @@ class StreamingRenderer {
|
|
|
1103
1117
|
|
|
1104
1118
|
static getToolTitle(toolName, input) {
|
|
1105
1119
|
const n = toolName.replace(/^mcp__[^_]+__/, '');
|
|
1106
|
-
if (n === 'Edit' && input.file_path) { const p = input.file_path
|
|
1107
|
-
if (n === 'Read' && input.file_path) return input.file_path
|
|
1108
|
-
if (n === 'Write' && input.file_path) return input.file_path
|
|
1120
|
+
if (n === 'Edit' && input.file_path) { const p = pathSplit(input.file_path); const f = p.pop(); const d = p.slice(-2).join('/'); return d ? d+'/'+f : f; }
|
|
1121
|
+
if (n === 'Read' && input.file_path) return pathBasename(input.file_path);
|
|
1122
|
+
if (n === 'Write' && input.file_path) return pathBasename(input.file_path);
|
|
1109
1123
|
if ((n === 'Bash' || n === 'bash') && (input.command || input.commands)) { const c = typeof (input.command||input.commands) === 'string' ? (input.command||input.commands) : JSON.stringify(input.command||input.commands); return c.length > 60 ? c.substring(0,57)+'...' : c; }
|
|
1110
1124
|
if (n === 'Glob' && input.pattern) return input.pattern;
|
|
1111
1125
|
if (n === 'Grep' && input.pattern) return input.pattern;
|
|
1112
1126
|
if (n === 'WebFetch' && input.url) { try { return new URL(input.url).hostname; } catch(e) { return input.url.substring(0,40); } }
|
|
1113
1127
|
if (n === 'WebSearch' && input.query) return input.query.substring(0,50);
|
|
1114
|
-
if (input.file_path) return input.file_path
|
|
1128
|
+
if (input.file_path) return pathBasename(input.file_path);
|
|
1115
1129
|
if (input.command) { const c = typeof input.command === 'string' ? input.command : JSON.stringify(input.command); return c.length > 50 ? c.substring(0,47)+'...' : c; }
|
|
1116
1130
|
if (input.query) return input.query.substring(0,50);
|
|
1117
1131
|
return '';
|
|
@@ -1134,8 +1148,9 @@ class StreamingRenderer {
|
|
|
1134
1148
|
if (data.length > 500) {
|
|
1135
1149
|
return `<div style="font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;background:var(--color-bg-code);color:#d1d5db;padding:0.5rem;border-radius:0.375rem;line-height:1.5">${esc(data.substring(0, 1000))}${data.length > 1000 ? '\n... (' + (data.length - 1000) + ' more chars)' : ''}</div>`;
|
|
1136
1150
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1151
|
+
const looksLikePath = /^[A-Za-z]:[\\\/]/.test(data) || data.startsWith('/');
|
|
1152
|
+
if (looksLikePath && !data.includes(' ') && data.includes('.')) {
|
|
1153
|
+
const parts = pathSplit(data);
|
|
1139
1154
|
const name = parts.pop();
|
|
1140
1155
|
const dir = parts.join('/');
|
|
1141
1156
|
return `<div style="display:flex;align-items:center;gap:0.375rem;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.8rem"><span style="opacity:0.5">📄</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
|
package/static/js/voice.js
CHANGED
|
@@ -317,9 +317,23 @@
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
function splitSentences(text) {
|
|
320
|
+
if (!text) return [text];
|
|
320
321
|
var raw = text.match(/[^.!?]+[.!?]+[\s]?|[^.!?]+$/g);
|
|
321
322
|
if (!raw) return [text];
|
|
322
|
-
|
|
323
|
+
var sentences = raw.map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
|
324
|
+
var result = [];
|
|
325
|
+
for (var i = 0; i < sentences.length; i++) {
|
|
326
|
+
var s = sentences[i];
|
|
327
|
+
if (result.length > 0) {
|
|
328
|
+
var prev = result[result.length - 1];
|
|
329
|
+
if (s.match(/^(\d+[\.\)]|\d+\s)/) || prev.match(/\d+[\.\)]$/)) {
|
|
330
|
+
result[result.length - 1] = prev + ' ' + s;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
result.push(s);
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
var audioChunkQueue = [];
|
|
@@ -382,17 +396,9 @@
|
|
|
382
396
|
return;
|
|
383
397
|
}
|
|
384
398
|
|
|
385
|
-
var sentences =
|
|
399
|
+
var sentences = [text];
|
|
386
400
|
var cachedSentences = [];
|
|
387
|
-
var uncachedText = [];
|
|
388
|
-
for (var i = 0; i < sentences.length; i++) {
|
|
389
|
-
var blob = getCachedTTSBlob(sentences[i]);
|
|
390
|
-
if (blob) {
|
|
391
|
-
cachedSentences.push({ idx: i, blob: blob });
|
|
392
|
-
} else {
|
|
393
|
-
uncachedText.push(sentences[i]);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
401
|
+
var uncachedText = [text];
|
|
396
402
|
|
|
397
403
|
if (cachedSentences.length === sentences.length) {
|
|
398
404
|
ttsConsecutiveFailures = 0;
|
|
@@ -530,16 +536,32 @@
|
|
|
530
536
|
if (!container) return;
|
|
531
537
|
var emptyMsg = container.querySelector('.voice-empty');
|
|
532
538
|
if (emptyMsg) emptyMsg.remove();
|
|
539
|
+
var lastChild = container.lastElementChild;
|
|
540
|
+
if (!isUser && lastChild && lastChild.classList.contains('voice-block') && !lastChild.classList.contains('voice-block-user')) {
|
|
541
|
+
var contentSpan = lastChild.querySelector('.voice-block-content');
|
|
542
|
+
if (contentSpan) {
|
|
543
|
+
contentSpan.textContent += '\n' + stripHtml(text);
|
|
544
|
+
lastChild._fullText = (lastChild._fullText || contentSpan.textContent) + '\n' + text;
|
|
545
|
+
scrollVoiceToBottom();
|
|
546
|
+
return lastChild;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
533
549
|
var div = document.createElement('div');
|
|
534
550
|
div.className = 'voice-block' + (isUser ? ' voice-block-user' : '');
|
|
535
|
-
|
|
536
|
-
|
|
551
|
+
if (isUser) {
|
|
552
|
+
div.textContent = text;
|
|
553
|
+
} else {
|
|
554
|
+
var contentSpan = document.createElement('span');
|
|
555
|
+
contentSpan.className = 'voice-block-content';
|
|
556
|
+
contentSpan.textContent = stripHtml(text);
|
|
557
|
+
div.appendChild(contentSpan);
|
|
558
|
+
div._fullText = text;
|
|
537
559
|
var rereadBtn = document.createElement('button');
|
|
538
560
|
rereadBtn.className = 'voice-reread-btn';
|
|
539
561
|
rereadBtn.title = 'Re-read aloud';
|
|
540
562
|
rereadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>';
|
|
541
563
|
rereadBtn.addEventListener('click', function() {
|
|
542
|
-
speak(
|
|
564
|
+
speak(div._fullText || contentSpan.textContent);
|
|
543
565
|
});
|
|
544
566
|
div.appendChild(rereadBtn);
|
|
545
567
|
}
|
package/telemetry-id
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
814cd4b3-ffd4-44c7-abbd-b2d9e757f0fc
|