let-them-talk 3.1.0 → 3.2.0
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/LICENSE +21 -0
- package/dashboard.html +25 -6
- package/dashboard.js +26 -6
- package/logo.png +0 -0
- package/package.json +11 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Dekelelz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dashboard.html
CHANGED
|
@@ -2810,7 +2810,7 @@ function renderAgents(agents) {
|
|
|
2810
2810
|
}
|
|
2811
2811
|
|
|
2812
2812
|
var avatarHtml = info.avatar
|
|
2813
|
-
? '<img class="agent-avatar-img" src="' + info.avatar + '" alt="' + escapeHtml(name) + '" onerror="this.style.display=\'none\'">'
|
|
2813
|
+
? '<img class="agent-avatar-img" src="' + escapeHtml(info.avatar) + '" alt="' + escapeHtml(name) + '" onerror="this.style.display=\'none\'">'
|
|
2814
2814
|
: '<div class="agent-avatar" style="background:' + color + '">' + initial(name) + '</div>';
|
|
2815
2815
|
var displayName = info.display_name || name;
|
|
2816
2816
|
var roleHtml = info.role ? '<span class="role-badge">' + escapeHtml(info.role) + '</span>' : '';
|
|
@@ -3061,6 +3061,8 @@ function renderMessages(messages) {
|
|
|
3061
3061
|
if (m.acked) badges += '<span class="badge badge-ack">ACK</span>';
|
|
3062
3062
|
if (m.thread_id) badges += '<span class="badge badge-thread">Thread</span>';
|
|
3063
3063
|
|
|
3064
|
+
var msgAvatarHtml = getMsgAvatar(m.from, color);
|
|
3065
|
+
|
|
3064
3066
|
if (isSystem) {
|
|
3065
3067
|
html += '<div class="message system-msg' + newClass + '">' +
|
|
3066
3068
|
'<div class="msg-body">' +
|
|
@@ -3068,8 +3070,6 @@ function renderMessages(messages) {
|
|
|
3068
3070
|
'</div></div>';
|
|
3069
3071
|
lastFrom = '';
|
|
3070
3072
|
lastTo = '';
|
|
3071
|
-
var msgAvatarHtml = getMsgAvatar(m.from, color);
|
|
3072
|
-
|
|
3073
3073
|
} else if (m.type === 'handoff') {
|
|
3074
3074
|
html += '<div class="message handoff-msg' + newClass + '">' +
|
|
3075
3075
|
buildMsgActions(m.id) +
|
|
@@ -3755,7 +3755,7 @@ textarea.addEventListener('input', function() {
|
|
|
3755
3755
|
function getMsgAvatar(name, color) {
|
|
3756
3756
|
var agent = cachedAgents[name];
|
|
3757
3757
|
if (agent && agent.avatar) {
|
|
3758
|
-
return '<img class="msg-avatar-img" src="' + agent.avatar + '" alt="' + escapeHtml(name) + '" onerror="this.outerHTML=\'<div class="msg-avatar" style="background:' + color + '">' + initial(name) + '</div>\'">';
|
|
3758
|
+
return '<img class="msg-avatar-img" src="' + escapeHtml(agent.avatar) + '" alt="' + escapeHtml(name) + '" onerror="this.outerHTML=\'<div class="msg-avatar" style="background:' + color + '">' + initial(name) + '</div>\'">';
|
|
3759
3759
|
}
|
|
3760
3760
|
return '<div class="msg-avatar" style="background:' + color + '">' + initial(name) + '</div>';
|
|
3761
3761
|
}
|
|
@@ -4205,7 +4205,13 @@ function addProject() {
|
|
|
4205
4205
|
if (res.success) {
|
|
4206
4206
|
input.value = '';
|
|
4207
4207
|
input.classList.remove('visible');
|
|
4208
|
+
// Auto-switch to the newly added project
|
|
4209
|
+
activeProject = res.project.path;
|
|
4208
4210
|
loadProjects();
|
|
4211
|
+
setTimeout(function() {
|
|
4212
|
+
document.getElementById('project-select').value = activeProject;
|
|
4213
|
+
switchProject();
|
|
4214
|
+
}, 200);
|
|
4209
4215
|
} else {
|
|
4210
4216
|
alert(res.error || 'Failed to add project');
|
|
4211
4217
|
}
|
|
@@ -4317,7 +4323,7 @@ function updateTypingIndicator(agents) {
|
|
|
4317
4323
|
var info = agents[keys[i]];
|
|
4318
4324
|
// Agent is alive, not listening, and was recently active (processing a message)
|
|
4319
4325
|
if (info.alive && !info.is_listening && info.status === 'active') {
|
|
4320
|
-
typing.push(info.display_name || keys[i]);
|
|
4326
|
+
typing.push(escapeHtml(info.display_name || keys[i]));
|
|
4321
4327
|
}
|
|
4322
4328
|
}
|
|
4323
4329
|
var bar = document.getElementById('typing-bar');
|
|
@@ -4599,6 +4605,8 @@ function renderPhoneModalContent() {
|
|
|
4599
4605
|
}
|
|
4600
4606
|
|
|
4601
4607
|
var url = 'http://' + lanState.lan_ip + ':' + lanState.port;
|
|
4608
|
+
// Include active project so the phone shows the same view
|
|
4609
|
+
if (activeProject) url += '?project=' + encodeURIComponent(activeProject);
|
|
4602
4610
|
var qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=180x180&color=58a6ff&bgcolor=0d1117&data=' + encodeURIComponent(url);
|
|
4603
4611
|
|
|
4604
4612
|
el.innerHTML =
|
|
@@ -4614,6 +4622,7 @@ function renderPhoneModalContent() {
|
|
|
4614
4622
|
|
|
4615
4623
|
function copyPhoneUrl() {
|
|
4616
4624
|
var url = 'http://' + lanState.lan_ip + ':' + lanState.port;
|
|
4625
|
+
if (activeProject) url += '?project=' + encodeURIComponent(activeProject);
|
|
4617
4626
|
navigator.clipboard.writeText(url).then(function() {
|
|
4618
4627
|
var btn = document.querySelector('.phone-url-copy');
|
|
4619
4628
|
btn.textContent = 'Copied!';
|
|
@@ -4720,7 +4729,7 @@ function doLaunch() {
|
|
|
4720
4729
|
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
4721
4730
|
if (data.error) {
|
|
4722
4731
|
resultEl.className = 'launch-result error';
|
|
4723
|
-
resultEl.innerHTML = data.error;
|
|
4732
|
+
resultEl.innerHTML = escapeHtml(data.error);
|
|
4724
4733
|
return;
|
|
4725
4734
|
}
|
|
4726
4735
|
resultEl.className = 'launch-result success';
|
|
@@ -4786,6 +4795,16 @@ function initSSE() {
|
|
|
4786
4795
|
}
|
|
4787
4796
|
|
|
4788
4797
|
// ==================== INIT ====================
|
|
4798
|
+
|
|
4799
|
+
// Auto-select project from URL param (for phone access links)
|
|
4800
|
+
(function() {
|
|
4801
|
+
var params = new URLSearchParams(window.location.search);
|
|
4802
|
+
var projectFromUrl = params.get('project');
|
|
4803
|
+
if (projectFromUrl) {
|
|
4804
|
+
activeProject = projectFromUrl;
|
|
4805
|
+
}
|
|
4806
|
+
})();
|
|
4807
|
+
|
|
4789
4808
|
loadProjects();
|
|
4790
4809
|
poll();
|
|
4791
4810
|
initSSE();
|
package/dashboard.js
CHANGED
|
@@ -3,19 +3,24 @@ const http = require('http');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
-
const { exec } = require('child_process');
|
|
6
|
+
const { exec, spawn } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const PORT = parseInt(process.env.AGENT_BRIDGE_PORT || '3000', 10);
|
|
9
9
|
let LAN_MODE = process.env.AGENT_BRIDGE_LAN === 'true';
|
|
10
10
|
|
|
11
11
|
function getLanIP() {
|
|
12
12
|
const interfaces = os.networkInterfaces();
|
|
13
|
+
let fallback = null;
|
|
13
14
|
for (const name of Object.keys(interfaces)) {
|
|
14
15
|
for (const iface of interfaces[name]) {
|
|
15
|
-
if (iface.family === 'IPv4' && !iface.internal)
|
|
16
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
17
|
+
// Prefer real LAN IPs (192.168.x, 10.x, 172.16-31.x) over link-local (169.254.x)
|
|
18
|
+
if (!iface.address.startsWith('169.254.')) return iface.address;
|
|
19
|
+
if (!fallback) fallback = iface.address;
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
|
-
return
|
|
23
|
+
return fallback;
|
|
19
24
|
}
|
|
20
25
|
const DEFAULT_DATA_DIR = process.env.AGENT_BRIDGE_DATA || path.join(process.cwd(), '.agent-bridge');
|
|
21
26
|
const HTML_FILE = path.join(__dirname, 'dashboard.html');
|
|
@@ -134,6 +139,9 @@ function getDefaultAvatar(name) {
|
|
|
134
139
|
function apiHistory(query) {
|
|
135
140
|
const projectPath = query.get('project') || null;
|
|
136
141
|
const branch = query.get('branch') || null;
|
|
142
|
+
if (branch && !/^[a-zA-Z0-9_-]{1,64}$/.test(branch)) {
|
|
143
|
+
return { error: 'Invalid branch name' };
|
|
144
|
+
}
|
|
137
145
|
const histFile = branch && branch !== 'main'
|
|
138
146
|
? filePath(`branch-${branch}-history.jsonl`, projectPath)
|
|
139
147
|
: filePath('history.jsonl', projectPath);
|
|
@@ -306,6 +314,14 @@ function apiAddProject(body) {
|
|
|
306
314
|
const name = body.name || path.basename(absPath);
|
|
307
315
|
if (projects.find(p => p.path === absPath)) return { error: 'Project already added' };
|
|
308
316
|
|
|
317
|
+
// Create .agent-bridge directory if it doesn't exist
|
|
318
|
+
const abDir = path.join(absPath, '.agent-bridge');
|
|
319
|
+
if (!fs.existsSync(abDir)) fs.mkdirSync(abDir, { recursive: true });
|
|
320
|
+
|
|
321
|
+
// Set up MCP config so agents can use it
|
|
322
|
+
const serverPath = path.join(__dirname, 'server.js').replace(/\\/g, '/');
|
|
323
|
+
ensureMCPConfig('claude', serverPath, absPath);
|
|
324
|
+
|
|
309
325
|
projects.push({ name, path: absPath, added_at: new Date().toISOString() });
|
|
310
326
|
saveProjects(projects);
|
|
311
327
|
return { success: true, project: { name, path: absPath } };
|
|
@@ -602,8 +618,7 @@ function apiLaunchAgent(body) {
|
|
|
602
618
|
|
|
603
619
|
// Try to launch terminal on Windows
|
|
604
620
|
if (process.platform === 'win32') {
|
|
605
|
-
|
|
606
|
-
exec(`start cmd /k "cd /d "${escapedDir}" && ${cliCmd}"`, { cwd: projectDir });
|
|
621
|
+
spawn('cmd', ['/c', 'start', 'cmd', '/k', `cd /d "${projectDir}" && ${cliCmd}`], { cwd: projectDir, shell: false, detached: true, stdio: 'ignore' });
|
|
607
622
|
return { success: true, launched: true, cli, project_dir: projectDir, prompt: launchPrompt };
|
|
608
623
|
}
|
|
609
624
|
|
|
@@ -649,7 +664,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
649
664
|
const allowedOrigin = `http://localhost:${PORT}`;
|
|
650
665
|
const reqOrigin = req.headers.origin;
|
|
651
666
|
if (LAN_MODE && reqOrigin) {
|
|
652
|
-
res.setHeader('Access-Control-Allow-Origin',
|
|
667
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
653
668
|
} else if (reqOrigin === allowedOrigin || reqOrigin === `http://127.0.0.1:${PORT}`) {
|
|
654
669
|
res.setHeader('Access-Control-Allow-Origin', reqOrigin);
|
|
655
670
|
}
|
|
@@ -781,6 +796,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
781
796
|
else if (url.pathname === '/api/workspaces' && req.method === 'GET') {
|
|
782
797
|
const projectPath = url.searchParams.get('project') || null;
|
|
783
798
|
const agentParam = url.searchParams.get('agent');
|
|
799
|
+
if (agentParam && !/^[a-zA-Z0-9]{1,20}$/.test(agentParam)) {
|
|
800
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
801
|
+
res.end(JSON.stringify({ error: 'Invalid agent name' }));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
784
804
|
const dataDir = resolveDataDir(projectPath);
|
|
785
805
|
const wsDir = path.join(dataDir, 'workspaces');
|
|
786
806
|
const result = {};
|
package/logo.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "let-them-talk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
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": {
|
|
@@ -9,14 +9,20 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node server.js",
|
|
12
|
-
"dashboard": "node dashboard.js"
|
|
12
|
+
"dashboard": "node dashboard.js",
|
|
13
|
+
"test": "echo \"No tests yet\""
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=16.0.0"
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"server.js",
|
|
16
20
|
"dashboard.js",
|
|
17
21
|
"dashboard.html",
|
|
18
22
|
"cli.js",
|
|
19
|
-
"templates/"
|
|
23
|
+
"templates/",
|
|
24
|
+
"logo.png",
|
|
25
|
+
"LICENSE"
|
|
20
26
|
],
|
|
21
27
|
"keywords": [
|
|
22
28
|
"mcp",
|
|
@@ -33,7 +39,7 @@
|
|
|
33
39
|
],
|
|
34
40
|
"repository": {
|
|
35
41
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/Dekelelz/let-them-talk.git"
|
|
42
|
+
"url": "git+https://github.com/Dekelelz/let-them-talk.git"
|
|
37
43
|
},
|
|
38
44
|
"homepage": "https://github.com/Dekelelz/let-them-talk",
|
|
39
45
|
"bugs": {
|
|
@@ -42,6 +48,6 @@
|
|
|
42
48
|
"author": "Dekelelz",
|
|
43
49
|
"license": "MIT",
|
|
44
50
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "
|
|
51
|
+
"@modelcontextprotocol/sdk": "1.12.1"
|
|
46
52
|
}
|
|
47
53
|
}
|