let-them-talk 3.3.2 → 3.4.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/CHANGELOG.md +49 -1
- package/LICENSE +2 -2
- package/README.md +213 -150
- package/SECURITY.md +1 -1
- package/cli.js +103 -2
- package/dashboard.html +868 -152
- package/dashboard.js +365 -13
- package/package.json +4 -3
- package/server.js +1 -1
package/dashboard.js
CHANGED
|
@@ -228,6 +228,74 @@ function apiStatus(query) {
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function apiStats(query) {
|
|
232
|
+
const projectPath = query.get('project') || null;
|
|
233
|
+
const history = readJsonl(filePath('history.jsonl', projectPath));
|
|
234
|
+
const agents = readJson(filePath('agents.json', projectPath));
|
|
235
|
+
|
|
236
|
+
// Per-agent stats
|
|
237
|
+
const perAgent = {};
|
|
238
|
+
let totalMessages = history.length;
|
|
239
|
+
const hourBuckets = new Array(24).fill(0);
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < history.length; i++) {
|
|
242
|
+
const m = history[i];
|
|
243
|
+
const from = m.from || 'unknown';
|
|
244
|
+
if (!perAgent[from]) {
|
|
245
|
+
perAgent[from] = { messages: 0, responseTimes: [], hours: new Array(24).fill(0) };
|
|
246
|
+
}
|
|
247
|
+
perAgent[from].messages++;
|
|
248
|
+
const ts = new Date(m.timestamp);
|
|
249
|
+
const hour = ts.getHours();
|
|
250
|
+
perAgent[from].hours[hour]++;
|
|
251
|
+
hourBuckets[hour]++;
|
|
252
|
+
|
|
253
|
+
// Compute response time if this is a reply
|
|
254
|
+
if (m.reply_to) {
|
|
255
|
+
for (let j = i - 1; j >= Math.max(0, i - 50); j--) {
|
|
256
|
+
if (history[j].id === m.reply_to) {
|
|
257
|
+
const delta = ts.getTime() - new Date(history[j].timestamp).getTime();
|
|
258
|
+
if (delta > 0 && delta < 3600000) perAgent[from].responseTimes.push(delta);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Build per-agent summary
|
|
266
|
+
const agentStats = {};
|
|
267
|
+
let busiestAgent = null;
|
|
268
|
+
let busiestCount = 0;
|
|
269
|
+
for (const [name, data] of Object.entries(perAgent)) {
|
|
270
|
+
const avgResponseMs = data.responseTimes.length
|
|
271
|
+
? Math.round(data.responseTimes.reduce((a, b) => a + b, 0) / data.responseTimes.length)
|
|
272
|
+
: null;
|
|
273
|
+
const peakHour = data.hours.indexOf(Math.max(...data.hours));
|
|
274
|
+
agentStats[name] = {
|
|
275
|
+
messages: data.messages,
|
|
276
|
+
avg_response_ms: avgResponseMs,
|
|
277
|
+
peak_hour: peakHour,
|
|
278
|
+
};
|
|
279
|
+
if (data.messages > busiestCount) {
|
|
280
|
+
busiestCount = data.messages;
|
|
281
|
+
busiestAgent = name;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Conversation velocity (messages per minute over last 10 minutes)
|
|
286
|
+
const tenMinAgo = Date.now() - 600000;
|
|
287
|
+
const recentCount = history.filter(m => new Date(m.timestamp).getTime() > tenMinAgo).length;
|
|
288
|
+
const velocity = Math.round((recentCount / 10) * 10) / 10;
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
total_messages: totalMessages,
|
|
292
|
+
busiest_agent: busiestAgent,
|
|
293
|
+
velocity_per_min: velocity,
|
|
294
|
+
hour_distribution: hourBuckets,
|
|
295
|
+
agents: agentStats,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
231
299
|
function apiReset(query) {
|
|
232
300
|
const projectPath = query.get('project') || null;
|
|
233
301
|
const dataDir = resolveDataDir(projectPath);
|
|
@@ -528,9 +596,12 @@ function apiUpdateTask(body, query) {
|
|
|
528
596
|
const task = tasks.find(t => t.id === body.task_id);
|
|
529
597
|
if (!task) return { error: 'Task not found' };
|
|
530
598
|
|
|
599
|
+
const validStatuses = ['pending', 'in_progress', 'done', 'blocked'];
|
|
600
|
+
if (!validStatuses.includes(body.status)) return { error: 'Invalid status. Must be: ' + validStatuses.join(', ') };
|
|
531
601
|
task.status = body.status;
|
|
532
602
|
task.updated_at = new Date().toISOString();
|
|
533
603
|
if (body.notes) {
|
|
604
|
+
if (!Array.isArray(task.notes)) task.notes = [];
|
|
534
605
|
task.notes.push({ by: 'Dashboard', text: body.notes, at: new Date().toISOString() });
|
|
535
606
|
}
|
|
536
607
|
|
|
@@ -544,8 +615,9 @@ function apiDiscover() {
|
|
|
544
615
|
const checked = new Set();
|
|
545
616
|
const existing = new Set(getProjects().map(p => p.path));
|
|
546
617
|
|
|
547
|
-
function scanDir(dir, depth) {
|
|
548
|
-
|
|
618
|
+
function scanDir(dir, depth, maxDepth) {
|
|
619
|
+
maxDepth = maxDepth || 3;
|
|
620
|
+
if (depth > maxDepth || checked.has(dir)) return;
|
|
549
621
|
checked.add(dir);
|
|
550
622
|
try {
|
|
551
623
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -559,17 +631,26 @@ function apiDiscover() {
|
|
|
559
631
|
if (!existing.has(projectPath)) {
|
|
560
632
|
found.push({ name: path.basename(projectPath), path: projectPath, dataDir: fullPath });
|
|
561
633
|
}
|
|
562
|
-
} else if (depth <
|
|
563
|
-
scanDir(fullPath, depth + 1);
|
|
634
|
+
} else if (depth < maxDepth) {
|
|
635
|
+
scanDir(fullPath, depth + 1, maxDepth);
|
|
564
636
|
}
|
|
565
637
|
}
|
|
566
638
|
} catch {}
|
|
567
639
|
}
|
|
568
640
|
|
|
569
|
-
// Scan from cwd, parent, and
|
|
641
|
+
// Scan from cwd, parent, home, Desktop, and common project locations
|
|
570
642
|
const cwd = process.cwd();
|
|
643
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
571
644
|
scanDir(cwd, 0);
|
|
572
645
|
scanDir(path.dirname(cwd), 1);
|
|
646
|
+
if (home) {
|
|
647
|
+
scanDir(home, 0);
|
|
648
|
+
scanDir(path.join(home, 'Desktop'), 0);
|
|
649
|
+
scanDir(path.join(home, 'Documents'), 0);
|
|
650
|
+
scanDir(path.join(home, 'Projects'), 0);
|
|
651
|
+
scanDir(path.join(home, 'Desktop', 'Claude Projects'), 0);
|
|
652
|
+
scanDir(path.join(home, 'Desktop', 'Projects'), 0);
|
|
653
|
+
}
|
|
573
654
|
|
|
574
655
|
return found;
|
|
575
656
|
}
|
|
@@ -652,6 +733,233 @@ function apiLaunchAgent(body) {
|
|
|
652
733
|
};
|
|
653
734
|
}
|
|
654
735
|
|
|
736
|
+
// --- v3.4: Message Edit ---
|
|
737
|
+
function apiEditMessage(body, query) {
|
|
738
|
+
const projectPath = query.get('project') || null;
|
|
739
|
+
const { id, content } = body;
|
|
740
|
+
if (!id || !content) return { error: 'Missing "id" and/or "content" fields' };
|
|
741
|
+
if (content.length > 50000) return { error: 'Content too long (max 50000 chars)' };
|
|
742
|
+
|
|
743
|
+
const dataDir = resolveDataDir(projectPath);
|
|
744
|
+
const historyFile = path.join(dataDir, 'history.jsonl');
|
|
745
|
+
const messagesFile = path.join(dataDir, 'messages.jsonl');
|
|
746
|
+
|
|
747
|
+
let found = false;
|
|
748
|
+
const now = new Date().toISOString();
|
|
749
|
+
|
|
750
|
+
// Update in history.jsonl
|
|
751
|
+
if (fs.existsSync(historyFile)) {
|
|
752
|
+
const lines = fs.readFileSync(historyFile, 'utf8').trim().split('\n').filter(Boolean);
|
|
753
|
+
const updated = lines.map(line => {
|
|
754
|
+
try {
|
|
755
|
+
const msg = JSON.parse(line);
|
|
756
|
+
if (msg.id === id) {
|
|
757
|
+
found = true;
|
|
758
|
+
if (!msg.edit_history) msg.edit_history = [];
|
|
759
|
+
msg.edit_history.push({ content: msg.content, edited_at: now });
|
|
760
|
+
msg.content = content;
|
|
761
|
+
msg.edited = true;
|
|
762
|
+
msg.edited_at = now;
|
|
763
|
+
return JSON.stringify(msg);
|
|
764
|
+
}
|
|
765
|
+
return line;
|
|
766
|
+
} catch { return line; }
|
|
767
|
+
});
|
|
768
|
+
if (found) fs.writeFileSync(historyFile, updated.join('\n') + '\n');
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Also update in messages.jsonl (for agents that haven't consumed yet)
|
|
772
|
+
if (found && fs.existsSync(messagesFile)) {
|
|
773
|
+
const raw = fs.readFileSync(messagesFile, 'utf8').trim();
|
|
774
|
+
if (raw) {
|
|
775
|
+
const lines = raw.split('\n');
|
|
776
|
+
const updated = lines.map(line => {
|
|
777
|
+
try {
|
|
778
|
+
const msg = JSON.parse(line);
|
|
779
|
+
if (msg.id === id) {
|
|
780
|
+
msg.content = content;
|
|
781
|
+
msg.edited = true;
|
|
782
|
+
msg.edited_at = now;
|
|
783
|
+
return JSON.stringify(msg);
|
|
784
|
+
}
|
|
785
|
+
return line;
|
|
786
|
+
} catch { return line; }
|
|
787
|
+
});
|
|
788
|
+
fs.writeFileSync(messagesFile, updated.join('\n') + '\n');
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (!found) return { error: 'Message not found' };
|
|
793
|
+
return { success: true, id, edited_at: now };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// --- v3.4: Message Delete ---
|
|
797
|
+
function apiDeleteMessage(body, query) {
|
|
798
|
+
const projectPath = query.get('project') || null;
|
|
799
|
+
const { id } = body;
|
|
800
|
+
if (!id) return { error: 'Missing "id" field' };
|
|
801
|
+
|
|
802
|
+
const dataDir = resolveDataDir(projectPath);
|
|
803
|
+
const historyFile = path.join(dataDir, 'history.jsonl');
|
|
804
|
+
const messagesFile = path.join(dataDir, 'messages.jsonl');
|
|
805
|
+
|
|
806
|
+
let found = false;
|
|
807
|
+
let msgFrom = null;
|
|
808
|
+
|
|
809
|
+
// Find the message first to check permissions
|
|
810
|
+
if (fs.existsSync(historyFile)) {
|
|
811
|
+
const lines = fs.readFileSync(historyFile, 'utf8').trim().split('\n');
|
|
812
|
+
for (const line of lines) {
|
|
813
|
+
try {
|
|
814
|
+
const msg = JSON.parse(line);
|
|
815
|
+
if (msg.id === id) { found = true; msgFrom = msg.from; break; }
|
|
816
|
+
} catch {}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (!found) return { error: 'Message not found' };
|
|
821
|
+
|
|
822
|
+
// Only allow deleting dashboard-injected or system messages
|
|
823
|
+
const allowed = ['Dashboard', 'dashboard', 'system', '__system__'];
|
|
824
|
+
if (!allowed.includes(msgFrom)) {
|
|
825
|
+
return { error: 'Can only delete messages sent from Dashboard or system' };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Remove from history.jsonl
|
|
829
|
+
if (fs.existsSync(historyFile)) {
|
|
830
|
+
const lines = fs.readFileSync(historyFile, 'utf8').trim().split('\n');
|
|
831
|
+
const filtered = lines.filter(line => {
|
|
832
|
+
try { return JSON.parse(line).id !== id; } catch { return true; }
|
|
833
|
+
});
|
|
834
|
+
fs.writeFileSync(historyFile, filtered.join('\n') + (filtered.length ? '\n' : ''));
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Remove from messages.jsonl
|
|
838
|
+
if (fs.existsSync(messagesFile)) {
|
|
839
|
+
const lines = fs.readFileSync(messagesFile, 'utf8').trim().split('\n');
|
|
840
|
+
const filtered = lines.filter(line => {
|
|
841
|
+
try { return JSON.parse(line).id !== id; } catch { return true; }
|
|
842
|
+
});
|
|
843
|
+
fs.writeFileSync(messagesFile, filtered.join('\n') + (filtered.length ? '\n' : ''));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return { success: true, id };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// --- v3.4: Conversation Templates ---
|
|
850
|
+
function apiGetConversationTemplates() {
|
|
851
|
+
const templatesDir = path.join(__dirname, 'conversation-templates');
|
|
852
|
+
if (!fs.existsSync(templatesDir)) {
|
|
853
|
+
// Return built-in templates
|
|
854
|
+
return getBuiltInConversationTemplates();
|
|
855
|
+
}
|
|
856
|
+
const custom = fs.readdirSync(templatesDir)
|
|
857
|
+
.filter(f => f.endsWith('.json'))
|
|
858
|
+
.map(f => { try { return JSON.parse(fs.readFileSync(path.join(templatesDir, f), 'utf8')); } catch { return null; } })
|
|
859
|
+
.filter(Boolean);
|
|
860
|
+
return [...getBuiltInConversationTemplates(), ...custom];
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function getBuiltInConversationTemplates() {
|
|
864
|
+
return [
|
|
865
|
+
{
|
|
866
|
+
id: 'code-review',
|
|
867
|
+
name: 'Code Review Pipeline',
|
|
868
|
+
description: 'Developer writes code, Reviewer checks it, Tester validates',
|
|
869
|
+
agents: [
|
|
870
|
+
{ name: 'Developer', role: 'Developer', prompt: 'You are a developer. Write code as instructed. After completing, send your code to Reviewer for review.' },
|
|
871
|
+
{ 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.' },
|
|
872
|
+
{ 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.' }
|
|
873
|
+
],
|
|
874
|
+
workflow: { name: 'Code Review', steps: ['Write Code', 'Review', 'Test', 'Approve'] }
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
id: 'debug-squad',
|
|
878
|
+
name: 'Debug Squad',
|
|
879
|
+
description: 'Investigator finds the bug, Fixer patches it, Verifier confirms the fix',
|
|
880
|
+
agents: [
|
|
881
|
+
{ name: 'Investigator', role: 'Bug Investigator', prompt: 'You investigate bugs. Analyze error logs, trace code paths, and identify root causes. Send findings to Fixer.' },
|
|
882
|
+
{ name: 'Fixer', role: 'Bug Fixer', prompt: 'You fix bugs. Wait for findings from Investigator. Implement fixes and send to Verifier for confirmation.' },
|
|
883
|
+
{ 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.' }
|
|
884
|
+
],
|
|
885
|
+
workflow: { name: 'Bug Fix', steps: ['Investigate', 'Fix', 'Verify', 'Close'] }
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
id: 'feature-build',
|
|
889
|
+
name: 'Feature Development',
|
|
890
|
+
description: 'Architect designs, Builder implements, Reviewer approves',
|
|
891
|
+
agents: [
|
|
892
|
+
{ 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.' },
|
|
893
|
+
{ 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.' },
|
|
894
|
+
{ name: 'Reviewer', role: 'Senior Reviewer', prompt: 'You are a senior reviewer. Review implementations from Builder against the architecture from Architect. Approve or request changes.' }
|
|
895
|
+
],
|
|
896
|
+
workflow: { name: 'Feature Dev', steps: ['Design', 'Implement', 'Review', 'Ship'] }
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
id: 'research-write',
|
|
900
|
+
name: 'Research & Write',
|
|
901
|
+
description: 'Researcher gathers info, Writer creates content, Editor polishes',
|
|
902
|
+
agents: [
|
|
903
|
+
{ name: 'Researcher', role: 'Researcher', prompt: 'You are a researcher. Gather information on the given topic. Organize findings and send a research brief to Writer.' },
|
|
904
|
+
{ 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.' },
|
|
905
|
+
{ 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.' }
|
|
906
|
+
],
|
|
907
|
+
workflow: { name: 'Content Pipeline', steps: ['Research', 'Draft', 'Edit', 'Publish'] }
|
|
908
|
+
}
|
|
909
|
+
];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function apiLaunchConversationTemplate(body, query) {
|
|
913
|
+
const projectPath = query.get('project') || null;
|
|
914
|
+
const { template_id } = body;
|
|
915
|
+
if (!template_id) return { error: 'Missing template_id' };
|
|
916
|
+
|
|
917
|
+
const templates = apiGetConversationTemplates();
|
|
918
|
+
const template = templates.find(t => t.id === template_id);
|
|
919
|
+
if (!template) return { error: 'Template not found: ' + template_id };
|
|
920
|
+
|
|
921
|
+
// Return the template config for the frontend to display launch instructions
|
|
922
|
+
return {
|
|
923
|
+
success: true,
|
|
924
|
+
template,
|
|
925
|
+
instructions: template.agents.map(a => ({
|
|
926
|
+
agent_name: a.name,
|
|
927
|
+
role: a.role,
|
|
928
|
+
prompt: `You are "${a.name}" with role "${a.role}". ${a.prompt}\n\nFirst register yourself with: register(name="${a.name}"), then update_profile(role="${a.role}"). Then enter listen mode.`
|
|
929
|
+
}))
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// --- v3.4: Agent Permissions ---
|
|
934
|
+
function apiUpdatePermissions(body, query) {
|
|
935
|
+
const projectPath = query.get('project') || null;
|
|
936
|
+
const dataDir = resolveDataDir(projectPath);
|
|
937
|
+
const permFile = path.join(dataDir, 'permissions.json');
|
|
938
|
+
|
|
939
|
+
const { agent, permissions } = body;
|
|
940
|
+
if (!agent || !permissions) return { error: 'Missing "agent" and/or "permissions" fields' };
|
|
941
|
+
|
|
942
|
+
let perms = {};
|
|
943
|
+
if (fs.existsSync(permFile)) {
|
|
944
|
+
try { perms = JSON.parse(fs.readFileSync(permFile, 'utf8')); } catch {}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// permissions: { can_read: [agents...] or "*", can_write_to: [agents...] or "*", is_admin: bool }
|
|
948
|
+
const allowed = {};
|
|
949
|
+
if (permissions.can_read !== undefined) allowed.can_read = permissions.can_read;
|
|
950
|
+
if (permissions.can_write_to !== undefined) allowed.can_write_to = permissions.can_write_to;
|
|
951
|
+
if (permissions.is_admin !== undefined) allowed.is_admin = !!permissions.is_admin;
|
|
952
|
+
perms[agent] = {
|
|
953
|
+
...perms[agent],
|
|
954
|
+
...allowed,
|
|
955
|
+
updated_at: new Date().toISOString()
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
959
|
+
fs.writeFileSync(permFile, JSON.stringify(perms, null, 2));
|
|
960
|
+
return { success: true, agent, permissions: perms[agent] };
|
|
961
|
+
}
|
|
962
|
+
|
|
655
963
|
// --- HTTP Server ---
|
|
656
964
|
|
|
657
965
|
// Load HTML at startup (re-read on each request in dev for hot-reload)
|
|
@@ -686,7 +994,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
686
994
|
} else if (reqOrigin === allowedOrigin || reqOrigin === `http://127.0.0.1:${PORT}`) {
|
|
687
995
|
res.setHeader('Access-Control-Allow-Origin', reqOrigin);
|
|
688
996
|
}
|
|
689
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
997
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
690
998
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
691
999
|
|
|
692
1000
|
if (req.method === 'OPTIONS') {
|
|
@@ -696,7 +1004,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
696
1004
|
}
|
|
697
1005
|
|
|
698
1006
|
// CSRF + DNS rebinding protection: validate Host and Origin on mutating requests
|
|
699
|
-
if (req.method === 'POST' || req.method === 'DELETE') {
|
|
1007
|
+
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE') {
|
|
700
1008
|
// Check Host header to block DNS rebinding attacks
|
|
701
1009
|
const host = (req.headers.host || '').replace(/:\d+$/, '');
|
|
702
1010
|
const validHosts = ['localhost', '127.0.0.1'];
|
|
@@ -741,12 +1049,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
741
1049
|
return;
|
|
742
1050
|
}
|
|
743
1051
|
|
|
744
|
-
// Serve dashboard HTML (re-read
|
|
1052
|
+
// Serve dashboard HTML (always re-read for hot reload)
|
|
745
1053
|
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
746
|
-
const html =
|
|
747
|
-
|
|
748
|
-
:
|
|
749
|
-
|
|
1054
|
+
const html = fs.readFileSync(HTML_FILE, 'utf8');
|
|
1055
|
+
res.writeHead(200, {
|
|
1056
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
1057
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
1058
|
+
'Pragma': 'no-cache',
|
|
1059
|
+
'Expires': '0'
|
|
1060
|
+
});
|
|
750
1061
|
res.end(html);
|
|
751
1062
|
}
|
|
752
1063
|
// Existing APIs (now with ?project= param support)
|
|
@@ -762,6 +1073,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
762
1073
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
763
1074
|
res.end(JSON.stringify(apiStatus(url.searchParams)));
|
|
764
1075
|
}
|
|
1076
|
+
else if (url.pathname === '/api/stats' && req.method === 'GET') {
|
|
1077
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1078
|
+
res.end(JSON.stringify(apiStats(url.searchParams)));
|
|
1079
|
+
}
|
|
765
1080
|
else if (url.pathname === '/api/reset' && req.method === 'POST') {
|
|
766
1081
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
767
1082
|
res.end(JSON.stringify(apiReset(url.searchParams)));
|
|
@@ -940,6 +1255,43 @@ const server = http.createServer(async (req, res) => {
|
|
|
940
1255
|
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
941
1256
|
res.end(JSON.stringify(result));
|
|
942
1257
|
}
|
|
1258
|
+
// --- v3.4: Message Edit ---
|
|
1259
|
+
else if (url.pathname === '/api/message' && req.method === 'PUT') {
|
|
1260
|
+
const body = await parseBody(req);
|
|
1261
|
+
const result = apiEditMessage(body, url.searchParams);
|
|
1262
|
+
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
1263
|
+
res.end(JSON.stringify(result));
|
|
1264
|
+
}
|
|
1265
|
+
// --- v3.4: Message Delete ---
|
|
1266
|
+
else if (url.pathname === '/api/message' && req.method === 'DELETE') {
|
|
1267
|
+
const body = await parseBody(req);
|
|
1268
|
+
const result = apiDeleteMessage(body, url.searchParams);
|
|
1269
|
+
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
1270
|
+
res.end(JSON.stringify(result));
|
|
1271
|
+
}
|
|
1272
|
+
// --- v3.4: Conversation Templates ---
|
|
1273
|
+
else if (url.pathname === '/api/conversation-templates' && req.method === 'GET') {
|
|
1274
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1275
|
+
res.end(JSON.stringify(apiGetConversationTemplates()));
|
|
1276
|
+
}
|
|
1277
|
+
else if (url.pathname === '/api/conversation-templates/launch' && req.method === 'POST') {
|
|
1278
|
+
const body = await parseBody(req);
|
|
1279
|
+
const result = apiLaunchConversationTemplate(body, url.searchParams);
|
|
1280
|
+
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
1281
|
+
res.end(JSON.stringify(result));
|
|
1282
|
+
}
|
|
1283
|
+
// --- v3.4: Agent Permissions ---
|
|
1284
|
+
else if (url.pathname === '/api/permissions' && req.method === 'GET') {
|
|
1285
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
1286
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1287
|
+
res.end(JSON.stringify(readJson(filePath('permissions.json', projectPath))));
|
|
1288
|
+
}
|
|
1289
|
+
else if (url.pathname === '/api/permissions' && req.method === 'POST') {
|
|
1290
|
+
const body = await parseBody(req);
|
|
1291
|
+
const result = apiUpdatePermissions(body, url.searchParams);
|
|
1292
|
+
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
1293
|
+
res.end(JSON.stringify(result));
|
|
1294
|
+
}
|
|
943
1295
|
// Server info (LAN mode detection for frontend)
|
|
944
1296
|
else if (url.pathname === '/api/server-info' && req.method === 'GET') {
|
|
945
1297
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1068,7 +1420,7 @@ server.listen(PORT, LAN_MODE ? '0.0.0.0' : '127.0.0.1', () => {
|
|
|
1068
1420
|
const dataDir = resolveDataDir();
|
|
1069
1421
|
const lanIP = getLanIP();
|
|
1070
1422
|
console.log('');
|
|
1071
|
-
console.log(' Let Them Talk - Agent Bridge Dashboard v3.
|
|
1423
|
+
console.log(' Let Them Talk - Agent Bridge Dashboard v3.4.0');
|
|
1072
1424
|
console.log(' ============================================');
|
|
1073
1425
|
console.log(' Dashboard: http://localhost:' + PORT);
|
|
1074
1426
|
if (LAN_MODE && lanIP) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "let-them-talk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.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": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dashboard.html",
|
|
22
22
|
"cli.js",
|
|
23
23
|
"templates/",
|
|
24
|
+
"conversation-templates/",
|
|
24
25
|
"logo.png",
|
|
25
26
|
"LICENSE",
|
|
26
27
|
"SECURITY.md",
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"bugs": {
|
|
48
49
|
"url": "https://github.com/Dekelelz/let-them-talk/issues"
|
|
49
50
|
},
|
|
50
|
-
"author": "Dekelelz",
|
|
51
|
-
"license": "
|
|
51
|
+
"author": "Dekelelz <contact@talk.unrealai.studio>",
|
|
52
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"@modelcontextprotocol/sdk": "1.27.1"
|
|
54
55
|
}
|
package/server.js
CHANGED
|
@@ -2021,7 +2021,7 @@ async function main() {
|
|
|
2021
2021
|
loadPlugins();
|
|
2022
2022
|
const transport = new StdioServerTransport();
|
|
2023
2023
|
await server.connect(transport);
|
|
2024
|
-
console.error('Agent Bridge MCP server v3.
|
|
2024
|
+
console.error('Agent Bridge MCP server v3.4.0 running (' + (27 + loadedPlugins.length) + ' tools)');
|
|
2025
2025
|
}
|
|
2026
2026
|
|
|
2027
2027
|
main().catch(console.error);
|