let-them-talk 5.3.0 → 5.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 +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7216
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
package/cli.js
CHANGED
|
@@ -4,30 +4,37 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
const
|
|
7
|
+
const { resolveDataDir: resolveSharedDataDir } = require('./data-dir');
|
|
8
|
+
const { createCanonicalState } = require('./state/canonical');
|
|
9
9
|
|
|
10
10
|
function printUsage() {
|
|
11
11
|
console.log(`
|
|
12
|
-
Let Them Talk — Agent Bridge v5.
|
|
12
|
+
Let Them Talk — Agent Bridge v5.4.0
|
|
13
13
|
MCP message broker for inter-agent communication
|
|
14
14
|
Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Setup (one-time):
|
|
17
17
|
npx let-them-talk init Auto-detect CLI and configure MCP
|
|
18
18
|
npx let-them-talk init --claude Configure for Claude Code
|
|
19
19
|
npx let-them-talk init --gemini Configure for Gemini CLI
|
|
20
20
|
npx let-them-talk init --codex Configure for Codex CLI
|
|
21
21
|
npx let-them-talk init --all Configure for all supported CLIs
|
|
22
|
-
npx let-them-talk init --ollama
|
|
23
|
-
npx let-them-talk init --template
|
|
22
|
+
npx let-them-talk init --ollama Setup Ollama agent bridge (local LLM)
|
|
23
|
+
npx let-them-talk init --template <name> Initialize and print an agent template
|
|
24
|
+
|
|
25
|
+
After init, use the local launcher (no re-download):
|
|
26
|
+
node .agent-bridge/launch.js Dashboard (http://localhost:3000)
|
|
27
|
+
node .agent-bridge/launch.js --lan Dashboard on LAN (phone/tablet)
|
|
28
|
+
node .agent-bridge/launch.js status Show active agents and message count
|
|
29
|
+
node .agent-bridge/launch.js msg <agent> <text> Send a message to an agent
|
|
30
|
+
node .agent-bridge/launch.js reset Clear all conversation data
|
|
31
|
+
node .agent-bridge/launch.js migrate Backfill canonical event stream from legacy projections
|
|
32
|
+
node .agent-bridge/launch.js migrate --dry-run Preview what migrate would do
|
|
33
|
+
|
|
34
|
+
Or via npx (re-downloads each time):
|
|
35
|
+
npx let-them-talk dashboard
|
|
36
|
+
npx let-them-talk status
|
|
24
37
|
npx let-them-talk templates List available agent templates
|
|
25
|
-
npx let-them-talk dashboard Launch the web dashboard (http://localhost:3000)
|
|
26
|
-
npx let-them-talk dashboard --lan Launch dashboard accessible on LAN (phone/tablet)
|
|
27
|
-
npx let-them-talk reset Clear all conversation data
|
|
28
|
-
npx let-them-talk msg <agent> <text> Send a message to an agent
|
|
29
|
-
npx let-them-talk run "prompt" [--agents N] [--timeout M] Autonomous execution with N agents, auto-stop after M minutes
|
|
30
|
-
npx let-them-talk status Show active agents and message count
|
|
31
38
|
npx let-them-talk uninstall Remove agent-bridge from all CLI configs
|
|
32
39
|
npx let-them-talk help Show this help message
|
|
33
40
|
|
|
@@ -77,11 +84,11 @@ function detectOllama() {
|
|
|
77
84
|
|
|
78
85
|
// The data directory where all agents read/write — must be the same for server + dashboard
|
|
79
86
|
function dataDir(cwd) {
|
|
80
|
-
return
|
|
87
|
+
return resolveSharedDataDir({ cwd });
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
// Configure for Claude Code (.mcp.json in project root)
|
|
84
|
-
function setupClaude(serverPath, cwd) {
|
|
91
|
+
function setupClaude(serverPath, cwd, log = console.log) {
|
|
85
92
|
const mcpConfigPath = path.join(cwd, '.mcp.json');
|
|
86
93
|
let mcpConfig = { mcpServers: {} };
|
|
87
94
|
if (fs.existsSync(mcpConfigPath)) {
|
|
@@ -92,7 +99,7 @@ function setupClaude(serverPath, cwd) {
|
|
|
92
99
|
// Backup corrupted file before overwriting
|
|
93
100
|
const backup = mcpConfigPath + '.backup';
|
|
94
101
|
fs.copyFileSync(mcpConfigPath, backup);
|
|
95
|
-
|
|
102
|
+
log(' [warn] Existing .mcp.json was invalid — backed up to .mcp.json.backup');
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
|
|
@@ -103,11 +110,11 @@ function setupClaude(serverPath, cwd) {
|
|
|
103
110
|
};
|
|
104
111
|
|
|
105
112
|
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
106
|
-
|
|
113
|
+
log(' [ok] Claude Code: .mcp.json updated');
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
// Configure for Gemini CLI (.gemini/settings.json or GEMINI.md with MCP config)
|
|
110
|
-
function setupGemini(serverPath, cwd) {
|
|
117
|
+
function setupGemini(serverPath, cwd, log = console.log) {
|
|
111
118
|
// Gemini CLI uses .gemini/settings.json for MCP configuration
|
|
112
119
|
const geminiDir = path.join(cwd, '.gemini');
|
|
113
120
|
const settingsPath = path.join(geminiDir, 'settings.json');
|
|
@@ -124,7 +131,7 @@ function setupGemini(serverPath, cwd) {
|
|
|
124
131
|
} catch {
|
|
125
132
|
const backup = settingsPath + '.backup';
|
|
126
133
|
fs.copyFileSync(settingsPath, backup);
|
|
127
|
-
|
|
134
|
+
log(' [warn] Existing settings.json was invalid — backed up to settings.json.backup');
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
@@ -136,11 +143,11 @@ function setupGemini(serverPath, cwd) {
|
|
|
136
143
|
};
|
|
137
144
|
|
|
138
145
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
139
|
-
|
|
146
|
+
log(' [ok] Gemini CLI: .gemini/settings.json updated');
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
// Configure for Codex CLI (uses .codex/config.toml)
|
|
143
|
-
function setupCodex(serverPath, cwd) {
|
|
150
|
+
function setupCodex(serverPath, cwd, log = console.log) {
|
|
144
151
|
const codexDir = path.join(cwd, '.codex');
|
|
145
152
|
const configPath = path.join(codexDir, 'config.toml');
|
|
146
153
|
|
|
@@ -171,16 +178,16 @@ timeout = 300
|
|
|
171
178
|
fs.writeFileSync(configPath, config);
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
log(' [ok] Codex CLI: .codex/config.toml updated');
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
// Setup Ollama agent bridge script
|
|
178
|
-
function setupOllama(serverPath, cwd) {
|
|
185
|
+
function setupOllama(serverPath, cwd, log = console.log) {
|
|
179
186
|
const dir = dataDir(cwd);
|
|
180
|
-
const scriptPath = path.join(
|
|
187
|
+
const scriptPath = path.join(dir, 'ollama-agent.js');
|
|
181
188
|
|
|
182
|
-
if (!fs.existsSync(
|
|
183
|
-
fs.mkdirSync(
|
|
189
|
+
if (!fs.existsSync(dir)) {
|
|
190
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
184
191
|
}
|
|
185
192
|
|
|
186
193
|
const script = `#!/usr/bin/env node
|
|
@@ -291,27 +298,112 @@ process.on('SIGINT', function() { console.log('\\n[' + name + '] Shutting down.'
|
|
|
291
298
|
const tmpPath = scriptPath + '.tmp.' + process.pid;
|
|
292
299
|
fs.writeFileSync(tmpPath, script);
|
|
293
300
|
fs.renameSync(tmpPath, scriptPath);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
log(' [ok] Ollama agent script created: .agent-bridge/ollama-agent.js');
|
|
302
|
+
log('');
|
|
303
|
+
log(' Launch an Ollama agent with:');
|
|
304
|
+
log(' node .agent-bridge/ollama-agent.js <name> <model>');
|
|
305
|
+
log('');
|
|
306
|
+
log(' Examples:');
|
|
307
|
+
log(' node .agent-bridge/ollama-agent.js Ollama llama3');
|
|
308
|
+
log(' node .agent-bridge/ollama-agent.js Coder codellama');
|
|
309
|
+
log(' node .agent-bridge/ollama-agent.js Writer mistral');
|
|
303
310
|
}
|
|
304
311
|
|
|
305
|
-
|
|
306
|
-
|
|
312
|
+
// Merge-safe AGENTS.md / CLAUDE.md updater. Writes a marker block with the
|
|
313
|
+
// background-worker rules so any agent cd'd into this folder gets them every
|
|
314
|
+
// session. Re-running replaces only the marked block, leaving user content alone.
|
|
315
|
+
function setupAgentsMarkdown(cwd, log = console.log) {
|
|
316
|
+
const BEGIN = '<!-- BEGIN let-them-talk (auto-managed — do not edit between markers) -->';
|
|
317
|
+
const END = '<!-- END let-them-talk -->';
|
|
318
|
+
const block = [
|
|
319
|
+
BEGIN,
|
|
320
|
+
'',
|
|
321
|
+
'## Let Them Talk — Background-Worker Mode',
|
|
322
|
+
'',
|
|
323
|
+
'This project uses the `agent-bridge` MCP server for multi-agent coordination.',
|
|
324
|
+
'When you run in this folder, you are a **background worker on a team**, not an',
|
|
325
|
+
'interactive chat assistant. Follow these rules strictly:',
|
|
326
|
+
'',
|
|
327
|
+
'1. **Your CLI terminal output is invisible** to the owner and to every other',
|
|
328
|
+
' agent. If you want anyone to see something, it MUST go through',
|
|
329
|
+
' `send_message(to="...", content="...")` or `broadcast(content="...")`.',
|
|
330
|
+
'',
|
|
331
|
+
'2. **No narration in terminal.** Do not "reply" to messages in your terminal',
|
|
332
|
+
' window. Do not summarize your progress in terminal. Do not print status',
|
|
333
|
+
' updates in terminal. Those are invisible. Talk like a human on a team chat',
|
|
334
|
+
' — announce starts, finishes, blockers, and questions via `send_message`.',
|
|
335
|
+
'',
|
|
336
|
+
'3. **Stay in the listen loop.** After every action, call `listen_group()` (or',
|
|
337
|
+
' `listen()` in direct mode). When it returns an empty batch, that is NORMAL',
|
|
338
|
+
' — call it again immediately. If it returns a tool error like',
|
|
339
|
+
' `"timed out awaiting tools/call"`, that is a Codex-level transport hiccup',
|
|
340
|
+
' — immediately call it again. Never stop looping, never treat an empty',
|
|
341
|
+
' return or tool error as "done".',
|
|
342
|
+
'',
|
|
343
|
+
'4. **Reply to Dashboard/Owner via `send_message(to="Dashboard")`.** The owner',
|
|
344
|
+
' reads replies in the dashboard Messages tab, not your terminal.',
|
|
345
|
+
'',
|
|
346
|
+
'5. **Do not answer on another agent\'s behalf.** If a message targets a',
|
|
347
|
+
' specific agent (`msg.to`), only that agent should reply.',
|
|
348
|
+
'',
|
|
349
|
+
'6. The loop only ends when the owner sends a message telling you to stop.',
|
|
350
|
+
'',
|
|
351
|
+
END,
|
|
352
|
+
].join('\n');
|
|
353
|
+
|
|
354
|
+
const targets = [
|
|
355
|
+
{ file: 'AGENTS.md', label: 'Codex / oh-my-codex' },
|
|
356
|
+
{ file: 'CLAUDE.md', label: 'Claude Code' },
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
for (const { file, label } of targets) {
|
|
360
|
+
const fp = path.join(cwd, file);
|
|
361
|
+
let existing = '';
|
|
362
|
+
let existed = false;
|
|
363
|
+
if (fs.existsSync(fp)) {
|
|
364
|
+
existing = fs.readFileSync(fp, 'utf8');
|
|
365
|
+
existed = true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const markerRegex = new RegExp(
|
|
369
|
+
BEGIN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
370
|
+
'[\\s\\S]*?' +
|
|
371
|
+
END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
|
372
|
+
'g'
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
let next;
|
|
376
|
+
if (markerRegex.test(existing)) {
|
|
377
|
+
// Replace only the managed block
|
|
378
|
+
next = existing.replace(markerRegex, block);
|
|
379
|
+
log(' [ok] ' + file + ': refreshed Let Them Talk block (' + label + ')');
|
|
380
|
+
} else if (existed) {
|
|
381
|
+
// Append below user content
|
|
382
|
+
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
383
|
+
next = existing + separator + block + '\n';
|
|
384
|
+
log(' [ok] ' + file + ': appended Let Them Talk block (' + label + ')');
|
|
385
|
+
} else {
|
|
386
|
+
// New file — minimal content so only our block is present
|
|
387
|
+
next = '# ' + path.basename(cwd) + ' — Agent Instructions\n\n' + block + '\n';
|
|
388
|
+
log(' [ok] ' + file + ': created with Let Them Talk block (' + label + ')');
|
|
389
|
+
}
|
|
390
|
+
fs.writeFileSync(fp, next);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function init(options) {
|
|
395
|
+
const opts = options || {};
|
|
396
|
+
const cwd = opts.cwd || process.cwd();
|
|
307
397
|
const serverPath = path.join(__dirname, 'server.js').replace(/\\/g, '/');
|
|
308
398
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
309
|
-
const
|
|
399
|
+
const argv = Array.isArray(opts.argv) ? opts.argv : process.argv;
|
|
400
|
+
const flag = opts.flag !== undefined ? opts.flag : argv[3];
|
|
401
|
+
const log = typeof opts.log === 'function' ? opts.log : console.log;
|
|
310
402
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
403
|
+
log('');
|
|
404
|
+
log(' Let Them Talk — Initializing Agent Bridge');
|
|
405
|
+
log(' ==========================================');
|
|
406
|
+
log('');
|
|
315
407
|
|
|
316
408
|
let targets = [];
|
|
317
409
|
|
|
@@ -326,12 +418,12 @@ function init() {
|
|
|
326
418
|
} else if (flag === '--ollama') {
|
|
327
419
|
const ollama = detectOllama();
|
|
328
420
|
if (!ollama.installed) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
421
|
+
log(' Ollama not found. Install it from: https://ollama.com/download');
|
|
422
|
+
log(' After installing, run: ollama pull llama3');
|
|
423
|
+
log('');
|
|
332
424
|
} else {
|
|
333
|
-
|
|
334
|
-
setupOllama(serverPath, cwd);
|
|
425
|
+
log(' Ollama detected: ' + ollama.version);
|
|
426
|
+
setupOllama(serverPath, cwd, log);
|
|
335
427
|
}
|
|
336
428
|
targets = detectCLIs();
|
|
337
429
|
if (targets.length === 0) targets = ['claude'];
|
|
@@ -341,22 +433,28 @@ function init() {
|
|
|
341
433
|
if (targets.length === 0) {
|
|
342
434
|
// Default to claude if nothing detected
|
|
343
435
|
targets = ['claude'];
|
|
344
|
-
|
|
436
|
+
log(' No CLI detected, defaulting to Claude Code config.');
|
|
345
437
|
} else {
|
|
346
|
-
|
|
438
|
+
log(` Detected CLI(s): ${targets.join(', ')}`);
|
|
347
439
|
}
|
|
348
440
|
}
|
|
349
441
|
|
|
350
|
-
|
|
442
|
+
log('');
|
|
351
443
|
|
|
352
444
|
for (const target of targets) {
|
|
353
445
|
switch (target) {
|
|
354
|
-
case 'claude': setupClaude(serverPath, cwd); break;
|
|
355
|
-
case 'gemini': setupGemini(serverPath, cwd); break;
|
|
356
|
-
case 'codex': setupCodex(serverPath, cwd); break;
|
|
446
|
+
case 'claude': setupClaude(serverPath, cwd, log); break;
|
|
447
|
+
case 'gemini': setupGemini(serverPath, cwd, log); break;
|
|
448
|
+
case 'codex': setupCodex(serverPath, cwd, log); break;
|
|
357
449
|
}
|
|
358
450
|
}
|
|
359
451
|
|
|
452
|
+
// Persistent system-level directive for any agent that starts in this folder.
|
|
453
|
+
// Codex (via oh-my-codex's developer_instructions) and Claude Code both read
|
|
454
|
+
// AGENTS.md / CLAUDE.md automatically on startup. A marker block lets us merge
|
|
455
|
+
// in/out cleanly without clobbering whatever else the user has written.
|
|
456
|
+
setupAgentsMarkdown(cwd, log);
|
|
457
|
+
|
|
360
458
|
// Add .agent-bridge/ and MCP config files to .gitignore
|
|
361
459
|
const gitignoreEntries = ['.agent-bridge/', '.mcp.json', '.codex/', '.gemini/'];
|
|
362
460
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -365,24 +463,62 @@ function init() {
|
|
|
365
463
|
if (missing.length) {
|
|
366
464
|
content += '\n# Agent Bridge (auto-added by let-them-talk init)\n' + missing.join('\n') + '\n';
|
|
367
465
|
fs.writeFileSync(gitignorePath, content);
|
|
368
|
-
|
|
466
|
+
log(' [ok] Added to .gitignore: ' + missing.join(', '));
|
|
369
467
|
} else {
|
|
370
|
-
|
|
468
|
+
log(' [ok] .gitignore already configured');
|
|
371
469
|
}
|
|
372
470
|
} else {
|
|
373
471
|
fs.writeFileSync(gitignorePath, '# Agent Bridge (auto-added by let-them-talk init)\n' + gitignoreEntries.join('\n') + '\n');
|
|
374
|
-
|
|
472
|
+
log(' [ok] .gitignore created');
|
|
375
473
|
}
|
|
376
474
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
475
|
+
// Save local launcher scripts so users never need to re-download
|
|
476
|
+
const bridgeDir = dataDir(cwd);
|
|
477
|
+
if (!fs.existsSync(bridgeDir)) {
|
|
478
|
+
fs.mkdirSync(bridgeDir, { recursive: true });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const cliPath = path.join(__dirname, 'cli.js').replace(/\\/g, '/');
|
|
482
|
+
|
|
483
|
+
// Dashboard launcher - run with: node .agent-bridge/launch.js
|
|
484
|
+
const launcherScript = `#!/usr/bin/env node
|
|
485
|
+
// Auto-generated by let-them-talk init - launch dashboard without re-downloading
|
|
486
|
+
// Usage: node .agent-bridge/launch.js [--lan|dashboard|status|reset|msg]
|
|
487
|
+
|
|
488
|
+
const firstArg = process.argv[2] || 'dashboard';
|
|
489
|
+
const cliPath = ${JSON.stringify(cliPath)};
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
require('fs').accessSync(cliPath);
|
|
493
|
+
} catch {
|
|
494
|
+
console.error(' Let Them Talk CLI not found at: ' + cliPath);
|
|
495
|
+
console.error(' The npx cache may have been cleaned. Fix with either:');
|
|
496
|
+
console.error(' npx let-them-talk init (re-creates launcher)');
|
|
497
|
+
console.error(' npm i -g let-them-talk (permanent global install)');
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Forward to cli.js with the command
|
|
502
|
+
const forwardedArgs = firstArg === '--lan'
|
|
503
|
+
? ['dashboard', '--lan', ...process.argv.slice(3)]
|
|
504
|
+
: [firstArg, ...process.argv.slice(3)];
|
|
505
|
+
process.argv = [process.argv[0], cliPath, ...forwardedArgs];
|
|
506
|
+
require(cliPath);
|
|
507
|
+
`;
|
|
508
|
+
|
|
509
|
+
fs.writeFileSync(path.join(bridgeDir, 'launch.js'), launcherScript);
|
|
510
|
+
const launcherPath = path.join(bridgeDir, 'launch.js');
|
|
511
|
+
log(' [ok] Local launcher saved to .agent-bridge/launch.js');
|
|
512
|
+
|
|
513
|
+
log('');
|
|
514
|
+
log(' Agent Bridge is ready! Restart your CLI to pick up the MCP tools.');
|
|
515
|
+
log('');
|
|
380
516
|
|
|
381
517
|
// Show template if --template was provided
|
|
382
518
|
var templateFlag = null;
|
|
383
|
-
for (var i = 3; i <
|
|
384
|
-
if (
|
|
385
|
-
templateFlag =
|
|
519
|
+
for (var i = 3; i < argv.length; i++) {
|
|
520
|
+
if (argv[i] === '--template' && argv[i + 1]) {
|
|
521
|
+
templateFlag = argv[i + 1];
|
|
386
522
|
break;
|
|
387
523
|
}
|
|
388
524
|
}
|
|
@@ -390,22 +526,30 @@ function init() {
|
|
|
390
526
|
if (templateFlag) {
|
|
391
527
|
showTemplate(templateFlag);
|
|
392
528
|
} else {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
529
|
+
log(' Open two terminals and start a conversation between agents.');
|
|
530
|
+
log(' Tip: Use "npx let-them-talk init --template pair" for ready-made prompts.');
|
|
531
|
+
log('');
|
|
532
|
+
log(' \x1b[1m Monitor:\x1b[0m');
|
|
533
|
+
log(' node .agent-bridge/launch.js (dashboard)');
|
|
534
|
+
log(' node .agent-bridge/launch.js status (agent status)');
|
|
535
|
+
log(' node .agent-bridge/launch.js reset (clear data)');
|
|
536
|
+
log('');
|
|
537
|
+
log(' Or use npx (re-downloads each time):');
|
|
538
|
+
log(' npx let-them-talk dashboard');
|
|
539
|
+
log('');
|
|
404
540
|
}
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
cwd,
|
|
544
|
+
flag: flag || null,
|
|
545
|
+
targets,
|
|
546
|
+
bridgeDir,
|
|
547
|
+
launcherPath,
|
|
548
|
+
};
|
|
405
549
|
}
|
|
406
550
|
|
|
407
551
|
function reset() {
|
|
408
|
-
const targetDir =
|
|
552
|
+
const targetDir = resolveDataDirCli();
|
|
409
553
|
|
|
410
554
|
if (!fs.existsSync(targetDir)) {
|
|
411
555
|
console.log(' No .agent-bridge/ directory found. Nothing to reset.');
|
|
@@ -436,38 +580,79 @@ function reset() {
|
|
|
436
580
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
437
581
|
const archivePath = path.join(archiveDir, timestamp);
|
|
438
582
|
try {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
fs.copyFileSync(src, path.join(archivePath, f));
|
|
446
|
-
archived++;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (archived > 0) {
|
|
450
|
-
console.log(' [ok] Archived ' + archived + ' files to .agent-bridge-archive/' + timestamp + '/');
|
|
583
|
+
const archiveResult = getCanonicalStateCli().archiveFiles({
|
|
584
|
+
fileNames: ['history.jsonl', 'messages.jsonl', 'agents.json', 'decisions.json', 'tasks.json'],
|
|
585
|
+
destinationDir: archivePath,
|
|
586
|
+
});
|
|
587
|
+
if (archiveResult.archived > 0) {
|
|
588
|
+
console.log(' [ok] Archived ' + archiveResult.archived + ' files to .agent-bridge-archive/' + timestamp + '/');
|
|
451
589
|
}
|
|
452
590
|
} catch (e) {
|
|
453
591
|
console.log(' [warn] Could not archive: ' + e.message + ' — proceeding with reset anyway.');
|
|
454
592
|
}
|
|
455
593
|
|
|
456
|
-
|
|
457
|
-
|
|
594
|
+
getCanonicalStateCli().resetRuntime({
|
|
595
|
+
fixedFileNames: [
|
|
596
|
+
'messages.jsonl',
|
|
597
|
+
'history.jsonl',
|
|
598
|
+
'agents.json',
|
|
599
|
+
'acks.json',
|
|
600
|
+
'tasks.json',
|
|
601
|
+
'profiles.json',
|
|
602
|
+
'workflows.json',
|
|
603
|
+
'branches.json',
|
|
604
|
+
'read_receipts.json',
|
|
605
|
+
'permissions.json',
|
|
606
|
+
'config.json',
|
|
607
|
+
'decisions.json',
|
|
608
|
+
],
|
|
609
|
+
});
|
|
458
610
|
console.log(' Cleared all data from ' + targetDir);
|
|
459
611
|
}
|
|
460
612
|
|
|
461
613
|
function getTemplates() {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
614
|
+
var all = [];
|
|
615
|
+
|
|
616
|
+
// 1. Built-in templates (shipped with the package)
|
|
617
|
+
var builtinDir = path.join(__dirname, 'templates');
|
|
618
|
+
if (fs.existsSync(builtinDir)) {
|
|
619
|
+
fs.readdirSync(builtinDir).filter(f => f.endsWith('.json')).forEach(f => {
|
|
620
|
+
try { var t = JSON.parse(fs.readFileSync(path.join(builtinDir, f), 'utf8')); t._source = 'built-in'; all.push(t); }
|
|
621
|
+
catch {}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 2. Project-local templates: .agent-bridge/templates/ in current working directory
|
|
626
|
+
var localDir = path.join(resolveDataDirCli(), 'templates');
|
|
627
|
+
if (fs.existsSync(localDir)) {
|
|
628
|
+
fs.readdirSync(localDir).filter(f => f.endsWith('.json')).forEach(f => {
|
|
629
|
+
try {
|
|
630
|
+
var t = JSON.parse(fs.readFileSync(path.join(localDir, f), 'utf8'));
|
|
631
|
+
t._source = 'local';
|
|
632
|
+
// Don't add duplicates (local overrides built-in with same name)
|
|
633
|
+
var existing = all.findIndex(e => e.name === t.name);
|
|
634
|
+
if (existing >= 0) all[existing] = t;
|
|
635
|
+
else all.push(t);
|
|
636
|
+
} catch {}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// 3. User-global templates: ~/.let-them-talk/templates/
|
|
641
|
+
var homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
642
|
+
var globalDir = path.join(homeDir, '.let-them-talk', 'templates');
|
|
643
|
+
if (fs.existsSync(globalDir)) {
|
|
644
|
+
fs.readdirSync(globalDir).filter(f => f.endsWith('.json')).forEach(f => {
|
|
645
|
+
try {
|
|
646
|
+
var t = JSON.parse(fs.readFileSync(path.join(globalDir, f), 'utf8'));
|
|
647
|
+
t._source = 'global';
|
|
648
|
+
var existing = all.findIndex(e => e.name === t.name);
|
|
649
|
+
if (existing >= 0) all[existing] = t;
|
|
650
|
+
else all.push(t);
|
|
651
|
+
} catch {}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return all;
|
|
471
656
|
}
|
|
472
657
|
|
|
473
658
|
function listTemplates() {
|
|
@@ -478,11 +663,17 @@ function listTemplates() {
|
|
|
478
663
|
console.log('');
|
|
479
664
|
for (const t of templates) {
|
|
480
665
|
const agentNames = t.agents.map(a => a.name).join(', ');
|
|
481
|
-
|
|
666
|
+
const sourceTag = t._source === 'local' ? ' [local]' : t._source === 'global' ? ' [global]' : '';
|
|
667
|
+
console.log(' ' + t.name.padEnd(12) + ' ' + t.description + sourceTag);
|
|
482
668
|
console.log(' ' + ''.padEnd(12) + ' Agents: ' + agentNames);
|
|
483
669
|
console.log('');
|
|
484
670
|
}
|
|
485
671
|
console.log(' Usage: npx let-them-talk init --template <name>');
|
|
672
|
+
console.log(' Note: this command lists agent templates only. Conversation workflow templates ship separately in agent-bridge/conversation-templates/*.json.');
|
|
673
|
+
console.log('');
|
|
674
|
+
console.log(' Custom templates:');
|
|
675
|
+
console.log(' Project-local: .agent-bridge/templates/*.json');
|
|
676
|
+
console.log(' User-global: ~/.let-them-talk/templates/*.json');
|
|
486
677
|
console.log('');
|
|
487
678
|
}
|
|
488
679
|
|
|
@@ -499,8 +690,9 @@ function showTemplate(templateName) {
|
|
|
499
690
|
console.log(' Template: ' + template.name);
|
|
500
691
|
console.log(' ' + template.description);
|
|
501
692
|
console.log('');
|
|
502
|
-
console.log(' Copy these prompts into each terminal:');
|
|
693
|
+
console.log(' Copy these agent prompts into each terminal:');
|
|
503
694
|
console.log(' ======================================');
|
|
695
|
+
console.log(' These prompts assume current onboarding: register, get_briefing(), then get_guide() when you need the current rules.');
|
|
504
696
|
|
|
505
697
|
for (var i = 0; i < template.agents.length; i++) {
|
|
506
698
|
var a = template.agents[i];
|
|
@@ -520,7 +712,11 @@ function dashboard() {
|
|
|
520
712
|
}
|
|
521
713
|
|
|
522
714
|
function resolveDataDirCli() {
|
|
523
|
-
return
|
|
715
|
+
return resolveSharedDataDir();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function getCanonicalStateCli() {
|
|
719
|
+
return createCanonicalState({ dataDir: resolveDataDirCli(), processPid: process.pid });
|
|
524
720
|
}
|
|
525
721
|
|
|
526
722
|
function readJsonl(filePath) {
|
|
@@ -569,10 +765,7 @@ function cliMsg() {
|
|
|
569
765
|
timestamp: new Date().toISOString(),
|
|
570
766
|
};
|
|
571
767
|
|
|
572
|
-
|
|
573
|
-
const historyFile = path.join(dir, 'history.jsonl');
|
|
574
|
-
fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
|
|
575
|
-
fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
|
|
768
|
+
getCanonicalStateCli().appendMessage(msg);
|
|
576
769
|
|
|
577
770
|
console.log(' Message sent to ' + recipient + ': ' + text);
|
|
578
771
|
}
|
|
@@ -658,155 +851,13 @@ function cliStatus() {
|
|
|
658
851
|
console.log('');
|
|
659
852
|
}
|
|
660
853
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Parse --agents flag (default: 3)
|
|
671
|
-
let agentCount = 3;
|
|
672
|
-
const agentsIdx = process.argv.indexOf('--agents');
|
|
673
|
-
if (agentsIdx !== -1 && process.argv[agentsIdx + 1]) {
|
|
674
|
-
agentCount = Math.max(2, Math.min(10, parseInt(process.argv[agentsIdx + 1]) || 3));
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Parse --timeout flag (default: no timeout, in minutes)
|
|
678
|
-
let timeoutMin = 0;
|
|
679
|
-
const timeoutIdx = process.argv.indexOf('--timeout');
|
|
680
|
-
if (timeoutIdx !== -1 && process.argv[timeoutIdx + 1]) {
|
|
681
|
-
timeoutMin = Math.max(1, parseInt(process.argv[timeoutIdx + 1]) || 0);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const cwd = process.cwd();
|
|
685
|
-
const dir = path.join(cwd, '.agent-bridge');
|
|
686
|
-
const serverPath = path.join(__dirname, 'server.js');
|
|
687
|
-
|
|
688
|
-
// Ensure data directory exists
|
|
689
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
690
|
-
|
|
691
|
-
// Set group conversation mode
|
|
692
|
-
const configPath = path.join(dir, 'config.json');
|
|
693
|
-
const config = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {};
|
|
694
|
-
config.conversation_mode = 'group';
|
|
695
|
-
fs.writeFileSync(configPath, JSON.stringify(config));
|
|
696
|
-
|
|
697
|
-
// Agent names based on count
|
|
698
|
-
const AGENT_NAMES = ['Lead', 'Builder', 'Reviewer', 'Architect', 'Frontend', 'Backend', 'Tester', 'Designer', 'DevOps', 'Security'];
|
|
699
|
-
const names = AGENT_NAMES.slice(0, agentCount);
|
|
700
|
-
|
|
701
|
-
console.log('');
|
|
702
|
-
console.log(' Let Them Talk — Autonomous Run');
|
|
703
|
-
console.log(' ===============================');
|
|
704
|
-
console.log(' Prompt: ' + prompt);
|
|
705
|
-
console.log(' Agents: ' + agentCount + ' (' + names.join(', ') + ')');
|
|
706
|
-
console.log(' Mode: Autonomous (proactive work loop)');
|
|
707
|
-
console.log('');
|
|
708
|
-
|
|
709
|
-
const { spawn } = require('child_process');
|
|
710
|
-
const children = [];
|
|
711
|
-
|
|
712
|
-
// Spawn agent processes
|
|
713
|
-
for (let i = 0; i < agentCount; i++) {
|
|
714
|
-
const agentName = names[i];
|
|
715
|
-
console.log(' Spawning agent: ' + agentName + '...');
|
|
716
|
-
|
|
717
|
-
const child = spawn('node', [serverPath], {
|
|
718
|
-
env: {
|
|
719
|
-
...process.env,
|
|
720
|
-
AGENT_BRIDGE_DATA_DIR: dir,
|
|
721
|
-
AGENT_BRIDGE_AUTO_REGISTER: agentName,
|
|
722
|
-
AGENT_BRIDGE_AUTO_PROMPT: i === 0 ? prompt : '', // only first agent gets the prompt
|
|
723
|
-
},
|
|
724
|
-
stdio: 'pipe',
|
|
725
|
-
cwd: cwd,
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
child.on('error', (err) => {
|
|
729
|
-
console.error(' [' + agentName + '] Error: ' + err.message);
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
child.on('exit', (code) => {
|
|
733
|
-
if (code !== 0 && code !== null) {
|
|
734
|
-
console.log(' \x1b[33m[' + agentName + '] Crashed (code ' + code + '). Auto-restarting...\x1b[0m');
|
|
735
|
-
const restart = spawn('node', [serverPath], {
|
|
736
|
-
env: { ...process.env, AGENT_BRIDGE_DATA_DIR: dir, AGENT_BRIDGE_AUTO_REGISTER: agentName },
|
|
737
|
-
stdio: 'pipe', cwd: cwd,
|
|
738
|
-
});
|
|
739
|
-
const entry = children.find(c => c.name === agentName);
|
|
740
|
-
if (entry) entry.process = restart;
|
|
741
|
-
} else {
|
|
742
|
-
console.log(' [' + agentName + '] Exited.');
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
children.push({ name: agentName, process: child });
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
console.log('');
|
|
750
|
-
console.log(' All ' + agentCount + ' agents spawned. They will auto-register and start working.');
|
|
751
|
-
console.log(' Open the dashboard to monitor: npx let-them-talk dashboard');
|
|
752
|
-
console.log('');
|
|
753
|
-
console.log(' Press Ctrl+C to stop all agents.');
|
|
754
|
-
console.log('');
|
|
755
|
-
|
|
756
|
-
// Inject the prompt as a dashboard message after agents register
|
|
757
|
-
setTimeout(() => {
|
|
758
|
-
try {
|
|
759
|
-
const messagesFile = path.join(dir, 'messages.jsonl');
|
|
760
|
-
const msg = {
|
|
761
|
-
id: 'run_' + Date.now().toString(36),
|
|
762
|
-
from: 'Dashboard',
|
|
763
|
-
to: '__group__',
|
|
764
|
-
content: prompt,
|
|
765
|
-
timestamp: new Date().toISOString(),
|
|
766
|
-
broadcast: true,
|
|
767
|
-
};
|
|
768
|
-
fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
|
|
769
|
-
const historyFile = path.join(dir, 'history.jsonl');
|
|
770
|
-
fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
|
|
771
|
-
console.log(' Prompt injected to team. Agents will pick it up via get_work().');
|
|
772
|
-
} catch (e) {
|
|
773
|
-
console.error(' Failed to inject prompt: ' + e.message);
|
|
774
|
-
}
|
|
775
|
-
}, 3000); // 3s delay for agents to register
|
|
776
|
-
|
|
777
|
-
// Clean shutdown on Ctrl+C
|
|
778
|
-
process.on('SIGINT', () => {
|
|
779
|
-
console.log('\n Stopping all agents...');
|
|
780
|
-
for (const c of children) {
|
|
781
|
-
try { c.process.kill(); } catch {}
|
|
782
|
-
}
|
|
783
|
-
process.exit(0);
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
// Auto-stop after --timeout minutes
|
|
787
|
-
if (timeoutMin > 0) {
|
|
788
|
-
console.log(' Auto-stop in ' + timeoutMin + ' minute(s).');
|
|
789
|
-
setTimeout(() => {
|
|
790
|
-
console.log('\n Timeout reached (' + timeoutMin + 'min). Stopping all agents...');
|
|
791
|
-
for (const c of children) { try { c.process.kill(); } catch {} }
|
|
792
|
-
process.exit(0);
|
|
793
|
-
}, timeoutMin * 60000);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Periodic progress updates every 30s
|
|
797
|
-
setInterval(() => {
|
|
798
|
-
try {
|
|
799
|
-
const agentsData = fs.existsSync(path.join(dir, 'agents.json')) ? JSON.parse(fs.readFileSync(path.join(dir, 'agents.json'), 'utf8')) : {};
|
|
800
|
-
const online = Object.values(agentsData).filter(a => {
|
|
801
|
-
try { process.kill(a.pid, 0); return true; } catch { return false; }
|
|
802
|
-
}).length;
|
|
803
|
-
const history = fs.existsSync(path.join(dir, 'history.jsonl')) ? fs.readFileSync(path.join(dir, 'history.jsonl'), 'utf8').trim().split(/\r?\n/).filter(l => l.trim()).length : 0;
|
|
804
|
-
const tasksData = fs.existsSync(path.join(dir, 'tasks.json')) ? JSON.parse(fs.readFileSync(path.join(dir, 'tasks.json'), 'utf8')) : [];
|
|
805
|
-
const done = Array.isArray(tasksData) ? tasksData.filter(t => t.status === 'done').length : 0;
|
|
806
|
-
const active = Array.isArray(tasksData) ? tasksData.filter(t => t.status === 'in_progress').length : 0;
|
|
807
|
-
console.log(` \x1b[90m[${new Date().toLocaleTimeString()}]\x1b[0m ${online} agents | ${history} msgs | ${done} done, ${active} active`);
|
|
808
|
-
} catch {}
|
|
809
|
-
}, 30000);
|
|
854
|
+
function cliMigrate() {
|
|
855
|
+
const args = process.argv.slice(3);
|
|
856
|
+
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
857
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
858
|
+
const projectArg = positional[0] || process.cwd();
|
|
859
|
+
const { migrate } = require('./scripts/migrate-legacy-to-canonical');
|
|
860
|
+
migrate(projectArg, { dryRun });
|
|
810
861
|
}
|
|
811
862
|
|
|
812
863
|
// v5.0: Diagnostic health check
|
|
@@ -817,7 +868,7 @@ function cliDoctor() {
|
|
|
817
868
|
let issues = 0;
|
|
818
869
|
|
|
819
870
|
// Check data directory
|
|
820
|
-
const dir =
|
|
871
|
+
const dir = resolveDataDirCli();
|
|
821
872
|
if (fs.existsSync(dir)) {
|
|
822
873
|
console.log(' \x1b[32m✓\x1b[0m .agent-bridge/ directory exists');
|
|
823
874
|
try { fs.accessSync(dir, fs.constants.W_OK); console.log(' \x1b[32m✓\x1b[0m .agent-bridge/ is writable'); }
|
|
@@ -1041,49 +1092,154 @@ function uninstall() {
|
|
|
1041
1092
|
console.log('');
|
|
1042
1093
|
}
|
|
1043
1094
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1095
|
+
// --- Assistant init ---
|
|
1096
|
+
function assistantInit() {
|
|
1097
|
+
const cwd = process.cwd();
|
|
1098
|
+
const dataDir = path.join(cwd, '.agent-bridge');
|
|
1099
|
+
const assistantDir = path.join(dataDir, 'assistant');
|
|
1100
|
+
|
|
1101
|
+
console.log('');
|
|
1102
|
+
console.log(' \x1b[1m\x1b[35mLet Them Talk — Assistant Setup\x1b[0m');
|
|
1103
|
+
console.log(' ════════════════════════════════════');
|
|
1104
|
+
console.log('');
|
|
1105
|
+
|
|
1106
|
+
// Check if Let Them Talk is already initialized
|
|
1107
|
+
if (!fs.existsSync(dataDir)) {
|
|
1108
|
+
console.log(' \x1b[31m✗ Let Them Talk not found.\x1b[0m');
|
|
1109
|
+
console.log(' Run \x1b[36mnpx let-them-talk init\x1b[0m first.');
|
|
1110
|
+
console.log('');
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Create assistant directory with template files
|
|
1115
|
+
if (!fs.existsSync(assistantDir)) {
|
|
1116
|
+
fs.mkdirSync(assistantDir, { recursive: true });
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const templateDir = path.join(__dirname, 'assistant');
|
|
1120
|
+
const files = ['Soul.md', 'Identity.md', 'Memory.md', 'Skills.md', 'Tools.md', 'SafetyRules.md'];
|
|
1121
|
+
let created = 0;
|
|
1122
|
+
let skipped = 0;
|
|
1123
|
+
|
|
1124
|
+
for (const file of files) {
|
|
1125
|
+
const dest = path.join(assistantDir, file);
|
|
1126
|
+
if (fs.existsSync(dest)) {
|
|
1127
|
+
console.log(' \x1b[33m⊘\x1b[0m ' + file + ' (already exists, skipped)');
|
|
1128
|
+
skipped++;
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
const src = path.join(templateDir, file);
|
|
1132
|
+
if (fs.existsSync(src)) {
|
|
1133
|
+
fs.copyFileSync(src, dest);
|
|
1134
|
+
} else {
|
|
1135
|
+
// Fallback: create minimal template
|
|
1136
|
+
const content = `# ${file.replace('.md', '')}\n\nEdit this file to customize your assistant.\n`;
|
|
1137
|
+
fs.writeFileSync(dest, content);
|
|
1138
|
+
}
|
|
1139
|
+
console.log(' \x1b[32m✓\x1b[0m ' + file);
|
|
1140
|
+
created++;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
console.log('');
|
|
1144
|
+
if (created > 0) {
|
|
1145
|
+
console.log(` Created ${created} file${created > 1 ? 's' : ''} in .agent-bridge/assistant/`);
|
|
1146
|
+
}
|
|
1147
|
+
if (skipped > 0) {
|
|
1148
|
+
console.log(` Skipped ${skipped} existing file${skipped > 1 ? 's' : ''}`);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
console.log('');
|
|
1152
|
+
console.log(' \x1b[1mNext steps:\x1b[0m');
|
|
1153
|
+
console.log('');
|
|
1154
|
+
console.log(' 1. Edit the personality files in \x1b[36m.agent-bridge/assistant/\x1b[0m');
|
|
1155
|
+
console.log(' - Soul.md → personality and tone');
|
|
1156
|
+
console.log(' - Identity.md → name and role');
|
|
1157
|
+
console.log(' - Skills.md → what it can do');
|
|
1158
|
+
console.log(' - Tools.md → allowed/blocked tools');
|
|
1159
|
+
console.log(' - SafetyRules.md → safety guardrails');
|
|
1160
|
+
console.log('');
|
|
1161
|
+
console.log(' 2. Start the dashboard:');
|
|
1162
|
+
console.log(' \x1b[36mnode .agent-bridge/launch.js\x1b[0m');
|
|
1163
|
+
console.log('');
|
|
1164
|
+
console.log(' 3. Open the Assistant tab in the dashboard:');
|
|
1165
|
+
console.log(' \x1b[36mhttp://<your-ip>:3000\x1b[0m → click \x1b[35mAssistant\x1b[0m tab');
|
|
1166
|
+
console.log('');
|
|
1167
|
+
console.log(' 4. Open a terminal with Let Them Talk and register as "Assistant"');
|
|
1168
|
+
console.log(' The agent should use the \x1b[36massistant()\x1b[0m tool to listen.');
|
|
1169
|
+
console.log('');
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function runCli() {
|
|
1173
|
+
const command = process.argv[2];
|
|
1174
|
+
|
|
1175
|
+
switch (command) {
|
|
1176
|
+
case 'init':
|
|
1177
|
+
init();
|
|
1178
|
+
break;
|
|
1179
|
+
case 'assistant':
|
|
1180
|
+
assistantInit();
|
|
1181
|
+
break;
|
|
1182
|
+
case 'templates':
|
|
1183
|
+
listTemplates();
|
|
1184
|
+
break;
|
|
1185
|
+
case 'dashboard':
|
|
1186
|
+
dashboard();
|
|
1187
|
+
break;
|
|
1188
|
+
case 'reset':
|
|
1189
|
+
reset();
|
|
1190
|
+
break;
|
|
1191
|
+
case 'doctor':
|
|
1192
|
+
cliDoctor();
|
|
1193
|
+
break;
|
|
1194
|
+
case 'migrate':
|
|
1195
|
+
case 'migrate-legacy':
|
|
1196
|
+
cliMigrate();
|
|
1197
|
+
break;
|
|
1198
|
+
case 'msg':
|
|
1199
|
+
case 'message':
|
|
1200
|
+
case 'send':
|
|
1201
|
+
cliMsg();
|
|
1202
|
+
break;
|
|
1203
|
+
case 'status':
|
|
1204
|
+
cliStatus();
|
|
1205
|
+
break;
|
|
1206
|
+
case 'uninstall':
|
|
1207
|
+
case 'remove':
|
|
1208
|
+
uninstall();
|
|
1209
|
+
break;
|
|
1210
|
+
case 'plugin':
|
|
1211
|
+
case 'plugins':
|
|
1212
|
+
console.log(' Plugins have been removed in v3.4.3. CLI terminals have their own extension systems.');
|
|
1213
|
+
break;
|
|
1214
|
+
case 'help':
|
|
1215
|
+
case '--help':
|
|
1216
|
+
case '-h':
|
|
1217
|
+
case undefined:
|
|
1218
|
+
printUsage();
|
|
1219
|
+
break;
|
|
1220
|
+
default:
|
|
1221
|
+
console.error(` Unknown command: ${command}`);
|
|
1222
|
+
printUsage();
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function shouldAutoRunCli() {
|
|
1228
|
+
if (require.main === module) return true;
|
|
1229
|
+
const argvPath = process.argv[1];
|
|
1230
|
+
if (!argvPath) return false;
|
|
1231
|
+
const normalizedArgvPath = path.resolve(argvPath).replace(/\\/g, '/');
|
|
1232
|
+
const normalizedFilePath = path.resolve(__filename).replace(/\\/g, '/');
|
|
1233
|
+
return process.platform === 'win32'
|
|
1234
|
+
? normalizedArgvPath.toLowerCase() === normalizedFilePath.toLowerCase()
|
|
1235
|
+
: normalizedArgvPath === normalizedFilePath;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
module.exports = {
|
|
1239
|
+
init,
|
|
1240
|
+
runCli,
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
if (shouldAutoRunCli()) {
|
|
1244
|
+
runCli();
|
|
1089
1245
|
}
|