agentgui 1.0.236 → 1.0.238

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 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 manages dependencies independently
19
+ // Skip this for bunx/npx which manage dependencies independently
20
20
  const nodeModulesPath = path.join(projectRoot, 'node_modules');
21
- const isBunx = process.env.npm_execpath && process.env.npm_execpath.includes('bunx');
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;
@@ -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, { cwd });
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, { cwd });
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 which = spawnSync('which', [agent.command], { encoding: 'utf-8', timeout: 3000 });
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;
package/lib/speech.js CHANGED
@@ -29,7 +29,14 @@ const POCKET_TTS_VOICES = [
29
29
  const PREDEFINED_IDS = new Set(POCKET_TTS_VOICES.filter(v => v.id !== 'default').map(v => v.id));
30
30
  const POCKET_PORT = 8787;
31
31
 
32
- const needsPatch = !serverTTS.getVoices(EXTRA_VOICE_DIRS).some(v => v.id === 'alba' && !v.isCustom);
32
+ function safeGetVoices(extraDirs) {
33
+ if (typeof serverTTS.getVoices === 'function') {
34
+ return serverTTS.getVoices(extraDirs || []);
35
+ }
36
+ return [];
37
+ }
38
+
39
+ const needsPatch = !safeGetVoices(EXTRA_VOICE_DIRS).some(v => v.id === 'alba' && !v.isCustom);
33
40
 
34
41
  function synthesizeDirect(text, voiceId) {
35
42
  const voicePath = serverTTS.findVoiceFile(voiceId, EXTRA_VOICE_DIRS);
@@ -96,7 +103,7 @@ function synthesizeStream(text, voiceId) {
96
103
  }
97
104
 
98
105
  function getVoices() {
99
- const upstream = serverTTS.getVoices(EXTRA_VOICE_DIRS);
106
+ const upstream = safeGetVoices(EXTRA_VOICE_DIRS);
100
107
  const custom = upstream.filter(v => v.isCustom);
101
108
  return [...POCKET_TTS_VOICES, ...custom];
102
109
  }
@@ -116,6 +123,10 @@ function getStatus() {
116
123
  }
117
124
 
118
125
  function preloadTTS() {
126
+ if (typeof serverTTS.findVoiceFile !== 'function' || typeof serverTTS.start !== 'function') {
127
+ console.log('[TTS] pocket-tts functions not available');
128
+ return;
129
+ }
119
130
  const defaultVoice = serverTTS.findVoiceFile('custom_cleetus', EXTRA_VOICE_DIRS) || '/config/voices/cleetus.wav';
120
131
  const voicePath = fs.existsSync(defaultVoice) ? defaultVoice : null;
121
132
  serverTTS.start(voicePath, {}).then(ok => {
@@ -127,15 +138,15 @@ function preloadTTS() {
127
138
  }
128
139
 
129
140
  function ttsCacheKey(text, voiceId) {
130
- return serverTTS.ttsCacheKey(text, voiceId);
141
+ return typeof serverTTS.ttsCacheKey === 'function' ? serverTTS.ttsCacheKey(text, voiceId) : null;
131
142
  }
132
143
 
133
144
  function ttsCacheGet(key) {
134
- return serverTTS.ttsCacheGet(key);
145
+ return typeof serverTTS.ttsCacheGet === 'function' ? serverTTS.ttsCacheGet(key) : null;
135
146
  }
136
147
 
137
148
  function splitSentences(text) {
138
- return serverTTS.splitSentences(text);
149
+ return typeof serverTTS.splitSentences === 'function' ? serverTTS.splitSentences(text) : [text];
139
150
  }
140
151
 
141
152
  export { transcribe, synthesize, synthesizeStream, getSTT, getStatus, getVoices, preloadTTS, ttsCacheKey, ttsCacheGet, splitSentences };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.236",
3
+ "version": "1.0.238",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -38,9 +38,17 @@ const modelDownloadState = {
38
38
  async function ensureModelsDownloaded() {
39
39
  const { createRequire: cr } = await import('module');
40
40
  const r = cr(import.meta.url);
41
- const { checkAllFilesExist, downloadModels } = r('sttttsmodels');
41
+ const models = r('sttttsmodels');
42
+ const checkAllFilesExist = models.checkAllFilesExist;
43
+ const downloadModels = models.downloadModels;
42
44
 
43
- if (checkAllFilesExist()) {
45
+ if (checkAllFilesExist && checkAllFilesExist()) {
46
+ modelDownloadState.complete = true;
47
+ return true;
48
+ }
49
+
50
+ if (!downloadModels) {
51
+ console.log('[MODELS] Download function not available, skipping');
44
52
  modelDownloadState.complete = true;
45
53
  return true;
46
54
  }
@@ -1260,7 +1268,8 @@ const server = http.createServer(async (req, res) => {
1260
1268
  delete childEnv.PORT;
1261
1269
  delete childEnv.BASE_URL;
1262
1270
  delete childEnv.HOT_RELOAD;
1263
- const child = spawn('npm', ['run', script], { cwd: wd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: childEnv });
1271
+ const isWindows = os.platform() === 'win32';
1272
+ const child = spawn('npm', ['run', script], { cwd: wd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: childEnv, shell: isWindows });
1264
1273
  activeScripts.set(conversationId, { process: child, script, startTime: Date.now() });
1265
1274
  broadcastSync({ type: 'script_started', conversationId, script, timestamp: Date.now() });
1266
1275
 
@@ -1508,7 +1517,8 @@ const server = http.createServer(async (req, res) => {
1508
1517
 
1509
1518
  const child = spawn(authCmd.cmd, authCmd.args, {
1510
1519
  stdio: ['pipe', 'pipe', 'pipe'],
1511
- env: { ...process.env, FORCE_COLOR: '1' }
1520
+ env: { ...process.env, FORCE_COLOR: '1' },
1521
+ shell: os.platform() === 'win32'
1512
1522
  });
1513
1523
  activeScripts.set(conversationId, { process: child, script: 'auth-' + agentId, startTime: Date.now() });
1514
1524
  broadcastSync({ type: 'script_started', conversationId, script: 'auth-' + agentId, agentId, timestamp: Date.now() });
@@ -1721,12 +1731,14 @@ const server = http.createServer(async (req, res) => {
1721
1731
  return;
1722
1732
  }
1723
1733
  try {
1724
- execSync(`git clone https://github.com/${repo}.git`, {
1734
+ const isWindows = os.platform() === 'win32';
1735
+ execSync('git clone https://github.com/' + repo + '.git', {
1725
1736
  cwd: cloneDir,
1726
1737
  encoding: 'utf-8',
1727
1738
  timeout: 120000,
1728
1739
  stdio: ['pipe', 'pipe', 'pipe'],
1729
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }
1740
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
1741
+ shell: isWindows
1730
1742
  });
1731
1743
  sendJSON(req, res, 200, { ok: true, repo, path: targetPath, name: repoName });
1732
1744
  } catch (err) {
@@ -1756,7 +1768,8 @@ const server = http.createServer(async (req, res) => {
1756
1768
 
1757
1769
  if (pathOnly === '/api/git/check-remote-ownership' && req.method === 'GET') {
1758
1770
  try {
1759
- const result = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf-8', cwd: STARTUP_CWD });
1771
+ const isWindows = os.platform() === 'win32';
1772
+ const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
1760
1773
  const remoteUrl = result.trim();
1761
1774
  const statusResult = execSync('git status --porcelain', { encoding: 'utf-8', cwd: STARTUP_CWD });
1762
1775
  const hasChanges = statusResult.trim().length > 0;
@@ -1770,7 +1783,8 @@ const server = http.createServer(async (req, res) => {
1770
1783
 
1771
1784
  if (pathOnly === '/api/git/push' && req.method === 'POST') {
1772
1785
  try {
1773
- execSync('git add -A && git commit -m "Auto-commit" && git push', { encoding: 'utf-8', cwd: STARTUP_CWD });
1786
+ const isWindows = os.platform() === 'win32';
1787
+ execSync('git add -A && git commit -m "Auto-commit" && git push', { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
1774
1788
  sendJSON(req, res, 200, { success: true });
1775
1789
  } catch (err) {
1776
1790
  sendJSON(req, res, 500, { error: err.message });
@@ -1784,7 +1798,9 @@ const server = http.createServer(async (req, res) => {
1784
1798
  const expandedPath = decodedPath.startsWith('~') ?
1785
1799
  decodedPath.replace('~', os.homedir()) : decodedPath;
1786
1800
  const normalizedPath = path.normalize(expandedPath);
1787
- if (!normalizedPath.startsWith('/') || normalizedPath.includes('..')) {
1801
+ const isWindows = os.platform() === 'win32';
1802
+ const isAbsolute = isWindows ? /^[A-Za-z]:[\\\/]/.test(normalizedPath) : normalizedPath.startsWith('/');
1803
+ if (!isAbsolute || normalizedPath.includes('..')) {
1788
1804
  res.writeHead(403); res.end('Forbidden'); return;
1789
1805
  }
1790
1806
  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">&#128193;</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 newPath = expandedBase + '/' + folder.name;
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.split('/').filter(Boolean);
193
- if (parts.length <= 1) return '/';
202
+ const parts = pathSplit(expanded);
203
+ if (parts.length <= 1) {
204
+ const separator = expanded.includes('\\') ? '\\' : '/';
205
+ return separator;
206
+ }
194
207
  parts.pop();
195
- return '/' + parts.join('/');
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.split('/').filter(Boolean);
216
+ const parts = pathSplit(expanded);
217
+ const separator = expanded.includes('\\') ? '\\' : '/';
203
218
 
204
219
  let html = '';
205
- html += '<span class="folder-breadcrumb-segment" data-path="/">/ </span>';
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 += '/' + parts[i];
224
+ accumulated += separator + parts[i];
210
225
  const isLast = i === parts.length - 1;
211
- html += '<span class="folder-breadcrumb-separator">/</span>';
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.split('/').filter(Boolean).pop() || 'root';
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.split('/').pop() : '';
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.split('/');
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">&#128196;</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.split('/');
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
- const parts = input.file_path.split('/');
670
- return parts.pop();
678
+ return pathBasename(input.file_path);
671
679
  }
672
680
  if (normalizedName === 'Write' && input.file_path) {
673
- const parts = input.file_path.split('/');
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.split('/').pop();
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 => l.trim() === '' || l.trim().startsWith('/'));
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.split('/');
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">&#128196;</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
- if (data.startsWith('/') && !data.includes(' ') && data.includes('.')) return this.renderFilePath(data);
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 => l.trim() === '' || l.trim().startsWith('/'));
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.split('/');
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">&#128196;</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.split('/'); const f = p.pop(); const d = p.slice(-2).join('/'); return d ? d+'/'+f : f; }
1107
- if (n === 'Read' && input.file_path) return input.file_path.split('/').pop();
1108
- if (n === 'Write' && input.file_path) return input.file_path.split('/').pop();
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.split('/').pop();
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
- if (data.startsWith('/') && !data.includes(' ') && data.includes('.')) {
1138
- const parts = data.split('/');
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">&#128196;</span><span style="color:var(--color-text-secondary)">${esc(dir)}/</span><span style="font-weight:600">${esc(name)}</span></div>`;
package/telemetry-id ADDED
@@ -0,0 +1 @@
1
+ 814cd4b3-ffd4-44c7-abbd-b2d9e757f0fc