let-them-talk 5.5.2 → 5.5.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/README.md +1 -1
- package/USAGE.md +1 -1
- package/cli.js +16 -1
- package/dashboard.js +1 -1
- package/package.json +1 -1
- package/scripts/repair-canonical-events.js +179 -0
- package/server.js +3 -3
- package/state/canonical.js +14 -0
package/README.md
CHANGED
|
@@ -310,7 +310,7 @@ The verify suite doesn't claim to cover every provider or runtime matrix, and do
|
|
|
310
310
|
- **Rate-limited** API endpoints on non-localhost requests.
|
|
311
311
|
- **No telemetry, no cloud.** Everything runs locally.
|
|
312
312
|
- **Obsidian-quality rich rendering** — GFM tables, fenced code with syntax highlighting (highlight.js), Obsidian-style callouts (`> [!NOTE]`, `> [!WARNING]`, `> [!SUMMARY]-` collapsible), Mermaid diagrams, KaTeX math, clickable image lightbox, copy-code buttons. Every shipping lib is bundled locally under `vendor/` so the dashboard works offline.
|
|
313
|
-
- **0 known vulnerabilities** in the shipped tarball as of v5.5.
|
|
313
|
+
- **0 known vulnerabilities** in the shipped tarball as of v5.5.3.
|
|
314
314
|
- **Sensitive-path blocks** on file-share: `.env`, `.pem`, `.key`, `.lan-token`, `mcp.json`, and the agent-bridge data directory cannot be shared.
|
|
315
315
|
- See [`SECURITY.md`](SECURITY.md) for the disclosure policy.
|
|
316
316
|
|
package/USAGE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- Generated from ../USAGE.md by scripts/sync-packaged-docs.js for published package consumers. -->
|
|
2
2
|
|
|
3
|
-
# Let Them Talk Usage Guide v5.5.
|
|
3
|
+
# Let Them Talk Usage Guide v5.5.3
|
|
4
4
|
|
|
5
5
|
This guide is the short operator view of the current runtime. For normative architecture details, use the docs under `docs/architecture/`.
|
|
6
6
|
|
package/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const { createCanonicalState } = require('./state/canonical');
|
|
|
9
9
|
|
|
10
10
|
function printUsage() {
|
|
11
11
|
console.log(`
|
|
12
|
-
Let Them Talk — Agent Bridge v5.5.
|
|
12
|
+
Let Them Talk — Agent Bridge v5.5.3
|
|
13
13
|
MCP message broker for inter-agent communication
|
|
14
14
|
Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
|
|
15
15
|
|
|
@@ -30,6 +30,8 @@ function printUsage() {
|
|
|
30
30
|
node .agent-bridge/launch.js reset Clear all conversation data
|
|
31
31
|
node .agent-bridge/launch.js migrate Backfill canonical event stream from legacy projections
|
|
32
32
|
node .agent-bridge/launch.js migrate --dry-run Preview what migrate would do
|
|
33
|
+
node .agent-bridge/launch.js repair Repair corrupted canonical event log (drops orphan redactions)
|
|
34
|
+
node .agent-bridge/launch.js repair --dry-run Preview what repair would do
|
|
33
35
|
|
|
34
36
|
Or via npx (re-downloads each time):
|
|
35
37
|
npx let-them-talk dashboard
|
|
@@ -879,6 +881,15 @@ function cliStatus() {
|
|
|
879
881
|
console.log('');
|
|
880
882
|
}
|
|
881
883
|
|
|
884
|
+
function cliRepairEvents() {
|
|
885
|
+
const { repairBranch: _ } = require('./scripts/repair-canonical-events');
|
|
886
|
+
// Delegate to the script's main — it reads process.argv and handles --dry-run
|
|
887
|
+
const scriptPath = path.join(__dirname, 'scripts', 'repair-canonical-events.js');
|
|
888
|
+
const args = process.argv.slice(3);
|
|
889
|
+
const child = require('child_process').spawnSync(process.execPath, [scriptPath, ...args], { stdio: 'inherit' });
|
|
890
|
+
if (child.status !== 0) process.exit(child.status || 1);
|
|
891
|
+
}
|
|
892
|
+
|
|
882
893
|
function cliMigrate() {
|
|
883
894
|
const args = process.argv.slice(3);
|
|
884
895
|
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
@@ -1143,6 +1154,10 @@ function runCli() {
|
|
|
1143
1154
|
case 'migrate-legacy':
|
|
1144
1155
|
cliMigrate();
|
|
1145
1156
|
break;
|
|
1157
|
+
case 'repair':
|
|
1158
|
+
case 'repair-events':
|
|
1159
|
+
cliRepairEvents();
|
|
1160
|
+
break;
|
|
1146
1161
|
case 'msg':
|
|
1147
1162
|
case 'message':
|
|
1148
1163
|
case 'send':
|
package/dashboard.js
CHANGED
|
@@ -3445,7 +3445,7 @@ server.listen(PORT, LAN_MODE ? '0.0.0.0' : '127.0.0.1', () => {
|
|
|
3445
3445
|
const dataDir = resolveDataDir();
|
|
3446
3446
|
const lanIP = getLanIP();
|
|
3447
3447
|
console.log('');
|
|
3448
|
-
console.log(' Let Them Talk - Agent Bridge Dashboard v5.5.
|
|
3448
|
+
console.log(' Let Them Talk - Agent Bridge Dashboard v5.5.3');
|
|
3449
3449
|
console.log(' ============================================');
|
|
3450
3450
|
console.log(' Dashboard: http://localhost:' + PORT);
|
|
3451
3451
|
if (LAN_MODE && lanIP) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Repair a corrupted canonical event log in-place.
|
|
3
|
+
//
|
|
4
|
+
// Problem this fixes:
|
|
5
|
+
// Pre-v5.5.3 Clear Messages would emit a message.redacted event for every
|
|
6
|
+
// message currently in the projection, even if that message had no
|
|
7
|
+
// corresponding message.sent event in the canonical log (e.g. legacy
|
|
8
|
+
// projection-only messages left over from a partial migration, or an
|
|
9
|
+
// earlier clear cycle). On the next rebuild/replay the redacted event
|
|
10
|
+
// fails with "cannot apply message.redacted because message X does not
|
|
11
|
+
// exist", which blocks further Clear Messages on that branch.
|
|
12
|
+
//
|
|
13
|
+
// v5.5.3 fixed the root cause in clearMessages so new redactions are
|
|
14
|
+
// gated on the presence of a message.sent ancestor. This script cleans
|
|
15
|
+
// up the orphan redactions that are already in the event log from
|
|
16
|
+
// previous versions so the branch can replay again.
|
|
17
|
+
//
|
|
18
|
+
// What it does:
|
|
19
|
+
// - For every branch under .agent-bridge/runtime/branches/<branch>/:
|
|
20
|
+
// - Reads events.jsonl
|
|
21
|
+
// - Collects the set of message IDs that have a message.sent event
|
|
22
|
+
// - Drops message.redacted and message.corrected events whose
|
|
23
|
+
// payload.message_id is not in that set (orphans)
|
|
24
|
+
// - Backs up the original events.jsonl to events.jsonl.pre-repair-<ts>
|
|
25
|
+
// - Writes the cleaned stream back
|
|
26
|
+
// - Deletes the branch's projection files (messages.jsonl, history.jsonl,
|
|
27
|
+
// dashboard-query-projection.json, events.head.json) so the runtime
|
|
28
|
+
// rebuilds them cleanly on the next read.
|
|
29
|
+
//
|
|
30
|
+
// Usage:
|
|
31
|
+
// node agent-bridge/scripts/repair-canonical-events.js [project-path]
|
|
32
|
+
// node agent-bridge/scripts/repair-canonical-events.js --dry-run [project-path]
|
|
33
|
+
//
|
|
34
|
+
// project-path defaults to the current working directory.
|
|
35
|
+
|
|
36
|
+
const fs = require('fs');
|
|
37
|
+
const path = require('path');
|
|
38
|
+
const { resolveDataDir } = require('../data-dir');
|
|
39
|
+
|
|
40
|
+
function readJsonl(file) {
|
|
41
|
+
if (!fs.existsSync(file)) return [];
|
|
42
|
+
return fs
|
|
43
|
+
.readFileSync(file, 'utf8')
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.filter((line) => line.trim())
|
|
46
|
+
.map((line) => {
|
|
47
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
48
|
+
})
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function repairBranch(branchDir, opts) {
|
|
53
|
+
const eventsFile = path.join(branchDir, 'events.jsonl');
|
|
54
|
+
if (!fs.existsSync(eventsFile)) return { skipped: true, reason: 'no events.jsonl' };
|
|
55
|
+
|
|
56
|
+
const events = readJsonl(eventsFile);
|
|
57
|
+
if (events.length === 0) return { skipped: true, reason: 'empty events.jsonl' };
|
|
58
|
+
|
|
59
|
+
const sentIds = new Set();
|
|
60
|
+
for (const ev of events) {
|
|
61
|
+
if (ev && ev.type === 'message.sent' && ev.payload && ev.payload.message && typeof ev.payload.message.id === 'string') {
|
|
62
|
+
sentIds.add(ev.payload.message.id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const kept = [];
|
|
67
|
+
const orphans = [];
|
|
68
|
+
for (const ev of events) {
|
|
69
|
+
if (ev && (ev.type === 'message.redacted' || ev.type === 'message.corrected')) {
|
|
70
|
+
const msgId = ev.payload && ev.payload.message_id;
|
|
71
|
+
if (msgId && !sentIds.has(msgId)) {
|
|
72
|
+
orphans.push(ev);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
kept.push(ev);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = {
|
|
80
|
+
branch: path.basename(branchDir),
|
|
81
|
+
total_events: events.length,
|
|
82
|
+
message_sent_events: sentIds.size,
|
|
83
|
+
orphan_redacted: orphans.filter((o) => o.type === 'message.redacted').length,
|
|
84
|
+
orphan_corrected: orphans.filter((o) => o.type === 'message.corrected').length,
|
|
85
|
+
kept_events: kept.length,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (opts.dryRun) {
|
|
89
|
+
result.dryRun = true;
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (orphans.length === 0) {
|
|
94
|
+
result.skipped = true;
|
|
95
|
+
result.reason = 'no orphan events';
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
100
|
+
const backupFile = path.join(branchDir, `events.jsonl.pre-repair-${stamp}`);
|
|
101
|
+
fs.copyFileSync(eventsFile, backupFile);
|
|
102
|
+
result.backup = backupFile;
|
|
103
|
+
|
|
104
|
+
fs.writeFileSync(eventsFile, kept.map((e) => JSON.stringify(e)).join('\n') + '\n');
|
|
105
|
+
|
|
106
|
+
// Drop cached projections + head pointer so the runtime rebuilds next read
|
|
107
|
+
for (const name of ['messages.jsonl', 'history.jsonl', 'dashboard-query-projection.json', 'events.head.json']) {
|
|
108
|
+
const p = path.join(branchDir, name);
|
|
109
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
110
|
+
}
|
|
111
|
+
result.projections_cleared = true;
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function main(argv) {
|
|
117
|
+
const args = argv.slice(2);
|
|
118
|
+
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
119
|
+
const projectArg = args.filter((a) => !a.startsWith('-'))[0] || process.cwd();
|
|
120
|
+
|
|
121
|
+
const dataDir = resolveDataDir({ cwd: projectArg });
|
|
122
|
+
const branchesDir = path.join(dataDir, 'runtime', 'branches');
|
|
123
|
+
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(' Canonical event log repair');
|
|
126
|
+
console.log(' ==========================');
|
|
127
|
+
console.log(' Project: ' + projectArg);
|
|
128
|
+
console.log(' Runtime: ' + branchesDir);
|
|
129
|
+
console.log(dryRun ? ' Mode: DRY RUN (no changes written)' : ' Mode: apply');
|
|
130
|
+
console.log('');
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(branchesDir)) {
|
|
133
|
+
console.log(' [info] No canonical runtime at this path. Nothing to repair.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const branches = fs.readdirSync(branchesDir).filter((name) => {
|
|
138
|
+
const p = path.join(branchesDir, name);
|
|
139
|
+
return fs.statSync(p).isDirectory();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (branches.length === 0) {
|
|
143
|
+
console.log(' [info] No branches present. Nothing to repair.');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const name of branches) {
|
|
148
|
+
const r = repairBranch(path.join(branchesDir, name), { dryRun });
|
|
149
|
+
console.log(` Branch "${name}":`);
|
|
150
|
+
if (r.skipped) {
|
|
151
|
+
console.log(' skipped — ' + r.reason);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(` ${r.total_events} total events, ${r.message_sent_events} sent messages`);
|
|
154
|
+
console.log(` orphan redactions: ${r.orphan_redacted}, orphan corrections: ${r.orphan_corrected}`);
|
|
155
|
+
if (!dryRun) {
|
|
156
|
+
console.log(' [ok] rewrote events.jsonl (' + r.kept_events + ' events kept)');
|
|
157
|
+
console.log(' [ok] backup: ' + r.backup);
|
|
158
|
+
console.log(' [ok] projections cleared — will rebuild on next read');
|
|
159
|
+
} else {
|
|
160
|
+
console.log(' [dry-run] would drop ' + (r.orphan_redacted + r.orphan_corrected) + ' orphan event(s) and back up the original');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log('');
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
console.log(' Done. Clear Messages should now work on the repaired branches.');
|
|
168
|
+
} else {
|
|
169
|
+
console.log(' Re-run without --dry-run to apply.');
|
|
170
|
+
}
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (require.main === module) {
|
|
175
|
+
try { main(process.argv); }
|
|
176
|
+
catch (e) { console.error(' [error] ' + (e && e.stack ? e.stack : e)); process.exit(1); }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = { repairBranch };
|
package/server.js
CHANGED
|
@@ -3708,7 +3708,7 @@ function buildListenGroupResponse(batch, consumed, agentName, listenStart) {
|
|
|
3708
3708
|
return new Date(a.timestamp) - new Date(b.timestamp);
|
|
3709
3709
|
});
|
|
3710
3710
|
|
|
3711
|
-
// LEAN RESPONSE (v5.5.
|
|
3711
|
+
// LEAN RESPONSE (v5.5.3+): the agent already has every prior message in its
|
|
3712
3712
|
// own LLM context, plus the full rule set from get_guide() + AGENTS.md. We
|
|
3713
3713
|
// only send the NEW messages + the managed-mode signals needed for
|
|
3714
3714
|
// turn-taking. No repeated reminders, no agent rosters, no "next_action"
|
|
@@ -8136,7 +8136,7 @@ function toolToggleRule(ruleId) {
|
|
|
8136
8136
|
// --- MCP Server setup ---
|
|
8137
8137
|
|
|
8138
8138
|
const server = new Server(
|
|
8139
|
-
{ name: 'agent-bridge', version: '5.5.
|
|
8139
|
+
{ name: 'agent-bridge', version: '5.5.3' },
|
|
8140
8140
|
{ capabilities: { tools: {} } }
|
|
8141
8141
|
);
|
|
8142
8142
|
|
|
@@ -9265,7 +9265,7 @@ async function main() {
|
|
|
9265
9265
|
try {
|
|
9266
9266
|
const transport = new StdioServerTransport();
|
|
9267
9267
|
await server.connect(transport);
|
|
9268
|
-
console.error('Agent Bridge MCP server v5.5.
|
|
9268
|
+
console.error('Agent Bridge MCP server v5.5.3 running (65 tools)');
|
|
9269
9269
|
} catch (e) {
|
|
9270
9270
|
console.error('ERROR: MCP server failed to start: ' + e.message);
|
|
9271
9271
|
console.error('Fix: Run "npx let-them-talk doctor" to check your setup.');
|
package/state/canonical.js
CHANGED
|
@@ -2770,8 +2770,22 @@ function createCanonicalState(options = {}) {
|
|
|
2770
2770
|
const redactedAt = params.redactedAt || new Date().toISOString();
|
|
2771
2771
|
const clearedMessageIds = [];
|
|
2772
2772
|
|
|
2773
|
+
// Only redact messages that actually have a canonical message.sent event
|
|
2774
|
+
// in this branch's log. Redacting a message that was never "sent" in
|
|
2775
|
+
// canonical terms (e.g. a legacy projection-only message left over from
|
|
2776
|
+
// pre-canonical clear cycles) would create an orphan redaction that
|
|
2777
|
+
// breaks future replay — the rebuild fails with "cannot apply
|
|
2778
|
+
// message.redacted because message X does not exist".
|
|
2779
|
+
const canonicalSentIds = new Set();
|
|
2780
|
+
for (const event of readCanonicalMessageEvents(branch)) {
|
|
2781
|
+
if (event && event.type === 'message.sent' && event.payload && event.payload.message && typeof event.payload.message.id === 'string') {
|
|
2782
|
+
canonicalSentIds.add(event.payload.message.id);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2773
2786
|
for (const message of Array.isArray(currentMessages) ? currentMessages : []) {
|
|
2774
2787
|
if (!message || typeof message.id !== 'string' || !message.id) continue;
|
|
2788
|
+
if (!canonicalSentIds.has(message.id)) continue; // skip projection-only / orphan
|
|
2775
2789
|
appendCanonicalMessageRedactedEvent({
|
|
2776
2790
|
branch,
|
|
2777
2791
|
actorAgent,
|