let-them-talk 4.0.2 → 4.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/CHANGELOG.md +44 -0
- package/cli.js +1 -1
- package/dashboard.html +304 -13
- package/dashboard.js +157 -3
- package/package.json +1 -1
- package/server.js +512 -83
package/dashboard.js
CHANGED
|
@@ -186,7 +186,22 @@ function apiHistory(query) {
|
|
|
186
186
|
const histFile = branch && branch !== 'main'
|
|
187
187
|
? filePath(`branch-${branch}-history.jsonl`, projectPath)
|
|
188
188
|
: filePath('history.jsonl', projectPath);
|
|
189
|
-
|
|
189
|
+
let history = readJsonl(histFile);
|
|
190
|
+
|
|
191
|
+
// Merge channel-specific history files
|
|
192
|
+
const dataDir = resolveDataDir(projectPath);
|
|
193
|
+
try {
|
|
194
|
+
const files = fs.readdirSync(dataDir);
|
|
195
|
+
for (const f of files) {
|
|
196
|
+
if (f.startsWith('channel-') && f.endsWith('-history.jsonl') && f !== 'channel-general-history.jsonl') {
|
|
197
|
+
const channelHistory = readJsonl(path.join(dataDir, f));
|
|
198
|
+
history = history.concat(channelHistory);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch {}
|
|
202
|
+
// Sort merged messages by timestamp
|
|
203
|
+
history.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
204
|
+
|
|
190
205
|
const acks = readJson(filePath('acks.json', projectPath));
|
|
191
206
|
const limit = parseInt(query.get('limit') || '500', 10);
|
|
192
207
|
const threadId = query.get('thread_id');
|
|
@@ -200,6 +215,29 @@ function apiHistory(query) {
|
|
|
200
215
|
return messages;
|
|
201
216
|
}
|
|
202
217
|
|
|
218
|
+
function apiChannels(query) {
|
|
219
|
+
const projectPath = query.get('project') || null;
|
|
220
|
+
const channelsFile = filePath('channels.json', projectPath);
|
|
221
|
+
const channels = readJson(channelsFile);
|
|
222
|
+
if (!channels) return { general: { description: 'General channel', members: ['*'], message_count: 0 } };
|
|
223
|
+
const dataDir = resolveDataDir(projectPath);
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const [name, ch] of Object.entries(channels)) {
|
|
226
|
+
let msgCount = 0;
|
|
227
|
+
const msgFile = name === 'general'
|
|
228
|
+
? filePath('history.jsonl', projectPath)
|
|
229
|
+
: path.join(dataDir, 'channel-' + name + '-history.jsonl');
|
|
230
|
+
try {
|
|
231
|
+
if (fs.existsSync(msgFile)) {
|
|
232
|
+
const content = fs.readFileSync(msgFile, 'utf8').trim();
|
|
233
|
+
if (content) msgCount = content.split('\n').length;
|
|
234
|
+
}
|
|
235
|
+
} catch {}
|
|
236
|
+
result[name] = { description: ch.description || '', members: ch.members, message_count: msgCount };
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
203
241
|
function apiAgents(query) {
|
|
204
242
|
const projectPath = query.get('project') || null;
|
|
205
243
|
const agents = readJson(filePath('agents.json', projectPath));
|
|
@@ -236,6 +274,14 @@ function apiAgents(query) {
|
|
|
236
274
|
bio: profile.bio || '',
|
|
237
275
|
appearance: profile.appearance || {},
|
|
238
276
|
};
|
|
277
|
+
// Include workspace status for agent intent board
|
|
278
|
+
try {
|
|
279
|
+
const wsPath = path.join(resolveDataDir(projectPath), 'workspaces', name + '.json');
|
|
280
|
+
if (fs.existsSync(wsPath)) {
|
|
281
|
+
const ws = JSON.parse(fs.readFileSync(wsPath, 'utf8'));
|
|
282
|
+
if (ws._status) result[name].current_status = ws._status;
|
|
283
|
+
}
|
|
284
|
+
} catch {}
|
|
239
285
|
}
|
|
240
286
|
return result;
|
|
241
287
|
}
|
|
@@ -1388,11 +1434,35 @@ const server = http.createServer(async (req, res) => {
|
|
|
1388
1434
|
if (libPath.includes('..') || libPath.includes('\\')) {
|
|
1389
1435
|
res.writeHead(400); res.end('Bad path'); return;
|
|
1390
1436
|
}
|
|
1391
|
-
// Search multiple node_modules locations (handles npx, local dev, monorepo)
|
|
1437
|
+
// Search multiple node_modules locations (handles npx, local dev, monorepo, global)
|
|
1392
1438
|
const searchPaths = [
|
|
1393
|
-
path.join(__dirname, 'node_modules', libPath), // inside
|
|
1439
|
+
path.join(__dirname, 'node_modules', libPath), // inside package (nested deps)
|
|
1394
1440
|
path.join(__dirname, '..', 'node_modules', libPath), // repo root (local dev)
|
|
1441
|
+
path.join(__dirname, '..', libPath), // npx sibling packages (three/ is next to let-them-talk/)
|
|
1395
1442
|
];
|
|
1443
|
+
// Also try require.resolve for robust npm path resolution (works with hoisted deps, npx cache, etc.)
|
|
1444
|
+
// Note: use require.resolve(pkg) not require.resolve(pkg/package.json) — modern packages
|
|
1445
|
+
// with "exports" fields block resolving package.json directly (ERR_PACKAGE_PATH_NOT_EXPORTED)
|
|
1446
|
+
try {
|
|
1447
|
+
const parts = libPath.split('/');
|
|
1448
|
+
const pkgName = parts[0];
|
|
1449
|
+
const subPath = parts.slice(1).join('/');
|
|
1450
|
+
// Try resolving the package's main entry, then navigate to subPath
|
|
1451
|
+
const resolved = require.resolve(pkgName);
|
|
1452
|
+
const pkgDir = path.dirname(resolved);
|
|
1453
|
+
// Walk up from the resolved entry to the package root (handle nested build/ dirs)
|
|
1454
|
+
let pkgRoot = pkgDir;
|
|
1455
|
+
while (pkgRoot !== path.dirname(pkgRoot)) {
|
|
1456
|
+
if (fs.existsSync(path.join(pkgRoot, 'package.json'))) {
|
|
1457
|
+
try {
|
|
1458
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
1459
|
+
if (pkg.name === pkgName) break;
|
|
1460
|
+
} catch {}
|
|
1461
|
+
}
|
|
1462
|
+
pkgRoot = path.dirname(pkgRoot);
|
|
1463
|
+
}
|
|
1464
|
+
searchPaths.push(path.join(pkgRoot, subPath));
|
|
1465
|
+
} catch {}
|
|
1396
1466
|
const filePath = searchPaths.find(p => fs.existsSync(p));
|
|
1397
1467
|
if (filePath) {
|
|
1398
1468
|
const ext = path.extname(filePath);
|
|
@@ -1468,6 +1538,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
1468
1538
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1469
1539
|
res.end(JSON.stringify(apiAgents(url.searchParams)));
|
|
1470
1540
|
}
|
|
1541
|
+
else if (url.pathname === '/api/channels' && req.method === 'GET') {
|
|
1542
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1543
|
+
res.end(JSON.stringify(apiChannels(url.searchParams)));
|
|
1544
|
+
}
|
|
1545
|
+
else if (url.pathname === '/api/decisions' && req.method === 'GET') {
|
|
1546
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
1547
|
+
const decisions = readJson(filePath('decisions.json', projectPath));
|
|
1548
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1549
|
+
res.end(JSON.stringify(decisions || []));
|
|
1550
|
+
}
|
|
1471
1551
|
else if (url.pathname === '/api/agents' && req.method === 'DELETE') {
|
|
1472
1552
|
const body = await parseBody(req);
|
|
1473
1553
|
if (!body.name) {
|
|
@@ -1548,6 +1628,80 @@ const server = http.createServer(async (req, res) => {
|
|
|
1548
1628
|
res.writeHead(result.error ? 400 : 200, { 'Content-Type': 'application/json' });
|
|
1549
1629
|
res.end(JSON.stringify(result));
|
|
1550
1630
|
}
|
|
1631
|
+
else if (url.pathname === '/api/search' && req.method === 'GET') {
|
|
1632
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
1633
|
+
const query = (url.searchParams.get('q') || '').trim();
|
|
1634
|
+
const from = url.searchParams.get('from') || null;
|
|
1635
|
+
const limit = Math.min(Math.max(1, parseInt(url.searchParams.get('limit') || '50', 10)), 100);
|
|
1636
|
+
if (query.length < 2) {
|
|
1637
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1638
|
+
res.end(JSON.stringify({ error: 'Query must be at least 2 characters' }));
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
// Search general history + all channel histories
|
|
1642
|
+
let allHistory = readJsonl(filePath('history.jsonl', projectPath));
|
|
1643
|
+
const dataDir = resolveDataDir(projectPath);
|
|
1644
|
+
try {
|
|
1645
|
+
const files = fs.readdirSync(dataDir);
|
|
1646
|
+
for (const f of files) {
|
|
1647
|
+
if (f.startsWith('channel-') && f.endsWith('-history.jsonl')) {
|
|
1648
|
+
allHistory = allHistory.concat(readJsonl(path.join(dataDir, f)));
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
} catch {}
|
|
1652
|
+
const queryLower = query.toLowerCase();
|
|
1653
|
+
const results = [];
|
|
1654
|
+
for (let i = allHistory.length - 1; i >= 0 && results.length < limit; i--) {
|
|
1655
|
+
const m = allHistory[i];
|
|
1656
|
+
if (from && m.from !== from) continue;
|
|
1657
|
+
if (m.content && m.content.toLowerCase().includes(queryLower)) {
|
|
1658
|
+
results.push({
|
|
1659
|
+
id: m.id, from: m.from, to: m.to,
|
|
1660
|
+
preview: m.content.substring(0, 200),
|
|
1661
|
+
timestamp: m.timestamp,
|
|
1662
|
+
...(m.channel && { channel: m.channel }),
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1667
|
+
res.end(JSON.stringify({ query, results_count: results.length, results }));
|
|
1668
|
+
}
|
|
1669
|
+
else if (url.pathname === '/api/export-json' && req.method === 'GET') {
|
|
1670
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
1671
|
+
const history = apiHistory(url.searchParams);
|
|
1672
|
+
const agents = apiAgents(url.searchParams);
|
|
1673
|
+
const decisions = readJson(filePath('decisions.json', projectPath)) || [];
|
|
1674
|
+
const tasks = readJson(filePath('tasks.json', projectPath)) || [];
|
|
1675
|
+
const channels = apiChannels(url.searchParams);
|
|
1676
|
+
const pkg = readJson(path.join(__dirname, 'package.json')) || {};
|
|
1677
|
+
const result = {
|
|
1678
|
+
export_version: 1,
|
|
1679
|
+
exported_at: new Date().toISOString(),
|
|
1680
|
+
project: projectPath || process.cwd(),
|
|
1681
|
+
version: pkg.version || 'unknown',
|
|
1682
|
+
summary: {
|
|
1683
|
+
message_count: history.length,
|
|
1684
|
+
agent_count: Object.keys(agents).length,
|
|
1685
|
+
decision_count: decisions.length,
|
|
1686
|
+
task_count: tasks.length,
|
|
1687
|
+
channel_count: Object.keys(channels).length,
|
|
1688
|
+
time_range: history.length > 0 ? {
|
|
1689
|
+
start: history[0].timestamp,
|
|
1690
|
+
end: history[history.length - 1].timestamp,
|
|
1691
|
+
} : null,
|
|
1692
|
+
},
|
|
1693
|
+
agents,
|
|
1694
|
+
channels,
|
|
1695
|
+
decisions,
|
|
1696
|
+
tasks,
|
|
1697
|
+
messages: history,
|
|
1698
|
+
};
|
|
1699
|
+
res.writeHead(200, {
|
|
1700
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
1701
|
+
'Content-Disposition': 'attachment; filename="conversation-' + new Date().toISOString().slice(0, 10) + '-full.json"',
|
|
1702
|
+
});
|
|
1703
|
+
res.end(JSON.stringify(result, null, 2));
|
|
1704
|
+
}
|
|
1551
1705
|
else if (url.pathname === '/api/export' && req.method === 'GET') {
|
|
1552
1706
|
const html = apiExportHtml(url.searchParams);
|
|
1553
1707
|
res.writeHead(200, {
|