let-them-talk 3.4.4 → 3.5.1
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 +30 -0
- package/LICENSE +1 -1
- package/README.md +9 -8
- package/cli.js +152 -3
- package/conversation-templates/code-review.json +11 -0
- package/conversation-templates/debug-squad.json +11 -0
- package/conversation-templates/feature-build.json +11 -0
- package/conversation-templates/research-write.json +11 -0
- package/dashboard.html +185 -3
- package/dashboard.js +287 -7
- package/package.json +2 -2
- package/server.js +227 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.5.0] - 2026-03-15
|
|
4
|
+
|
|
5
|
+
### Added — Group Conversation Mode
|
|
6
|
+
- **`set_conversation_mode("group")`** — enables free multi-agent collaboration with auto-broadcast
|
|
7
|
+
- **`listen_group()`** — batch message receiver with random stagger (1-3s) to prevent simultaneous responses
|
|
8
|
+
- Returns ALL unconsumed messages + last 20 messages of context + hints about silent agents
|
|
9
|
+
- Auto-broadcast in group mode: every message is shared with all agents automatically
|
|
10
|
+
- Cooldown enforcement: agents must wait 3s between sends to maintain conversation flow
|
|
11
|
+
- Cascade prevention: broadcast copies don't trigger further broadcasts
|
|
12
|
+
- MCP tools: 27 → 29
|
|
13
|
+
|
|
14
|
+
### Added — Dashboard Features
|
|
15
|
+
- **Notification panel** — bell icon with badge count, dropdown event feed (agent online/offline, listening status changes)
|
|
16
|
+
- **Agent leaderboard** — performance scoring (0-100) with responsiveness, activity, reliability, collaboration dimensions
|
|
17
|
+
- **Cross-project search** — "All Projects" toggle in search bar, searches across all registered projects
|
|
18
|
+
- **Animated replay export** — Export conversation as self-playing HTML file with typing animations and play/pause controls
|
|
19
|
+
- **Ollama integration** — `npx let-them-talk init --ollama` auto-detects Ollama, creates bridge script for local models
|
|
20
|
+
|
|
21
|
+
### Fixed — PID & Registration Integrity
|
|
22
|
+
- Registration file locking with try/finally (prevents race conditions when multiple agents register simultaneously)
|
|
23
|
+
- PID stale detection uses `last_activity` with 30s threshold (prevents false "alive" from Windows PID reuse)
|
|
24
|
+
- Lock file cleaned up on process exit
|
|
25
|
+
- Dashboard inject/nudge snapshots project context at click time (prevents wrong-project race)
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
- `toolHandoff` and workflow auto-handoff now check `canSendTo` permissions
|
|
29
|
+
- `lastSentAt` updated in `toolBroadcast` (prevents cooldown bypass)
|
|
30
|
+
- `config.json` added to both server and dashboard reset cleanup
|
|
31
|
+
- Auto-broadcast respects `canSendTo` per recipient
|
|
32
|
+
|
|
3
33
|
## [3.4.4] - 2026-03-15
|
|
4
34
|
|
|
5
35
|
### Fixed
|
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.
|
|
9
|
+
Licensed Work: Let Them Talk v3.5.0
|
|
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/README.md
CHANGED
|
@@ -84,19 +84,20 @@ Each terminal spawns its own MCP server process. All processes share a `.agent-b
|
|
|
84
84
|
|
|
85
85
|
## Highlights
|
|
86
86
|
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
89
|
-
- **
|
|
90
|
-
- **
|
|
87
|
+
- **29 MCP tools** — messaging, tasks, workflows, profiles, workspaces, branching, group chat
|
|
88
|
+
- **Group conversation mode** — free multi-agent collaboration with auto-broadcast, stagger delays, and cooldown
|
|
89
|
+
- **Premium dashboard** — glassmorphism UI, notifications panel, agent leaderboard, cross-project search
|
|
90
|
+
- **Animated replay export** — export conversations as self-playing HTML with typing animations
|
|
91
|
+
- **Ollama integration** — `npx let-them-talk init --ollama` for local AI models
|
|
92
|
+
- **Stats & analytics** — per-agent scores, response times, hourly charts, conversation velocity
|
|
93
|
+
- **Conversation templates** — 4 built-in workflows (Code Review, Debug Squad, Feature Dev, Research & Write)
|
|
91
94
|
- **Message management** — edit, delete, copy messages with full edit history
|
|
92
95
|
- **Task management** — drag-and-drop kanban board between agents
|
|
93
96
|
- **Workflow pipelines** — multi-step automation with auto-handoff
|
|
94
97
|
- **Agent profiles** — display names, SVG avatars, roles, bios
|
|
95
98
|
- **Conversation branching** — fork at any point, isolated history per branch
|
|
96
|
-
- **
|
|
97
|
-
- **
|
|
98
|
-
- **CLI tools** — send messages and check status directly from the command line
|
|
99
|
-
- **Secure by default** — CSRF protection, LAN auth tokens, Content Security Policy, agent permissions
|
|
99
|
+
- **Multi-format export** — HTML, Markdown, JSON, and animated replay
|
|
100
|
+
- **Secure by default** — CSRF, LAN auth tokens, CSP, permissions, registration locking
|
|
100
101
|
- **Zero config** — one `npx` command, auto-detects your CLI, works immediately
|
|
101
102
|
|
|
102
103
|
## Agent Templates
|
package/cli.js
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
6
7
|
|
|
7
8
|
const command = process.argv[2];
|
|
8
9
|
|
|
9
10
|
function printUsage() {
|
|
10
11
|
console.log(`
|
|
11
|
-
Let Them Talk — Agent Bridge v3.
|
|
12
|
+
Let Them Talk — Agent Bridge v3.5.1
|
|
12
13
|
MCP message broker for inter-agent communication
|
|
13
|
-
Supports: Claude Code, Gemini CLI, Codex CLI
|
|
14
|
+
Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
|
|
14
15
|
|
|
15
16
|
Usage:
|
|
16
17
|
npx let-them-talk init Auto-detect CLI and configure MCP
|
|
@@ -18,7 +19,8 @@ function printUsage() {
|
|
|
18
19
|
npx let-them-talk init --gemini Configure for Gemini CLI
|
|
19
20
|
npx let-them-talk init --codex Configure for Codex CLI
|
|
20
21
|
npx let-them-talk init --all Configure for all supported CLIs
|
|
21
|
-
npx let-them-talk init --
|
|
22
|
+
npx let-them-talk init --ollama Setup Ollama agent bridge (local LLM)
|
|
23
|
+
npx let-them-talk init --template T Initialize with a team template (pair, team, review, debate, ollama)
|
|
22
24
|
npx let-them-talk templates List available agent templates
|
|
23
25
|
npx let-them-talk dashboard Launch the web dashboard (http://localhost:3000)
|
|
24
26
|
npx let-them-talk dashboard --lan Launch dashboard accessible on LAN (phone/tablet)
|
|
@@ -52,6 +54,16 @@ function detectCLIs() {
|
|
|
52
54
|
return detected;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
// Detect Ollama installation
|
|
58
|
+
function detectOllama() {
|
|
59
|
+
try {
|
|
60
|
+
const version = execSync('ollama --version', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
61
|
+
return { installed: true, version };
|
|
62
|
+
} catch {
|
|
63
|
+
return { installed: false };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
// The data directory where all agents read/write — must be the same for server + dashboard
|
|
56
68
|
function dataDir(cwd) {
|
|
57
69
|
return path.join(cwd, '.agent-bridge').replace(/\\/g, '/');
|
|
@@ -147,6 +159,131 @@ AGENT_BRIDGE_DATA_DIR = ${JSON.stringify(dataDir(cwd))}
|
|
|
147
159
|
console.log(' [ok] Codex CLI: .codex/config.toml updated');
|
|
148
160
|
}
|
|
149
161
|
|
|
162
|
+
// Setup Ollama agent bridge script
|
|
163
|
+
function setupOllama(serverPath, cwd) {
|
|
164
|
+
const dir = dataDir(cwd);
|
|
165
|
+
const scriptPath = path.join(cwd, '.agent-bridge', 'ollama-agent.js');
|
|
166
|
+
|
|
167
|
+
if (!fs.existsSync(path.join(cwd, '.agent-bridge'))) {
|
|
168
|
+
fs.mkdirSync(path.join(cwd, '.agent-bridge'), { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const script = `#!/usr/bin/env node
|
|
172
|
+
// ollama-agent.js - bridges Ollama to Let Them Talk
|
|
173
|
+
// Usage: node .agent-bridge/ollama-agent.js [agent-name] [model]
|
|
174
|
+
const fs = require('fs'), path = require('path'), http = require('http');
|
|
175
|
+
const DATA_DIR = path.join(__dirname);
|
|
176
|
+
const name = process.argv[2] || 'Ollama';
|
|
177
|
+
const model = process.argv[3] || 'llama3';
|
|
178
|
+
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
|
|
179
|
+
|
|
180
|
+
function readJson(f) { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return {}; } }
|
|
181
|
+
function readJsonl(f) { if (!fs.existsSync(f)) return []; return fs.readFileSync(f, 'utf8').split('\\n').filter(l => l.trim()).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); }
|
|
182
|
+
|
|
183
|
+
// Register agent
|
|
184
|
+
function register() {
|
|
185
|
+
const agentsFile = path.join(DATA_DIR, 'agents.json');
|
|
186
|
+
const agents = readJson(agentsFile);
|
|
187
|
+
agents[name] = { pid: process.pid, timestamp: new Date().toISOString(), last_activity: new Date().toISOString(), provider: 'Ollama (' + model + ')' };
|
|
188
|
+
fs.writeFileSync(agentsFile, JSON.stringify(agents, null, 2));
|
|
189
|
+
console.log('[' + name + '] Registered (PID ' + process.pid + ', model: ' + model + ')');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Update heartbeat
|
|
193
|
+
function heartbeat() {
|
|
194
|
+
const agentsFile = path.join(DATA_DIR, 'agents.json');
|
|
195
|
+
const agents = readJson(agentsFile);
|
|
196
|
+
if (agents[name]) {
|
|
197
|
+
agents[name].last_activity = new Date().toISOString();
|
|
198
|
+
agents[name].pid = process.pid;
|
|
199
|
+
fs.writeFileSync(agentsFile, JSON.stringify(agents, null, 2));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Call Ollama API
|
|
204
|
+
function callOllama(prompt) {
|
|
205
|
+
return new Promise(function(resolve, reject) {
|
|
206
|
+
const url = new URL(OLLAMA_URL + '/api/chat');
|
|
207
|
+
const body = JSON.stringify({ model: model, messages: [{ role: 'user', content: prompt }], stream: false });
|
|
208
|
+
const req = http.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }, function(res) {
|
|
209
|
+
let data = '';
|
|
210
|
+
res.on('data', function(c) { data += c; });
|
|
211
|
+
res.on('end', function() {
|
|
212
|
+
try { const j = JSON.parse(data); resolve(j.message ? j.message.content : data); }
|
|
213
|
+
catch { resolve(data); }
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
req.on('error', reject);
|
|
217
|
+
req.write(body);
|
|
218
|
+
req.end();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Send a message
|
|
223
|
+
function sendMessage(to, content) {
|
|
224
|
+
const msgId = 'm' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
225
|
+
const msg = { id: msgId, from: name, to: to, content: content, timestamp: new Date().toISOString() };
|
|
226
|
+
fs.appendFileSync(path.join(DATA_DIR, 'messages.jsonl'), JSON.stringify(msg) + '\\n');
|
|
227
|
+
fs.appendFileSync(path.join(DATA_DIR, 'history.jsonl'), JSON.stringify(msg) + '\\n');
|
|
228
|
+
console.log('[' + name + '] -> ' + to + ': ' + content.substring(0, 80) + (content.length > 80 ? '...' : ''));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Listen for messages
|
|
232
|
+
let lastOffset = 0;
|
|
233
|
+
function checkMessages() {
|
|
234
|
+
const consumedFile = path.join(DATA_DIR, 'consumed-' + name + '.json');
|
|
235
|
+
const consumed = readJson(consumedFile);
|
|
236
|
+
lastOffset = consumed.offset || 0;
|
|
237
|
+
|
|
238
|
+
const messages = readJsonl(path.join(DATA_DIR, 'messages.jsonl'));
|
|
239
|
+
const newMsgs = messages.slice(lastOffset).filter(function(m) {
|
|
240
|
+
return m.to === name || (m.to === 'all' && m.from !== name);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (newMsgs.length > 0) {
|
|
244
|
+
consumed.offset = messages.length;
|
|
245
|
+
fs.writeFileSync(consumedFile, JSON.stringify(consumed));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return newMsgs;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function processMessages() {
|
|
252
|
+
const msgs = checkMessages();
|
|
253
|
+
for (const m of msgs) {
|
|
254
|
+
console.log('[' + name + '] <- ' + m.from + ': ' + m.content.substring(0, 80));
|
|
255
|
+
try {
|
|
256
|
+
const response = await callOllama(m.content);
|
|
257
|
+
sendMessage(m.from, response);
|
|
258
|
+
} catch (e) {
|
|
259
|
+
sendMessage(m.from, 'Error calling Ollama: ' + e.message);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Main loop
|
|
265
|
+
register();
|
|
266
|
+
const hb = setInterval(heartbeat, 10000);
|
|
267
|
+
hb.unref();
|
|
268
|
+
console.log('[' + name + '] Listening for messages... (Ctrl+C to stop)');
|
|
269
|
+
setInterval(processMessages, 2000);
|
|
270
|
+
|
|
271
|
+
// Cleanup on exit
|
|
272
|
+
process.on('SIGINT', function() { console.log('\\n[' + name + '] Shutting down.'); process.exit(0); });
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
fs.writeFileSync(scriptPath, script);
|
|
276
|
+
console.log(' [ok] Ollama agent script created: .agent-bridge/ollama-agent.js');
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log(' Launch an Ollama agent with:');
|
|
279
|
+
console.log(' node .agent-bridge/ollama-agent.js <name> <model>');
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(' Examples:');
|
|
282
|
+
console.log(' node .agent-bridge/ollama-agent.js Ollama llama3');
|
|
283
|
+
console.log(' node .agent-bridge/ollama-agent.js Coder codellama');
|
|
284
|
+
console.log(' node .agent-bridge/ollama-agent.js Writer mistral');
|
|
285
|
+
}
|
|
286
|
+
|
|
150
287
|
function init() {
|
|
151
288
|
const cwd = process.cwd();
|
|
152
289
|
const serverPath = path.join(__dirname, 'server.js').replace(/\\/g, '/');
|
|
@@ -168,6 +305,18 @@ function init() {
|
|
|
168
305
|
targets = ['codex'];
|
|
169
306
|
} else if (flag === '--all') {
|
|
170
307
|
targets = ['claude', 'gemini', 'codex'];
|
|
308
|
+
} else if (flag === '--ollama') {
|
|
309
|
+
const ollama = detectOllama();
|
|
310
|
+
if (!ollama.installed) {
|
|
311
|
+
console.log(' Ollama not found. Install it from: https://ollama.com/download');
|
|
312
|
+
console.log(' After installing, run: ollama pull llama3');
|
|
313
|
+
console.log('');
|
|
314
|
+
} else {
|
|
315
|
+
console.log(' Ollama detected: ' + ollama.version);
|
|
316
|
+
setupOllama(serverPath, cwd);
|
|
317
|
+
}
|
|
318
|
+
targets = detectCLIs();
|
|
319
|
+
if (targets.length === 0) targets = ['claude'];
|
|
171
320
|
} else {
|
|
172
321
|
// Auto-detect
|
|
173
322
|
targets = detectCLIs();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "code-review",
|
|
3
|
+
"name": "Code Review Pipeline",
|
|
4
|
+
"description": "Developer writes code, Reviewer checks it, Tester validates",
|
|
5
|
+
"agents": [
|
|
6
|
+
{ "name": "Developer", "role": "Developer", "prompt": "You are a developer. Write code as instructed. After completing, send your code to Reviewer for review." },
|
|
7
|
+
{ "name": "Reviewer", "role": "Code Reviewer", "prompt": "You are a code reviewer. Wait for code from Developer. Review it for bugs, style, and best practices. Send feedback back to Developer or approve and forward to Tester." },
|
|
8
|
+
{ "name": "Tester", "role": "QA Tester", "prompt": "You are a QA tester. Wait for approved code from Reviewer. Write and run tests. Report results back to the team." }
|
|
9
|
+
],
|
|
10
|
+
"workflow": { "name": "Code Review", "steps": ["Write Code", "Review", "Test", "Approve"] }
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "debug-squad",
|
|
3
|
+
"name": "Debug Squad",
|
|
4
|
+
"description": "Investigator finds the bug, Fixer patches it, Verifier confirms the fix",
|
|
5
|
+
"agents": [
|
|
6
|
+
{ "name": "Investigator", "role": "Bug Investigator", "prompt": "You investigate bugs. Analyze error logs, trace code paths, and identify root causes. Send findings to Fixer." },
|
|
7
|
+
{ "name": "Fixer", "role": "Bug Fixer", "prompt": "You fix bugs. Wait for findings from Investigator. Implement fixes and send to Verifier for confirmation." },
|
|
8
|
+
{ "name": "Verifier", "role": "Fix Verifier", "prompt": "You verify bug fixes. Wait for patches from Fixer. Test the fix and confirm resolution or send back for more work." }
|
|
9
|
+
],
|
|
10
|
+
"workflow": { "name": "Bug Fix", "steps": ["Investigate", "Fix", "Verify", "Close"] }
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "feature-build",
|
|
3
|
+
"name": "Feature Development",
|
|
4
|
+
"description": "Architect designs, Builder implements, Reviewer approves",
|
|
5
|
+
"agents": [
|
|
6
|
+
{ "name": "Architect", "role": "Software Architect", "prompt": "You are a software architect. Design the feature architecture, define interfaces, and create the implementation plan. Send the plan to Builder." },
|
|
7
|
+
{ "name": "Builder", "role": "Developer", "prompt": "You are a developer. Wait for architecture plans from Architect. Implement the feature following the design. Send completed code to Reviewer." },
|
|
8
|
+
{ "name": "Reviewer", "role": "Senior Reviewer", "prompt": "You are a senior reviewer. Review implementations from Builder against the architecture from Architect. Approve or request changes." }
|
|
9
|
+
],
|
|
10
|
+
"workflow": { "name": "Feature Dev", "steps": ["Design", "Implement", "Review", "Ship"] }
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "research-write",
|
|
3
|
+
"name": "Research & Write",
|
|
4
|
+
"description": "Researcher gathers info, Writer creates content, Editor polishes",
|
|
5
|
+
"agents": [
|
|
6
|
+
{ "name": "Researcher", "role": "Researcher", "prompt": "You are a researcher. Gather information on the given topic. Organize findings and send a research brief to Writer." },
|
|
7
|
+
{ "name": "Writer", "role": "Writer", "prompt": "You are a writer. Wait for research from Researcher. Write clear, well-structured content based on the findings. Send to Editor." },
|
|
8
|
+
{ "name": "Editor", "role": "Editor", "prompt": "You are an editor. Review and polish content from Writer. Check for clarity, accuracy, and style. Send back final version or request revisions." }
|
|
9
|
+
],
|
|
10
|
+
"workflow": { "name": "Content Pipeline", "steps": ["Research", "Draft", "Edit", "Publish"] }
|
|
11
|
+
}
|
package/dashboard.html
CHANGED
|
@@ -2611,6 +2611,13 @@
|
|
|
2611
2611
|
</div>
|
|
2612
2612
|
</div>
|
|
2613
2613
|
<div class="header-actions">
|
|
2614
|
+
<div style="position:relative;display:inline-block">
|
|
2615
|
+
<button class="notif-toggle" id="notif-bell" onclick="toggleNotifPanel()" title="Notifications" style="position:relative">🔔<span id="notif-badge" style="display:none;position:absolute;top:-4px;right:-4px;background:var(--red);color:#fff;font-size:8px;font-weight:700;min-width:14px;height:14px;border-radius:7px;text-align:center;line-height:14px;padding:0 3px">0</span></button>
|
|
2616
|
+
<div id="notif-panel" style="display:none;position:absolute;right:0;top:100%;margin-top:8px;background:var(--surface);border:1px solid var(--border-light);border-radius:12px;width:320px;max-height:400px;overflow-y:auto;z-index:300;box-shadow:var(--shadow-lg)">
|
|
2617
|
+
<div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:13px;display:flex;justify-content:space-between;align-items:center">Notifications <button onclick="clearNotifications()" style="background:none;border:none;color:var(--text-muted);font-size:11px;cursor:pointer">Clear</button></div>
|
|
2618
|
+
<div id="notif-list" style="padding:4px 0"><div style="padding:16px;text-align:center;color:var(--text-muted);font-size:12px">No notifications yet</div></div>
|
|
2619
|
+
</div>
|
|
2620
|
+
</div>
|
|
2614
2621
|
<button class="notif-toggle" id="notif-toggle" onclick="toggleNotifications()" title="Browser notifications">🔔</button>
|
|
2615
2622
|
<button class="theme-toggle" id="theme-toggle" onclick="toggleTheme()" title="Toggle dark/light theme">🌙</button>
|
|
2616
2623
|
<button class="sound-toggle" id="sound-toggle" onclick="toggleSound()" title="Toggle notification sound">🔈</button>
|
|
@@ -2623,6 +2630,7 @@
|
|
|
2623
2630
|
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportShareableHTML();toggleExportMenu()">HTML (shareable)</div>
|
|
2624
2631
|
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportConversation();toggleExportMenu()">Markdown (.md)</div>
|
|
2625
2632
|
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportJSON();toggleExportMenu()">JSON (.json)</div>
|
|
2633
|
+
<div style="padding:7px 12px;font-size:12px;cursor:pointer;transition:background 0.1s;border-top:1px solid var(--border)" onmouseover="this.style.background='var(--surface-3)'" onmouseout="this.style.background=''" onclick="exportReplay();toggleExportMenu()">Animated Replay (.html)</div>
|
|
2626
2634
|
</div>
|
|
2627
2635
|
</div>
|
|
2628
2636
|
<button class="btn btn-danger" onclick="doReset()">Reset</button>
|
|
@@ -2744,6 +2752,7 @@
|
|
|
2744
2752
|
<div class="branch-tabs" id="branch-tabs"></div>
|
|
2745
2753
|
<div class="search-bar" id="search-bar">
|
|
2746
2754
|
<input class="search-input" id="search-input" placeholder="Search messages... ( / )" oninput="onSearch()">
|
|
2755
|
+
<button id="search-all-btn" onclick="toggleSearchAll()" title="Search across all projects" style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:var(--text-muted);white-space:nowrap;transition:all 0.2s">All Projects</button>
|
|
2747
2756
|
<span class="search-count" id="search-count"></span>
|
|
2748
2757
|
<button class="compact-toggle" id="compact-toggle" onclick="toggleCompactMode()" title="Toggle compact view">Compact</button>
|
|
2749
2758
|
</div>
|
|
@@ -2778,7 +2787,7 @@
|
|
|
2778
2787
|
</div>
|
|
2779
2788
|
</div>
|
|
2780
2789
|
<div class="app-footer">
|
|
2781
|
-
<span>Let Them Talk v3.
|
|
2790
|
+
<span>Let Them Talk v3.5.0</span>
|
|
2782
2791
|
</div>
|
|
2783
2792
|
<div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
|
|
2784
2793
|
<div class="profile-popup-header">
|
|
@@ -3182,8 +3191,10 @@ function renderAgents(agents) {
|
|
|
3182
3191
|
}
|
|
3183
3192
|
|
|
3184
3193
|
function sendNudge(agentName) {
|
|
3194
|
+
var lockedProject = activeProject;
|
|
3195
|
+
var pq = lockedProject ? '?project=' + encodeURIComponent(lockedProject) : '';
|
|
3185
3196
|
var body = JSON.stringify({ to: agentName, content: 'Hey ' + agentName + ', the user is waiting for you. Please check for new messages and continue your work.' });
|
|
3186
|
-
lttFetch('/api/inject' +
|
|
3197
|
+
lttFetch('/api/inject' + pq, {
|
|
3187
3198
|
method: 'POST',
|
|
3188
3199
|
headers: { 'Content-Type': 'application/json' },
|
|
3189
3200
|
body: body
|
|
@@ -3240,8 +3251,11 @@ function doInject() {
|
|
|
3240
3251
|
var content = document.getElementById('inject-content').value.trim();
|
|
3241
3252
|
if (!target || !content) return;
|
|
3242
3253
|
|
|
3254
|
+
// Lock project context at send time — prevents race if user switches project mid-type
|
|
3255
|
+
var lockedProject = activeProject;
|
|
3256
|
+
var pq = lockedProject ? '?project=' + encodeURIComponent(lockedProject) : '';
|
|
3243
3257
|
var body = JSON.stringify({ to: target, content: content });
|
|
3244
|
-
lttFetch('/api/inject' +
|
|
3258
|
+
lttFetch('/api/inject' + pq, {
|
|
3245
3259
|
method: 'POST',
|
|
3246
3260
|
headers: { 'Content-Type': 'application/json' },
|
|
3247
3261
|
body: body
|
|
@@ -3558,6 +3572,10 @@ var searchQuery = '';
|
|
|
3558
3572
|
|
|
3559
3573
|
function onSearch() {
|
|
3560
3574
|
searchQuery = document.getElementById('search-input').value.toLowerCase().trim();
|
|
3575
|
+
if (searchAllMode && searchQuery.length >= 2) {
|
|
3576
|
+
searchAllProjects(searchQuery);
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3561
3579
|
lastMessageCount = 0;
|
|
3562
3580
|
renderMessages(cachedHistory);
|
|
3563
3581
|
}
|
|
@@ -4281,6 +4299,7 @@ function fetchStats() {
|
|
|
4281
4299
|
lttFetch('/api/stats' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4282
4300
|
renderStats(data);
|
|
4283
4301
|
}).catch(function(e) { console.error('Stats fetch failed:', e); });
|
|
4302
|
+
fetchScores();
|
|
4284
4303
|
}
|
|
4285
4304
|
|
|
4286
4305
|
function renderStats(data) {
|
|
@@ -4356,6 +4375,11 @@ function renderStats(data) {
|
|
|
4356
4375
|
}
|
|
4357
4376
|
html += '</div></div>';
|
|
4358
4377
|
|
|
4378
|
+
html += '<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:16px;margin-top:16px">' +
|
|
4379
|
+
'<h3 style="font-size:14px;font-weight:700;margin-bottom:12px;color:var(--accent)">Agent Leaderboard</h3>' +
|
|
4380
|
+
'<div id="scores-area"><div style="color:var(--text-muted);font-size:12px">Loading scores...</div></div>' +
|
|
4381
|
+
'</div>';
|
|
4382
|
+
|
|
4359
4383
|
el.innerHTML = html;
|
|
4360
4384
|
}
|
|
4361
4385
|
|
|
@@ -4686,6 +4710,163 @@ function switchBranch(name) {
|
|
|
4686
4710
|
poll();
|
|
4687
4711
|
}
|
|
4688
4712
|
|
|
4713
|
+
// ==================== v3.5: NOTIFICATIONS PANEL ====================
|
|
4714
|
+
|
|
4715
|
+
var notifData = [];
|
|
4716
|
+
var notifSeen = 0;
|
|
4717
|
+
|
|
4718
|
+
function toggleNotifPanel() {
|
|
4719
|
+
var panel = document.getElementById('notif-panel');
|
|
4720
|
+
var isOpen = panel.style.display !== 'none';
|
|
4721
|
+
panel.style.display = isOpen ? 'none' : 'block';
|
|
4722
|
+
if (!isOpen) {
|
|
4723
|
+
notifSeen = notifData.length;
|
|
4724
|
+
updateNotifBadge();
|
|
4725
|
+
fetchNotifications();
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
function fetchNotifications() {
|
|
4730
|
+
var pq = projectParam();
|
|
4731
|
+
lttFetch('/api/notifications' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4732
|
+
notifData = Array.isArray(data) ? data : [];
|
|
4733
|
+
renderNotifList();
|
|
4734
|
+
updateNotifBadge();
|
|
4735
|
+
}).catch(function() {});
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
function renderNotifList() {
|
|
4739
|
+
var el = document.getElementById('notif-list');
|
|
4740
|
+
if (!notifData.length) { el.innerHTML = '<div style="padding:16px;text-align:center;color:var(--text-muted);font-size:12px">No notifications yet</div>'; return; }
|
|
4741
|
+
var html = '';
|
|
4742
|
+
for (var i = notifData.length - 1; i >= 0; i--) {
|
|
4743
|
+
var n = notifData[i];
|
|
4744
|
+
var icon = n.type === 'online' ? '🟢' : n.type === 'offline' ? '🔴' : n.type === 'listening' ? '🔊' : '🔕';
|
|
4745
|
+
var time = new Date(n.timestamp).toLocaleTimeString();
|
|
4746
|
+
html += '<div style="padding:8px 16px;border-bottom:1px solid var(--border);font-size:12px;display:flex;gap:8px;align-items:center">' +
|
|
4747
|
+
'<span>' + icon + '</span>' +
|
|
4748
|
+
'<div style="flex:1"><div style="color:var(--text)">' + escapeHtml(n.message) + '</div><div style="color:var(--text-muted);font-size:10px">' + time + '</div></div>' +
|
|
4749
|
+
'</div>';
|
|
4750
|
+
}
|
|
4751
|
+
el.innerHTML = html;
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
function updateNotifBadge() {
|
|
4755
|
+
var badge = document.getElementById('notif-badge');
|
|
4756
|
+
var unseen = notifData.length - notifSeen;
|
|
4757
|
+
if (unseen > 0) { badge.textContent = unseen; badge.style.display = 'block'; }
|
|
4758
|
+
else { badge.style.display = 'none'; }
|
|
4759
|
+
}
|
|
4760
|
+
|
|
4761
|
+
function clearNotifications() {
|
|
4762
|
+
notifData = [];
|
|
4763
|
+
notifSeen = 0;
|
|
4764
|
+
renderNotifList();
|
|
4765
|
+
updateNotifBadge();
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4768
|
+
// Close notif panel on outside click
|
|
4769
|
+
document.addEventListener('click', function(e) {
|
|
4770
|
+
var panel = document.getElementById('notif-panel');
|
|
4771
|
+
var bell = document.getElementById('notif-bell');
|
|
4772
|
+
if (panel && panel.style.display !== 'none' && !panel.contains(e.target) && e.target !== bell && !bell.contains(e.target)) {
|
|
4773
|
+
panel.style.display = 'none';
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
|
|
4777
|
+
// ==================== v3.5: CROSS-PROJECT SEARCH ====================
|
|
4778
|
+
|
|
4779
|
+
var searchAllMode = false;
|
|
4780
|
+
|
|
4781
|
+
function toggleSearchAll() {
|
|
4782
|
+
searchAllMode = !searchAllMode;
|
|
4783
|
+
var btn = document.getElementById('search-all-btn');
|
|
4784
|
+
btn.style.background = searchAllMode ? 'var(--accent-dim)' : 'var(--surface-2)';
|
|
4785
|
+
btn.style.color = searchAllMode ? 'var(--accent)' : 'var(--text-muted)';
|
|
4786
|
+
btn.style.borderColor = searchAllMode ? 'var(--accent)' : 'var(--border)';
|
|
4787
|
+
if (searchAllMode) {
|
|
4788
|
+
document.getElementById('search-input').placeholder = 'Search ALL projects...';
|
|
4789
|
+
} else {
|
|
4790
|
+
document.getElementById('search-input').placeholder = 'Search messages... ( / )';
|
|
4791
|
+
}
|
|
4792
|
+
onSearch();
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
function searchAllProjects(query) {
|
|
4796
|
+
if (!query || query.length < 2) return;
|
|
4797
|
+
var countEl = document.getElementById('search-count');
|
|
4798
|
+
countEl.textContent = 'Searching...';
|
|
4799
|
+
lttFetch('/api/search-all?q=' + encodeURIComponent(query) + '&limit=30').then(function(r) { return r.json(); }).then(function(data) {
|
|
4800
|
+
if (data.error) { countEl.textContent = data.error; return; }
|
|
4801
|
+
countEl.textContent = data.total + ' results across ' + data.results.length + ' projects';
|
|
4802
|
+
// Render results in the messages area
|
|
4803
|
+
var area = document.getElementById('messages');
|
|
4804
|
+
var html = '';
|
|
4805
|
+
for (var i = 0; i < data.results.length; i++) {
|
|
4806
|
+
var proj = data.results[i];
|
|
4807
|
+
html += '<div class="date-sep">' + escapeHtml(proj.project) + ' (' + proj.messages.length + ')</div>';
|
|
4808
|
+
for (var j = 0; j < proj.messages.length; j++) {
|
|
4809
|
+
var m = proj.messages[j];
|
|
4810
|
+
var time = new Date(m.timestamp).toLocaleString();
|
|
4811
|
+
html += '<div class="message" style="opacity:0.85">' +
|
|
4812
|
+
'<div class="msg-body"><div class="msg-header"><span class="msg-from" style="color:var(--accent)">' + escapeHtml(m.from) + '</span>' +
|
|
4813
|
+
'<span class="msg-arrow">→</span><span class="msg-to">' + escapeHtml(m.to || 'all') + '</span>' +
|
|
4814
|
+
'<span class="msg-time">' + time + '</span></div>' +
|
|
4815
|
+
'<div class="msg-content">' + escapeHtml(m.content.substring(0, 300)) + (m.content.length > 300 ? '...' : '') + '</div></div></div>';
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
if (!html) html = '<div class="empty-state"><div class="empty-text">No results found</div></div>';
|
|
4819
|
+
area.innerHTML = html;
|
|
4820
|
+
}).catch(function() { countEl.textContent = 'Search failed'; });
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
// ==================== v3.5: REPLAY EXPORT ====================
|
|
4824
|
+
|
|
4825
|
+
function exportReplay() {
|
|
4826
|
+
var pq = projectParam();
|
|
4827
|
+
window.location.href = '/api/export-replay' + (pq || '?') + (_lttToken ? '&token=' + encodeURIComponent(_lttToken) : '');
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// ==================== v3.5: PERFORMANCE SCORES ====================
|
|
4831
|
+
|
|
4832
|
+
function fetchScores() {
|
|
4833
|
+
var pq = projectParam();
|
|
4834
|
+
lttFetch('/api/scores' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4835
|
+
renderScores(data);
|
|
4836
|
+
}).catch(function() {});
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4839
|
+
function renderScores(data) {
|
|
4840
|
+
if (!data || !data.agents) return;
|
|
4841
|
+
var el = document.getElementById('scores-area');
|
|
4842
|
+
if (!el) return;
|
|
4843
|
+
var agents = data.agents;
|
|
4844
|
+
var names = Object.keys(agents).sort(function(a, b) { return agents[b].score - agents[a].score; });
|
|
4845
|
+
if (!names.length) { el.innerHTML = '<div style="color:var(--text-muted);font-size:12px;padding:16px;text-align:center">No agent data yet</div>'; return; }
|
|
4846
|
+
|
|
4847
|
+
var html = '<div style="display:grid;gap:8px">';
|
|
4848
|
+
for (var i = 0; i < names.length; i++) {
|
|
4849
|
+
var n = names[i];
|
|
4850
|
+
var a = agents[n];
|
|
4851
|
+
var medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : '#' + (i + 1);
|
|
4852
|
+
var scoreColor = a.score >= 80 ? 'var(--green)' : a.score >= 60 ? 'var(--accent)' : a.score >= 40 ? 'var(--orange)' : 'var(--red)';
|
|
4853
|
+
html += '<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:10px;padding:12px 16px;display:flex;align-items:center;gap:12px">' +
|
|
4854
|
+
'<div style="font-size:18px;width:28px;text-align:center">' + medal + '</div>' +
|
|
4855
|
+
'<div style="flex:1;min-width:0"><div style="font-weight:600;font-size:13px">' + escapeHtml(n) + '</div>' +
|
|
4856
|
+
'<div style="display:flex;gap:12px;margin-top:4px;font-size:10px;color:var(--text-muted)">' +
|
|
4857
|
+
'<span>Resp: ' + a.responsiveness + '</span>' +
|
|
4858
|
+
'<span>Act: ' + a.activity + '</span>' +
|
|
4859
|
+
'<span>Rel: ' + a.reliability + '</span>' +
|
|
4860
|
+
'<span>Collab: ' + a.collaboration + '</span>' +
|
|
4861
|
+
'</div>' +
|
|
4862
|
+
'</div>' +
|
|
4863
|
+
'<div style="font-size:22px;font-weight:700;color:' + scoreColor + '">' + a.score + '</div>' +
|
|
4864
|
+
'</div>';
|
|
4865
|
+
}
|
|
4866
|
+
html += '</div>';
|
|
4867
|
+
el.innerHTML = html;
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4689
4870
|
// ==================== POLLING ====================
|
|
4690
4871
|
|
|
4691
4872
|
function poll() {
|
|
@@ -4737,6 +4918,7 @@ function poll() {
|
|
|
4737
4918
|
renderBookmarksSidebar();
|
|
4738
4919
|
fetchActivity();
|
|
4739
4920
|
fetchBranches();
|
|
4921
|
+
fetchNotifications();
|
|
4740
4922
|
updateTypingIndicator(cachedAgents);
|
|
4741
4923
|
if (activeView === 'tasks') fetchTasks();
|
|
4742
4924
|
if (activeView === 'workspaces') fetchWorkspaces();
|