let-them-talk 3.4.2 → 3.4.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.4.3] - 2026-03-15
4
+
5
+ ### Removed — Plugin System
6
+ - Removed the entire plugin system (`vm.runInNewContext` sandbox, plugin CLI commands, dashboard plugin UI)
7
+ - **Why:** Plugins were an unnecessary attack surface. Node.js `vm` is not a security sandbox — plugins could escape and execute arbitrary OS commands. CLI terminals (Claude Code, Gemini, Codex) have their own extension systems, making our plugins redundant.
8
+ - `npx let-them-talk plugin` now shows a deprecation notice
9
+ - MCP tools reduced from 27 + plugins to 27 (all core tools remain)
10
+ - ~200 lines of code removed from server.js, cli.js, dashboard.js, dashboard.html
11
+
3
12
  ## [3.4.2] - 2026-03-15
4
13
 
5
14
  ### Security — CSRF Protection
package/LICENSE CHANGED
@@ -6,7 +6,7 @@ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
6
6
  Parameters
7
7
 
8
8
  Licensor: Dekelelz
9
- Licensed Work: Let Them Talk v3.4.2
9
+ Licensed Work: Let Them Talk v3.4.3
10
10
  The Licensed Work is (c) 2024-2026 Dekelelz.
11
11
  Additional Use Grant: You may make use of the Licensed Work, provided that
12
12
  you may not use the Licensed Work for a Commercial
package/cli.js CHANGED
@@ -8,7 +8,7 @@ const command = process.argv[2];
8
8
 
9
9
  function printUsage() {
10
10
  console.log(`
11
- Let Them Talk — Agent Bridge v3.4.2
11
+ Let Them Talk — Agent Bridge v3.4.3
12
12
  MCP message broker for inter-agent communication
13
13
  Supports: Claude Code, Gemini CLI, Codex CLI
14
14
 
@@ -23,11 +23,6 @@ function printUsage() {
23
23
  npx let-them-talk dashboard Launch the web dashboard (http://localhost:3000)
24
24
  npx let-them-talk dashboard --lan Launch dashboard accessible on LAN (phone/tablet)
25
25
  npx let-them-talk reset Clear all conversation data
26
- npx let-them-talk plugin list List installed plugins
27
- npx let-them-talk plugin add <file> Install a plugin from a .js file
28
- npx let-them-talk plugin remove <n> Remove a plugin by name
29
- npx let-them-talk plugin enable <n> Enable a plugin
30
- npx let-them-talk plugin disable <n> Disable a plugin
31
26
  npx let-them-talk msg <agent> <text> Send a message to an agent
32
27
  npx let-them-talk status Show active agents and message count
33
28
  npx let-them-talk help Show this help message
@@ -293,124 +288,6 @@ function showTemplate(templateName) {
293
288
  }
294
289
  }
295
290
 
296
- function pluginCmd() {
297
- const subCmd = process.argv[3];
298
- const dataDir = process.env.AGENT_BRIDGE_DATA_DIR || path.join(process.cwd(), '.agent-bridge');
299
- const pluginsDir = path.join(dataDir, 'plugins');
300
- const pluginsFile = path.join(dataDir, 'plugins.json');
301
-
302
- function getRegistry() {
303
- if (!fs.existsSync(pluginsFile)) return [];
304
- try { return JSON.parse(fs.readFileSync(pluginsFile, 'utf8')); } catch { return []; }
305
- }
306
-
307
- function saveRegistry(reg) {
308
- if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
309
- fs.writeFileSync(pluginsFile, JSON.stringify(reg, null, 2));
310
- }
311
-
312
- switch (subCmd) {
313
- case 'list': {
314
- const plugins = getRegistry();
315
- if (!plugins.length) {
316
- console.log(' No plugins installed.');
317
- console.log(' Install with: npx let-them-talk plugin add <file.js>');
318
- return;
319
- }
320
- console.log('');
321
- console.log(' Installed Plugins');
322
- console.log(' =================');
323
- for (const p of plugins) {
324
- const status = p.enabled !== false ? 'enabled' : 'disabled';
325
- console.log(' ' + p.name.padEnd(20) + ' ' + status.padEnd(10) + ' ' + (p.description || ''));
326
- }
327
- console.log('');
328
- break;
329
- }
330
- case 'add': {
331
- const filePath = process.argv[4];
332
- if (!filePath) { console.error(' Usage: npx let-them-talk plugin add <file.js>'); process.exit(1); }
333
- const absPath = path.resolve(filePath);
334
- if (!fs.existsSync(absPath)) { console.error(' File not found: ' + absPath); process.exit(1); }
335
-
336
- // Validate plugin structure without executing it (no require — prevents RCE on install)
337
- try {
338
- const src = fs.readFileSync(absPath, 'utf8');
339
- if (!src.includes('module.exports') || !src.includes('name') || !src.includes('handler')) {
340
- console.error(' Plugin must export name, description, and handler (module.exports = { name, handler })');
341
- process.exit(1);
342
- }
343
-
344
- // Extract plugin name from source using regex (no eval)
345
- const nameMatch = src.match(/name\s*:\s*['"]([^'"]+)['"]/);
346
- const descMatch = src.match(/description\s*:\s*['"]([^'"]+)['"]/);
347
- const pluginName = nameMatch ? nameMatch[1] : path.basename(absPath, '.js');
348
- const pluginDesc = descMatch ? descMatch[1] : '';
349
-
350
- if (!fs.existsSync(pluginsDir)) fs.mkdirSync(pluginsDir, { recursive: true });
351
- const destFile = path.join(pluginsDir, path.basename(absPath));
352
- fs.copyFileSync(absPath, destFile);
353
-
354
- const reg = getRegistry();
355
- if (!reg.find(p => p.name === pluginName)) {
356
- reg.push({ name: pluginName, description: pluginDesc, file: path.basename(absPath), enabled: true, added_at: new Date().toISOString() });
357
- saveRegistry(reg);
358
- }
359
- console.log(' Plugin "' + pluginName + '" installed successfully.');
360
- console.log(' Restart CLI to load the new tool (runs sandboxed).');
361
- } catch (e) {
362
- console.error(' Failed to install plugin: ' + e.message);
363
- process.exit(1);
364
- }
365
- break;
366
- }
367
- case 'remove': {
368
- const name = process.argv[4];
369
- if (!name) { console.error(' Usage: npx let-them-talk plugin remove <name>'); process.exit(1); }
370
- const reg = getRegistry();
371
- const plugin = reg.find(p => p.name === name);
372
- if (!plugin) { console.error(' Plugin not found: ' + name); process.exit(1); }
373
- const newReg = reg.filter(p => p.name !== name);
374
- saveRegistry(newReg);
375
- if (plugin.file) {
376
- const pluginFile = path.resolve(pluginsDir, plugin.file);
377
- // Prevent path traversal — only delete files inside pluginsDir
378
- if (pluginFile.startsWith(path.resolve(pluginsDir) + path.sep) && fs.existsSync(pluginFile)) {
379
- fs.unlinkSync(pluginFile);
380
- }
381
- }
382
- console.log(' Plugin "' + name + '" removed.');
383
- break;
384
- }
385
- case 'enable': {
386
- const name = process.argv[4];
387
- if (!name) { console.error(' Usage: npx let-them-talk plugin enable <name>'); process.exit(1); }
388
- const reg = getRegistry();
389
- const plugin = reg.find(p => p.name === name);
390
- if (!plugin) { console.error(' Plugin not found: ' + name); process.exit(1); }
391
- plugin.enabled = true;
392
- saveRegistry(reg);
393
- console.log(' Plugin "' + name + '" enabled.');
394
- break;
395
- }
396
- case 'disable': {
397
- const name = process.argv[4];
398
- if (!name) { console.error(' Usage: npx let-them-talk plugin disable <name>'); process.exit(1); }
399
- const reg = getRegistry();
400
- const plugin = reg.find(p => p.name === name);
401
- if (!plugin) { console.error(' Plugin not found: ' + name); process.exit(1); }
402
- plugin.enabled = false;
403
- saveRegistry(reg);
404
- console.log(' Plugin "' + name + '" disabled.');
405
- break;
406
- }
407
- default:
408
- console.error(' Unknown plugin command: ' + (subCmd || ''));
409
- console.error(' Available: list, add, remove, enable, disable');
410
- process.exit(1);
411
- }
412
- }
413
-
414
291
  function dashboard() {
415
292
  if (process.argv.includes('--lan')) {
416
293
  process.env.AGENT_BRIDGE_LAN = 'true';
@@ -532,7 +409,7 @@ switch (command) {
532
409
  break;
533
410
  case 'plugin':
534
411
  case 'plugins':
535
- pluginCmd();
412
+ console.log(' Plugins have been removed in v3.4.3. CLI terminals have their own extension systems.');
536
413
  break;
537
414
  case 'help':
538
415
  case '--help':
package/dashboard.html CHANGED
@@ -2572,65 +2572,7 @@
2572
2572
  }
2573
2573
 
2574
2574
  /* ===== v3.0: PLUGINS SECTION ===== */
2575
- .plugin-card {
2576
- background: var(--surface-2);
2577
- border: 1px solid var(--border);
2578
- border-radius: 6px;
2579
- padding: 8px 10px;
2580
- margin-bottom: 4px;
2581
- display: flex;
2582
- align-items: center;
2583
- justify-content: space-between;
2584
- }
2585
-
2586
- .plugin-info {
2587
- flex: 1;
2588
- min-width: 0;
2589
- }
2590
-
2591
- .plugin-name {
2592
- font-size: 12px;
2593
- font-weight: 600;
2594
- }
2595
-
2596
- .plugin-desc {
2597
- font-size: 10px;
2598
- color: var(--text-muted);
2599
- }
2600
-
2601
- .plugin-toggle {
2602
- background: var(--surface-3);
2603
- border: 1px solid var(--border);
2604
- border-radius: 10px;
2605
- width: 36px;
2606
- height: 20px;
2607
- cursor: pointer;
2608
- position: relative;
2609
- transition: all 0.2s;
2610
- flex-shrink: 0;
2611
- }
2612
-
2613
- .plugin-toggle.on {
2614
- background: var(--green-dim);
2615
- border-color: var(--green);
2616
- }
2617
-
2618
- .plugin-toggle::after {
2619
- content: '';
2620
- position: absolute;
2621
- width: 14px;
2622
- height: 14px;
2623
- border-radius: 50%;
2624
- background: var(--text-dim);
2625
- top: 2px;
2626
- left: 2px;
2627
- transition: all 0.2s;
2628
- }
2629
-
2630
- .plugin-toggle.on::after {
2631
- left: 18px;
2632
- background: var(--green);
2633
- }
2575
+ /* Plugins removed in v3.4.3 */
2634
2576
 
2635
2577
  /* ===== v3.0: AVATAR PICKER ===== */
2636
2578
  .avatar-option {
@@ -2758,12 +2700,6 @@
2758
2700
  <div id="activity-heatmap"></div>
2759
2701
  </div>
2760
2702
 
2761
- <!-- Plugins Section -->
2762
- <div class="sidebar-section">
2763
- <div class="sidebar-title"><span>Plugins</span></div>
2764
- <div id="plugins-list"></div>
2765
- </div>
2766
-
2767
2703
  <!-- Bookmarks Section -->
2768
2704
  <div class="sidebar-section">
2769
2705
  <div class="sidebar-title">
@@ -2842,7 +2778,7 @@
2842
2778
  </div>
2843
2779
  </div>
2844
2780
  <div class="app-footer">
2845
- <span>Let Them Talk v3.4.2</span>
2781
+ <span>Let Them Talk v3.4.3</span>
2846
2782
  </div>
2847
2783
  <div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
2848
2784
  <div class="profile-popup-header">
@@ -4750,44 +4686,6 @@ function switchBranch(name) {
4750
4686
  poll();
4751
4687
  }
4752
4688
 
4753
- // ==================== v3.0: PLUGINS ====================
4754
-
4755
- function fetchPlugins() {
4756
- var pq = projectParam();
4757
- lttFetch('/api/plugins' + pq).then(function(r) { return r.json(); }).then(function(data) {
4758
- renderPlugins(Array.isArray(data) ? data : []);
4759
- }).catch(function() {});
4760
- }
4761
-
4762
- function renderPlugins(plugins) {
4763
- var el = document.getElementById('plugins-list');
4764
- if (!plugins.length) {
4765
- el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:4px;">No plugins installed</div>';
4766
- return;
4767
- }
4768
- var html = '';
4769
- for (var i = 0; i < plugins.length; i++) {
4770
- var p = plugins[i];
4771
- var onClass = p.enabled !== false ? ' on' : '';
4772
- html += '<div class="plugin-card">' +
4773
- '<div class="plugin-info">' +
4774
- '<div class="plugin-name">' + escapeHtml(p.name) + '</div>' +
4775
- '<div class="plugin-desc">' + escapeHtml(p.description || '') + '</div>' +
4776
- '</div>' +
4777
- '<div class="plugin-toggle' + onClass + '" onclick="togglePlugin(\'' + escapeHtml(p.name) + '\')"></div>' +
4778
- '</div>';
4779
- }
4780
- el.innerHTML = html;
4781
- }
4782
-
4783
- function togglePlugin(name) {
4784
- lttFetch('/api/plugins' + projectParam(), {
4785
- method: 'POST',
4786
- headers: { 'Content-Type': 'application/json' },
4787
- body: JSON.stringify({ action: 'toggle', name: name })
4788
- }).then(function() { fetchPlugins(); }).catch(function() {});
4789
- }
4790
-
4791
4689
  // ==================== POLLING ====================
4792
4690
 
4793
4691
  function poll() {
@@ -4839,7 +4737,6 @@ function poll() {
4839
4737
  renderBookmarksSidebar();
4840
4738
  fetchActivity();
4841
4739
  fetchBranches();
4842
- fetchPlugins();
4843
4740
  updateTypingIndicator(cachedAgents);
4844
4741
  if (activeView === 'tasks') fetchTasks();
4845
4742
  if (activeView === 'workspaces') fetchWorkspaces();
package/dashboard.js CHANGED
@@ -328,7 +328,7 @@ function apiStats(query) {
328
328
  function apiReset(query) {
329
329
  const projectPath = query.get('project') || null;
330
330
  const dataDir = resolveDataDir(projectPath);
331
- const fixedFiles = ['messages.jsonl', 'history.jsonl', 'agents.json', 'acks.json', 'tasks.json', 'profiles.json', 'workflows.json', 'branches.json', 'plugins.json', 'read_receipts.json', 'permissions.json'];
331
+ const fixedFiles = ['messages.jsonl', 'history.jsonl', 'agents.json', 'acks.json', 'tasks.json', 'profiles.json', 'workflows.json', 'branches.json', 'read_receipts.json', 'permissions.json'];
332
332
  for (const f of fixedFiles) {
333
333
  const p = path.join(dataDir, f);
334
334
  if (fs.existsSync(p)) fs.unlinkSync(p);
@@ -1300,27 +1300,6 @@ const server = http.createServer(async (req, res) => {
1300
1300
  res.writeHead(200, { 'Content-Type': 'application/json' });
1301
1301
  res.end(JSON.stringify(branches));
1302
1302
  }
1303
- else if (url.pathname === '/api/plugins' && req.method === 'GET') {
1304
- const projectPath = url.searchParams.get('project') || null;
1305
- const pluginsFile = filePath('plugins.json', projectPath);
1306
- res.writeHead(200, { 'Content-Type': 'application/json' });
1307
- res.end(JSON.stringify(fs.existsSync(pluginsFile) ? JSON.parse(fs.readFileSync(pluginsFile, 'utf8')) : []));
1308
- }
1309
- else if (url.pathname === '/api/plugins' && req.method === 'POST') {
1310
- const body = await parseBody(req);
1311
- const projectPath = url.searchParams.get('project') || null;
1312
- const pluginsFile = filePath('plugins.json', projectPath);
1313
- let plugins = [];
1314
- if (fs.existsSync(pluginsFile)) try { plugins = JSON.parse(fs.readFileSync(pluginsFile, 'utf8')); } catch {}
1315
- if (body.action === 'toggle' && body.name) {
1316
- const p = plugins.find(x => x.name === body.name);
1317
- if (!p) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Plugin not found' })); return; }
1318
- p.enabled = !p.enabled;
1319
- fs.writeFileSync(pluginsFile, JSON.stringify(plugins, null, 2));
1320
- }
1321
- res.writeHead(200, { 'Content-Type': 'application/json' });
1322
- res.end(JSON.stringify({ success: true }));
1323
- }
1324
1303
  else if (url.pathname === '/api/projects' && req.method === 'DELETE') {
1325
1304
  const body = await parseBody(req);
1326
1305
  const result = apiRemoveProject(body);
@@ -1501,7 +1480,7 @@ server.listen(PORT, LAN_MODE ? '0.0.0.0' : '127.0.0.1', () => {
1501
1480
  const dataDir = resolveDataDir();
1502
1481
  const lanIP = getLanIP();
1503
1482
  console.log('');
1504
- console.log(' Let Them Talk - Agent Bridge Dashboard v3.4.2');
1483
+ console.log(' Let Them Talk - Agent Bridge Dashboard v3.4.3');
1505
1484
  console.log(' ============================================');
1506
1485
  console.log(' Dashboard: http://localhost:' + PORT);
1507
1486
  if (LAN_MODE && lanIP) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "let-them-talk",
3
- "version": "3.4.2",
3
+ "version": "3.4.3",
4
4
  "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -18,8 +18,7 @@ const PROFILES_FILE = path.join(DATA_DIR, 'profiles.json');
18
18
  const WORKFLOWS_FILE = path.join(DATA_DIR, 'workflows.json');
19
19
  const WORKSPACES_DIR = path.join(DATA_DIR, 'workspaces');
20
20
  const BRANCHES_FILE = path.join(DATA_DIR, 'branches.json');
21
- const PLUGINS_FILE = path.join(DATA_DIR, 'plugins.json');
22
- const PLUGINS_DIR = path.join(DATA_DIR, 'plugins');
21
+ // Plugins removed in v3.4.3 — unnecessary attack surface, CLIs have their own extension systems
23
22
 
24
23
  // In-memory state for this process
25
24
  let registeredName = null;
@@ -418,17 +417,6 @@ function getHistoryFile(branch) {
418
417
  return path.join(DATA_DIR, `branch-${sanitizeName(branch)}-history.jsonl`);
419
418
  }
420
419
 
421
- // --- Plugin helpers ---
422
-
423
- function getPluginRegistry() {
424
- if (!fs.existsSync(PLUGINS_FILE)) return [];
425
- try { return JSON.parse(fs.readFileSync(PLUGINS_FILE, 'utf8')); } catch { return []; }
426
- }
427
-
428
- function savePluginRegistry(plugins) {
429
- fs.writeFileSync(PLUGINS_FILE, JSON.stringify(plugins, null, 2));
430
- }
431
-
432
420
  // --- Tool implementations ---
433
421
 
434
422
  function toolRegister(name, provider = null) {
@@ -1181,8 +1169,8 @@ function toolReset() {
1181
1169
  }
1182
1170
  }
1183
1171
  }
1184
- // Remove profiles, workflows, branches, plugins, permissions, read receipts
1185
- for (const f of [PROFILES_FILE, WORKFLOWS_FILE, BRANCHES_FILE, PLUGINS_FILE, PERMISSIONS_FILE, READ_RECEIPTS_FILE]) {
1172
+ // Remove profiles, workflows, branches, permissions, read receipts
1173
+ for (const f of [PROFILES_FILE, WORKFLOWS_FILE, BRANCHES_FILE, PERMISSIONS_FILE, READ_RECEIPTS_FILE]) {
1186
1174
  if (fs.existsSync(f)) fs.unlinkSync(f);
1187
1175
  }
1188
1176
  // Remove workspaces dir
@@ -1497,11 +1485,6 @@ const server = new Server(
1497
1485
  );
1498
1486
 
1499
1487
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1500
- const pluginTools = loadedPlugins.map(p => ({
1501
- name: 'plugin_' + p.name,
1502
- description: '[Plugin] ' + p.description,
1503
- inputSchema: p.inputSchema,
1504
- }));
1505
1488
  return {
1506
1489
  tools: [
1507
1490
  {
@@ -1875,7 +1858,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1875
1858
  properties: {},
1876
1859
  },
1877
1860
  },
1878
- ...pluginTools,
1879
1861
  ],
1880
1862
  };
1881
1863
  });
@@ -1969,12 +1951,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1969
1951
  result = toolListBranches();
1970
1952
  break;
1971
1953
  default:
1972
- // Check if it's a plugin tool
1973
- if (name.startsWith('plugin_')) {
1974
- const pluginName = name.substring(7);
1975
- result = await executePlugin(pluginName, args);
1976
- break;
1977
- }
1978
1954
  return {
1979
1955
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
1980
1956
  isError: true,
@@ -2014,90 +1990,11 @@ process.on('exit', () => {
2014
1990
  process.on('SIGTERM', () => process.exit(0));
2015
1991
  process.on('SIGINT', () => process.exit(0));
2016
1992
 
2017
- // --- Phase 5: Plugin system ---
2018
-
2019
- let loadedPlugins = []; // { name, description, inputSchema, handler }
2020
-
2021
- function loadPlugins() {
2022
- loadedPlugins = [];
2023
- if (!fs.existsSync(PLUGINS_DIR)) return;
2024
- const registry = getPluginRegistry();
2025
- const enabledNames = new Set(registry.filter(p => p.enabled !== false).map(p => p.name));
2026
-
2027
- try {
2028
- const vm = require('vm');
2029
- const files = fs.readdirSync(PLUGINS_DIR).filter(f => f.endsWith('.js'));
2030
- for (const file of files) {
2031
- try {
2032
- const pluginPath = path.join(PLUGINS_DIR, file);
2033
- const code = fs.readFileSync(pluginPath, 'utf8');
2034
- // Run plugin in a sandboxed VM context — no require, no process, no child_process
2035
- const sandbox = { module: { exports: {} }, exports: {}, console: { log: () => {}, error: () => {}, warn: () => {} } };
2036
- vm.runInNewContext(code, sandbox, { filename: file, timeout: 5000 });
2037
- const plugin = sandbox.module.exports;
2038
- if (!plugin.name || !plugin.description || !plugin.handler) {
2039
- console.error(`Plugin ${file}: missing name, description, or handler`);
2040
- continue;
2041
- }
2042
- if (!enabledNames.has(plugin.name) && enabledNames.size > 0) continue;
2043
- loadedPlugins.push({
2044
- name: plugin.name,
2045
- description: plugin.description,
2046
- inputSchema: plugin.inputSchema || { type: 'object', properties: {} },
2047
- handler: plugin.handler,
2048
- });
2049
- console.error(`Plugin loaded: ${plugin.name} (sandboxed)`);
2050
- } catch (e) {
2051
- console.error(`Plugin ${file} failed to load: ${e.message}`);
2052
- }
2053
- }
2054
- } catch {}
2055
- }
2056
-
2057
- function executePlugin(pluginName, args) {
2058
- const plugin = loadedPlugins.find(p => p.name === pluginName);
2059
- if (!plugin) return { error: `Plugin "${pluginName}" not found` };
2060
-
2061
- const context = {
2062
- registeredName,
2063
- sendMessage: (to, content) => toolSendMessage(content, to),
2064
- getAgents: () => toolListAgents().agents,
2065
- getHistory: (limit) => toolGetHistory(limit),
2066
- readFile: (filePath) => {
2067
- const resolved = path.resolve(filePath);
2068
- const allowedRoot = path.resolve(process.cwd());
2069
- let realPath;
2070
- try { realPath = fs.realpathSync(resolved); } catch { throw new Error('File not found'); }
2071
- if (!realPath.startsWith(allowedRoot + path.sep) && realPath !== allowedRoot) {
2072
- throw new Error('File path must be within the project directory');
2073
- }
2074
- return fs.readFileSync(realPath, 'utf8');
2075
- },
2076
- };
2077
-
2078
- return new Promise((resolve) => {
2079
- const timeout = setTimeout(() => resolve({ error: 'Plugin execution timed out (30s)' }), 30000);
2080
- try {
2081
- const result = plugin.handler(args, context);
2082
- if (result && typeof result.then === 'function') {
2083
- result.then(r => { clearTimeout(timeout); resolve(r); }).catch(e => { clearTimeout(timeout); resolve({ error: e.message }); });
2084
- } else {
2085
- clearTimeout(timeout);
2086
- resolve(result);
2087
- }
2088
- } catch (e) {
2089
- clearTimeout(timeout);
2090
- resolve({ error: e.message });
2091
- }
2092
- });
2093
- }
2094
-
2095
1993
  async function main() {
2096
1994
  ensureDataDir();
2097
- loadPlugins();
2098
1995
  const transport = new StdioServerTransport();
2099
1996
  await server.connect(transport);
2100
- console.error('Agent Bridge MCP server v3.4.2 running (' + (27 + loadedPlugins.length) + ' tools)');
1997
+ console.error('Agent Bridge MCP server v3.4.3 running (27 tools)');
2101
1998
  }
2102
1999
 
2103
2000
  main().catch(console.error);