let-them-talk 3.3.1 → 3.3.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/CHANGELOG.md +9 -0
- package/LICENSE +75 -21
- package/README.md +205 -150
- package/SECURITY.md +1 -1
- package/cli.js +1 -1
- package/dashboard.html +253 -31
- package/dashboard.js +32 -13
- package/package.json +3 -3
- package/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.3.2] - 2026-03-14
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- License changed from MIT to Business Source License 1.1 (BSL)
|
|
7
|
+
- Added SECURITY.md with vulnerability disclosure policy
|
|
8
|
+
- Added CHANGELOG.md to published npm package
|
|
9
|
+
- Added .npmignore for cleaner package distribution
|
|
10
|
+
- Version synced across all files (server, CLI, dashboard)
|
|
11
|
+
|
|
3
12
|
## [3.0.0] - 2026-03-14
|
|
4
13
|
|
|
5
14
|
### Added — Agent Profiles
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
4
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
5
|
+
|
|
6
|
+
Parameters
|
|
7
|
+
|
|
8
|
+
Licensor: Dekelelz
|
|
9
|
+
Licensed Work: Let Them Talk v3.3.3
|
|
10
|
+
The Licensed Work is (c) 2024-2026 Dekelelz.
|
|
11
|
+
Additional Use Grant: You may make use of the Licensed Work, provided that
|
|
12
|
+
you may not use the Licensed Work for a Commercial
|
|
13
|
+
Hosted Service. A "Commercial Hosted Service" is a
|
|
14
|
+
service offered to third parties on a hosted or
|
|
15
|
+
managed basis that provides substantially the same
|
|
16
|
+
functionality as the Licensed Work, or any modified
|
|
17
|
+
version thereof, as a commercial offering.
|
|
18
|
+
|
|
19
|
+
For clarity, the following uses are always permitted:
|
|
20
|
+
- Self-hosting for personal or internal business use
|
|
21
|
+
- Using as a development tool in any project
|
|
22
|
+
- Integrating into non-competing products
|
|
23
|
+
- Academic and research use
|
|
24
|
+
- Non-commercial use of any kind
|
|
25
|
+
|
|
26
|
+
Change Date: 2028-03-14
|
|
27
|
+
Change License: Apache License, Version 2.0
|
|
28
|
+
|
|
29
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
30
|
+
please contact: contact@talk.unrealai.studio
|
|
31
|
+
|
|
32
|
+
Notice
|
|
33
|
+
|
|
34
|
+
Business Source License 1.1
|
|
35
|
+
|
|
36
|
+
Terms
|
|
37
|
+
|
|
38
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
39
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
40
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
41
|
+
use.
|
|
42
|
+
|
|
43
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
44
|
+
available distribution of a specific version of the Licensed Work under this
|
|
45
|
+
License, whichever comes first, the Licensor hereby grants you rights under the
|
|
46
|
+
terms of the Change License, and the rights granted in the paragraph above
|
|
47
|
+
terminate.
|
|
48
|
+
|
|
49
|
+
If your use of the Licensed Work does not comply with the requirements currently
|
|
50
|
+
in effect as described in this License, you must purchase a commercial license
|
|
51
|
+
from the Licensor, its affiliated entities, or authorized resellers, or you must
|
|
52
|
+
refrain from using the Licensed Work.
|
|
53
|
+
|
|
54
|
+
All copies of the original and modified Licensed Work, and derivative works of
|
|
55
|
+
the Licensed Work, are subject to this License. This License applies separately
|
|
56
|
+
for each version of the Licensed Work and the Change Date may vary for each
|
|
57
|
+
version of the Licensed Work released by Licensor.
|
|
58
|
+
|
|
59
|
+
You must conspicuously display this License on each original or modified copy of
|
|
60
|
+
the Licensed Work. If you receive the Licensed Work in original or modified form
|
|
61
|
+
from a third party, the terms and conditions set forth in this License apply to
|
|
62
|
+
your use of that work.
|
|
63
|
+
|
|
64
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
65
|
+
terminate your rights under this License for the current and all other versions
|
|
66
|
+
of the Licensed Work.
|
|
67
|
+
|
|
68
|
+
This License does not grant you any right in any trademark or logo of Licensor
|
|
69
|
+
or its affiliates (provided that you may use a trademark or logo of Licensor as
|
|
70
|
+
expressly required by this License).
|
|
71
|
+
|
|
72
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
|
|
73
|
+
"AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
|
|
74
|
+
OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY,
|
|
75
|
+
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
|
package/README.md
CHANGED
|
@@ -1,63 +1,148 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="agent-bridge/logo.png" alt="Let Them Talk" width="120">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://discord.gg/6Y9YgkFNJP)
|
|
5
|
+
<h1 align="center">Let Them Talk</h1>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Multi-agent collaboration for AI CLI terminals.</strong><br>
|
|
9
|
+
Let your AI agents talk, delegate, review, and build together.
|
|
10
|
+
</p>
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/let-them-talk"><img src="https://img.shields.io/npm/v/let-them-talk.svg?style=flat&color=58a6ff" alt="npm"></a>
|
|
14
|
+
<a href="https://github.com/Dekelelz/let-them-talk/blob/master/LICENSE"><img src="https://img.shields.io/badge/License-BSL%201.1-f59e0b.svg?style=flat" alt="BSL 1.1"></a>
|
|
15
|
+
<a href="https://discord.gg/6Y9YgkFNJP"><img src="https://img.shields.io/discord/1482478651000885359?color=5865F2&label=Discord&logo=discord&logoColor=white&style=flat" alt="Discord"></a>
|
|
16
|
+
<a href="https://www.npmjs.com/package/let-them-talk"><img src="https://img.shields.io/npm/dm/let-them-talk.svg?style=flat&color=3fb950" alt="Downloads"></a>
|
|
17
|
+
</p>
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="https://talk.unrealai.studio">Website</a> ·
|
|
21
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
22
|
+
<a href="VISION.md">Vision</a> ·
|
|
23
|
+
<a href="#agent-templates">Templates</a> ·
|
|
24
|
+
<a href="#web-dashboard">Dashboard</a> ·
|
|
25
|
+
<a href="https://discord.gg/6Y9YgkFNJP">Discord</a>
|
|
26
|
+
</p>
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
# 1. Install in any project
|
|
15
|
-
npx let-them-talk init
|
|
28
|
+
---
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
npx let-them-talk dashboard
|
|
30
|
+
Let Them Talk is an MCP server that connects multiple AI CLI terminals through a shared filesystem. Open Claude Code, Gemini CLI, or Codex CLI in separate terminals — they discover each other, exchange messages, share files, assign tasks, and coordinate through workflows. A real-time web dashboard lets you watch everything unfold, inject messages, and manage the conversation.
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
If you want your AI agents to stop working in isolation and start collaborating like a team, this is it.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
Preferred setup: one command to install, one to launch the dashboard.
|
|
25
37
|
|
|
26
38
|
```bash
|
|
27
|
-
npx let-them-talk init
|
|
28
|
-
npx let-them-talk
|
|
29
|
-
npx let-them-talk init --template debate # Pro + Con
|
|
30
|
-
npx let-them-talk templates # List all templates
|
|
39
|
+
npx let-them-talk init # auto-detects your CLI and configures MCP
|
|
40
|
+
npx let-them-talk dashboard # opens the web dashboard at localhost:3000
|
|
31
41
|
```
|
|
32
42
|
|
|
43
|
+
Then open two terminals and tell each agent to register:
|
|
44
|
+
|
|
45
|
+
**Terminal 1:** `Register as "A", say hello to B, then call listen()`
|
|
46
|
+
|
|
47
|
+
**Terminal 2:** `Register as "B", then call listen()`
|
|
48
|
+
|
|
49
|
+
That's it. They'll start talking. Watch it live in the dashboard.
|
|
50
|
+
|
|
51
|
+
> **Templates:** Skip the manual setup with `npx let-them-talk init --template team` — gives you ready-to-paste prompts for a Coordinator + Researcher + Coder team. [See all templates](#agent-templates).
|
|
52
|
+
|
|
53
|
+
## Supported CLIs
|
|
54
|
+
|
|
55
|
+
| CLI | Config File | Auto-detected |
|
|
56
|
+
|-----|-------------|:-------------:|
|
|
57
|
+
| Claude Code | `.mcp.json` | Yes |
|
|
58
|
+
| Gemini CLI | `.gemini/settings.json` | Yes |
|
|
59
|
+
| Codex CLI | `.codex/config.toml` | Yes |
|
|
60
|
+
|
|
61
|
+
Run `npx let-them-talk init --all` to configure all three at once.
|
|
62
|
+
|
|
33
63
|
## How It Works
|
|
34
64
|
|
|
35
65
|
```
|
|
36
|
-
Terminal 1
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
Terminal 1 Terminal 2 Terminal 3
|
|
67
|
+
(Claude Code) (Gemini CLI) (Codex CLI)
|
|
68
|
+
| | |
|
|
69
|
+
v v v
|
|
70
|
+
MCP Server MCP Server MCP Server
|
|
71
|
+
(stdio) (stdio) (stdio)
|
|
72
|
+
| | |
|
|
73
|
+
+----------- .agent-bridge/ directory ----------+
|
|
74
|
+
messages · agents · tasks
|
|
75
|
+
profiles · workflows · plugins
|
|
76
|
+
|
|
|
77
|
+
v
|
|
78
|
+
Web Dashboard :3000
|
|
79
|
+
SSE real-time · Kanban
|
|
80
|
+
Agent monitoring · Injection
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Each terminal spawns its own MCP server process. All processes share a `.agent-bridge/` directory in your project root. The dashboard reads the same files via Server-Sent Events for instant updates.
|
|
84
|
+
|
|
85
|
+
## Highlights
|
|
86
|
+
|
|
87
|
+
- **27 MCP tools** — messaging, tasks, workflows, profiles, workspaces, branching, plugins
|
|
88
|
+
- **Real-time dashboard** — SSE-powered (~200ms latency), markdown rendering, dark/light theme
|
|
89
|
+
- **Multi-agent** — 2 agents auto-route, 3+ agents specify recipients, broadcast to all
|
|
90
|
+
- **Task management** — kanban board with create/assign/track between agents
|
|
91
|
+
- **Workflow pipelines** — multi-step automation with auto-handoff
|
|
92
|
+
- **Agent profiles** — display names, SVG avatars, roles, bios
|
|
93
|
+
- **Conversation branching** — fork at any point, isolated history per branch
|
|
94
|
+
- **File sharing** — send code, diffs, and results directly between agents
|
|
95
|
+
- **Plugin system** — extend with custom tools, 30s sandboxed execution
|
|
96
|
+
- **Zero config** — one `npx` command, auto-detects your CLI, works immediately
|
|
97
|
+
|
|
98
|
+
## Agent Templates
|
|
99
|
+
|
|
100
|
+
Pre-built team configurations. Each template gives you ready-to-paste prompts for every terminal.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx let-them-talk init --template pair # A + B
|
|
104
|
+
npx let-them-talk init --template team # Coordinator + Researcher + Coder
|
|
105
|
+
npx let-them-talk init --template review # Author + Reviewer
|
|
106
|
+
npx let-them-talk init --template debate # Pro + Con
|
|
107
|
+
npx let-them-talk templates # List all available templates
|
|
52
108
|
```
|
|
53
109
|
|
|
54
|
-
|
|
110
|
+
| Template | Agents | Best For |
|
|
111
|
+
|----------|--------|----------|
|
|
112
|
+
| **pair** | A, B | Brainstorming, Q&A, simple conversations |
|
|
113
|
+
| **team** | Coordinator, Researcher, Coder | Complex features needing research + implementation |
|
|
114
|
+
| **review** | Author, Reviewer | Code review with structured feedback loops |
|
|
115
|
+
| **debate** | Pro, Con | Evaluating trade-offs, architecture decisions |
|
|
116
|
+
|
|
117
|
+
## Web Dashboard
|
|
118
|
+
|
|
119
|
+
Launch with `npx let-them-talk dashboard` — opens at `http://localhost:3000`.
|
|
120
|
+
|
|
121
|
+
**4 main tabs:**
|
|
55
122
|
|
|
56
|
-
|
|
123
|
+
- **Messages** — live feed with full markdown, message grouping, search, bookmarks, pins, emoji reactions, conversation replay
|
|
124
|
+
- **Tasks** — kanban board (pending / in progress / done / blocked), update status from dashboard
|
|
125
|
+
- **Workspaces** — per-agent key-value storage browser
|
|
126
|
+
- **Workflows** — horizontal pipeline visualization, advance or skip steps
|
|
57
127
|
|
|
58
|
-
|
|
128
|
+
**Plus:**
|
|
59
129
|
|
|
60
|
-
|
|
130
|
+
- Agent monitoring with active / sleeping / dead / listening status
|
|
131
|
+
- Profile popups with avatars and role badges
|
|
132
|
+
- Activity heatmap and per-agent stats
|
|
133
|
+
- Message injection and broadcast from browser
|
|
134
|
+
- Conversation branching with branch tabs
|
|
135
|
+
- Export as shareable HTML or Markdown
|
|
136
|
+
- Multi-project support with auto-discover
|
|
137
|
+
- Dark / light theme toggle
|
|
138
|
+
- Mobile responsive with hamburger sidebar
|
|
139
|
+
- Browser notifications and sound alerts
|
|
140
|
+
- LAN mode for phone access
|
|
141
|
+
|
|
142
|
+
## MCP Tools (27 + plugins)
|
|
143
|
+
|
|
144
|
+
<details>
|
|
145
|
+
<summary><strong>Messaging (13 tools)</strong></summary>
|
|
61
146
|
|
|
62
147
|
| Tool | Description |
|
|
63
148
|
|------|-------------|
|
|
@@ -71,112 +156,54 @@ Each CLI terminal spawns its own MCP server process via stdio. All processes rea
|
|
|
71
156
|
| `ack_message` | Confirm message was processed |
|
|
72
157
|
| `get_history` | View conversation with thread/branch filter |
|
|
73
158
|
| `get_summary` | Condensed conversation recap |
|
|
74
|
-
| `handoff` | Transfer work
|
|
75
|
-
| `share_file` | Send file contents
|
|
76
|
-
| `reset` | Clear data (auto-archives first) |
|
|
159
|
+
| `handoff` | Transfer work with context |
|
|
160
|
+
| `share_file` | Send file contents (max 100KB) |
|
|
161
|
+
| `reset` | Clear all data (auto-archives first) |
|
|
162
|
+
|
|
163
|
+
</details>
|
|
77
164
|
|
|
78
|
-
|
|
165
|
+
<details>
|
|
166
|
+
<summary><strong>Tasks & Workflows (6 tools)</strong></summary>
|
|
79
167
|
|
|
80
168
|
| Tool | Description |
|
|
81
169
|
|------|-------------|
|
|
82
170
|
| `create_task` | Create and assign tasks |
|
|
83
|
-
| `update_task` | Update
|
|
171
|
+
| `update_task` | Update status: pending / in_progress / done / blocked |
|
|
84
172
|
| `list_tasks` | View tasks with filters |
|
|
85
173
|
| `create_workflow` | Create multi-step pipeline with assignees |
|
|
86
174
|
| `advance_workflow` | Complete current step, auto-handoff to next |
|
|
87
|
-
| `workflow_status` | Get workflow progress |
|
|
175
|
+
| `workflow_status` | Get workflow progress percentage |
|
|
88
176
|
|
|
89
|
-
|
|
177
|
+
</details>
|
|
178
|
+
|
|
179
|
+
<details>
|
|
180
|
+
<summary><strong>Profiles & Workspaces (4 tools)</strong></summary>
|
|
90
181
|
|
|
91
182
|
| Tool | Description |
|
|
92
183
|
|------|-------------|
|
|
93
184
|
| `update_profile` | Set display name, avatar, bio, role |
|
|
94
|
-
| `workspace_write` | Write
|
|
95
|
-
| `workspace_read` | Read workspace
|
|
185
|
+
| `workspace_write` | Write key-value data (50 keys, 100KB/value) |
|
|
186
|
+
| `workspace_read` | Read your workspace or another agent's |
|
|
96
187
|
| `workspace_list` | List workspace keys |
|
|
97
188
|
|
|
98
|
-
|
|
189
|
+
</details>
|
|
190
|
+
|
|
191
|
+
<details>
|
|
192
|
+
<summary><strong>Branching (3 tools)</strong></summary>
|
|
99
193
|
|
|
100
194
|
| Tool | Description |
|
|
101
195
|
|------|-------------|
|
|
102
|
-
| `fork_conversation` | Fork
|
|
196
|
+
| `fork_conversation` | Fork at any message point |
|
|
103
197
|
| `switch_branch` | Switch to a different branch |
|
|
104
198
|
| `list_branches` | List all branches with message counts |
|
|
105
199
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **Messages** — SSE-powered real-time feed, full markdown, message grouping, date separators, bookmarks, pins, emoji reactions, search, conversation replay
|
|
109
|
-
- **Tasks** — Kanban board (pending/in_progress/done/blocked), status updates from dashboard
|
|
110
|
-
- **Workspaces** — Per-agent key-value browser with collapsible accordion UI
|
|
111
|
-
- **Workflows** — Horizontal pipeline visualization, advance/skip steps from dashboard
|
|
112
|
-
- **Agent monitoring** — active/sleeping/dead/listening status, profile popups with avatars, provider badges, activity heatmap
|
|
113
|
-
- **Conversation branching** — branch tabs, switch between conversation forks
|
|
114
|
-
- **Message injection** — send messages or broadcast to agents from the browser
|
|
115
|
-
- **Plugin management** — plugin cards with enable/disable toggles
|
|
116
|
-
- **Export** — shareable HTML or Markdown download
|
|
117
|
-
- **Multi-project** — monitor multiple folders + auto-discover
|
|
118
|
-
- **Dark/light theme** — toggle with localStorage persistence
|
|
119
|
-
- **Mobile responsive** — hamburger sidebar, works on phones and tablets
|
|
120
|
-
|
|
121
|
-
### Reliability
|
|
122
|
-
|
|
123
|
-
- **Heartbeat** — 10s pings track agent liveness
|
|
124
|
-
- **Auto-compact** — message queue cleaned when > 500 lines
|
|
125
|
-
- **Auto-archive** — conversations saved before reset
|
|
126
|
-
- **Context hints** — warns agents when conversation gets long
|
|
127
|
-
- **Dead recipient warnings** — alerts when sending to offline agents
|
|
128
|
-
- **Clean exit** — agents deregister on process exit
|
|
129
|
-
|
|
130
|
-
## Agent Templates
|
|
131
|
-
|
|
132
|
-
Pre-built team configurations with ready-to-paste prompts:
|
|
133
|
-
|
|
134
|
-
| Template | Agents | Best For |
|
|
135
|
-
|----------|--------|----------|
|
|
136
|
-
| `pair` | A, B | Simple conversations, brainstorming |
|
|
137
|
-
| `team` | Coordinator, Researcher, Coder | Complex features, research + implementation |
|
|
138
|
-
| `review` | Author, Reviewer | Code review with structured feedback |
|
|
139
|
-
| `debate` | Pro, Con | Evaluating trade-offs and decisions |
|
|
140
|
-
|
|
141
|
-
## CLI Commands
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
npx let-them-talk init # Auto-detect CLI and configure
|
|
145
|
-
npx let-them-talk init --all # Configure for all CLIs
|
|
146
|
-
npx let-them-talk init --template <name> # Use a team template
|
|
147
|
-
npx let-them-talk templates # List available templates
|
|
148
|
-
npx let-them-talk dashboard # Launch web dashboard
|
|
149
|
-
npx let-them-talk reset # Clear conversation data
|
|
150
|
-
npx let-them-talk plugin list # List installed plugins
|
|
151
|
-
npx let-them-talk plugin add <file.js> # Install a plugin
|
|
152
|
-
npx let-them-talk plugin remove <name> # Remove a plugin
|
|
153
|
-
npx let-them-talk plugin enable <name> # Enable a plugin
|
|
154
|
-
npx let-them-talk plugin disable <name> # Disable a plugin
|
|
155
|
-
npx let-them-talk help # Show help
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Updating
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
# If using npx (recommended) — clear cache to get latest version
|
|
162
|
-
npx clear-npx-cache
|
|
163
|
-
npx let-them-talk init # Re-run to update MCP config paths
|
|
164
|
-
|
|
165
|
-
# If installed globally
|
|
166
|
-
npm update -g let-them-talk
|
|
167
|
-
|
|
168
|
-
# Check your version
|
|
169
|
-
npx let-them-talk help # Shows version in header
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
After updating, restart your CLI terminals to pick up the new MCP server.
|
|
200
|
+
</details>
|
|
173
201
|
|
|
174
202
|
## Plugins
|
|
175
203
|
|
|
176
|
-
Extend Let Them Talk with custom tools.
|
|
204
|
+
Extend Let Them Talk with custom tools. Drop a `.js` file in `.agent-bridge/plugins/`.
|
|
177
205
|
|
|
178
206
|
```javascript
|
|
179
|
-
// plugins/my-tool.js
|
|
180
207
|
module.exports = {
|
|
181
208
|
name: 'my-tool',
|
|
182
209
|
description: 'What this tool does',
|
|
@@ -188,21 +215,56 @@ module.exports = {
|
|
|
188
215
|
required: ['query']
|
|
189
216
|
},
|
|
190
217
|
handler(args, ctx) {
|
|
191
|
-
// ctx
|
|
218
|
+
// ctx: sendMessage, getAgents, getHistory, readFile, registeredName, dataDir
|
|
192
219
|
return { result: 'done', query: args.query };
|
|
193
220
|
}
|
|
194
221
|
};
|
|
195
222
|
```
|
|
196
223
|
|
|
197
|
-
|
|
224
|
+
```bash
|
|
225
|
+
npx let-them-talk plugin add my-tool.js # install
|
|
226
|
+
npx let-them-talk plugin list # list installed
|
|
227
|
+
npx let-them-talk plugin remove my-tool # remove
|
|
228
|
+
npx let-them-talk plugin enable my-tool # enable
|
|
229
|
+
npx let-them-talk plugin disable my-tool # disable
|
|
230
|
+
```
|
|
198
231
|
|
|
199
|
-
|
|
232
|
+
Plugins run sandboxed with a 30-second timeout. Manage via CLI or dashboard.
|
|
200
233
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
234
|
+
## CLI Reference
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
npx let-them-talk init # auto-detect CLI, configure MCP
|
|
238
|
+
npx let-them-talk init --all # configure all CLIs
|
|
239
|
+
npx let-them-talk init --template <name> # use a team template
|
|
240
|
+
npx let-them-talk templates # list templates
|
|
241
|
+
npx let-them-talk dashboard # launch web dashboard
|
|
242
|
+
npx let-them-talk reset # clear conversation data
|
|
243
|
+
npx let-them-talk plugin <subcommand> # manage plugins
|
|
244
|
+
npx let-them-talk help # show help
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Updating
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npx clear-npx-cache # clear cached version
|
|
251
|
+
npx let-them-talk init # re-run to update config
|
|
252
|
+
npx let-them-talk help # verify version
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
After updating, restart your CLI terminals to pick up the new MCP server.
|
|
256
|
+
|
|
257
|
+
## Security
|
|
258
|
+
|
|
259
|
+
Let Them Talk is a **local message broker**. It passes text messages between CLI terminals via shared files on your machine. It does **not** give agents any capabilities beyond what they already have.
|
|
260
|
+
|
|
261
|
+
**Does not:** access the internet, store API keys, run cloud services, or grant new filesystem access.
|
|
262
|
+
|
|
263
|
+
**Built-in protections:** CORS restriction, XSS prevention, path traversal protection, symlink validation, origin enforcement, SSE connection limits, input validation, message size limits (1MB), plugin sandboxing (30s timeout).
|
|
264
|
+
|
|
265
|
+
**LAN mode:** Optional phone access exposes the dashboard to your local WiFi only. Requires explicit activation.
|
|
266
|
+
|
|
267
|
+
Full details: [SECURITY.md](SECURITY.md)
|
|
206
268
|
|
|
207
269
|
## Environment Variables
|
|
208
270
|
|
|
@@ -210,34 +272,27 @@ Plugins run sandboxed with a 30-second timeout. Manage them via CLI or the dashb
|
|
|
210
272
|
|----------|---------|-------------|
|
|
211
273
|
| `AGENT_BRIDGE_DATA_DIR` | `{cwd}/.agent-bridge/` | Data directory path |
|
|
212
274
|
| `AGENT_BRIDGE_PORT` | `3000` | Dashboard port |
|
|
275
|
+
| `AGENT_BRIDGE_LAN` | `false` | Enable LAN mode |
|
|
213
276
|
| `NODE_ENV` | — | Set to `development` for hot-reload |
|
|
214
277
|
|
|
215
|
-
##
|
|
278
|
+
## Contributing
|
|
216
279
|
|
|
217
|
-
|
|
280
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
218
281
|
|
|
219
|
-
|
|
220
|
-
- Does not give agents filesystem access (they already have it via their CLI)
|
|
221
|
-
- Does not expose anything to the internet (dashboard binds to localhost only)
|
|
222
|
-
- Does not store or transmit API keys
|
|
223
|
-
- Does not run any cloud services
|
|
282
|
+
## Contact
|
|
224
283
|
|
|
225
|
-
|
|
226
|
-
- **CSRF protection** — external websites cannot send requests to the dashboard
|
|
227
|
-
- **XSS prevention** — all inputs are escaped before rendering
|
|
228
|
-
- **Path traversal protection** — agents cannot read files outside the project directory
|
|
229
|
-
- **Symlink protection** — follows symlinks and validates the real path
|
|
230
|
-
- **Origin enforcement** — POST/DELETE requests require valid localhost/LAN origin
|
|
231
|
-
- **SSE connection limits** — prevents connection exhaustion DoS
|
|
232
|
-
- **Forced sender identity** — dashboard messages are always marked as "Dashboard"
|
|
233
|
-
- **Input validation** — branch names, agent names, and paths are validated
|
|
284
|
+
For business inquiries, licensing, and partnerships: **contact@talk.unrealai.studio**
|
|
234
285
|
|
|
235
|
-
|
|
236
|
-
LAN mode (phone access) only exposes the dashboard to your local WiFi network, not the internet. It requires explicit activation and a firewall rule. A warning is shown when enabled.
|
|
286
|
+
## License
|
|
237
287
|
|
|
238
|
-
|
|
239
|
-
Plugins run with full Node.js access. Only install plugins you trust. This is the same trust model as npm packages.
|
|
288
|
+
[Business Source License 1.1](LICENSE) — Free to use, self-host, and modify. Cannot be offered as a competing commercial hosted service. Converts to Apache 2.0 on March 14, 2028.
|
|
240
289
|
|
|
241
|
-
|
|
290
|
+
---
|
|
242
291
|
|
|
243
|
-
|
|
292
|
+
<p align="center">
|
|
293
|
+
Built by <a href="https://github.com/Dekelelz">Dekelelz</a> ·
|
|
294
|
+
<a href="https://talk.unrealai.studio">Website</a> ·
|
|
295
|
+
<a href="https://discord.gg/6Y9YgkFNJP">Discord</a> ·
|
|
296
|
+
<a href="https://www.npmjs.com/package/let-them-talk">npm</a> ·
|
|
297
|
+
<a href="mailto:contact@talk.unrealai.studio">Contact</a>
|
|
298
|
+
</p>
|
package/SECURITY.md
CHANGED
|
@@ -14,7 +14,7 @@ If you discover a security vulnerability in Let Them Talk, please report it resp
|
|
|
14
14
|
|
|
15
15
|
**Do NOT open a public GitHub issue for security vulnerabilities.**
|
|
16
16
|
|
|
17
|
-
Instead, please email **
|
|
17
|
+
Instead, please email **contact@talk.unrealai.studio** or use [GitHub's private vulnerability reporting](https://github.com/Dekelelz/let-them-talk/security/advisories/new).
|
|
18
18
|
|
|
19
19
|
### What to include
|
|
20
20
|
|
package/cli.js
CHANGED
package/dashboard.html
CHANGED
|
@@ -1737,9 +1737,11 @@
|
|
|
1737
1737
|
}
|
|
1738
1738
|
|
|
1739
1739
|
/* ===== RESPONSIVE ===== */
|
|
1740
|
+
/* ===== MOBILE: TABLET (768px) ===== */
|
|
1740
1741
|
@media (max-width: 768px) {
|
|
1741
1742
|
:root {
|
|
1742
|
-
--sidebar-w:
|
|
1743
|
+
--sidebar-w: 280px;
|
|
1744
|
+
--header-h: 50px;
|
|
1743
1745
|
}
|
|
1744
1746
|
|
|
1745
1747
|
.mobile-toggle { display: block; }
|
|
@@ -1752,6 +1754,7 @@
|
|
|
1752
1754
|
z-index: 90;
|
|
1753
1755
|
transform: translateX(-100%);
|
|
1754
1756
|
box-shadow: 4px 0 20px rgba(0,0,0,0.3);
|
|
1757
|
+
transition: transform 0.25s ease;
|
|
1755
1758
|
}
|
|
1756
1759
|
|
|
1757
1760
|
.sidebar.open { transform: translateX(0); }
|
|
@@ -1769,19 +1772,143 @@
|
|
|
1769
1772
|
|
|
1770
1773
|
.header-stats { display: none; }
|
|
1771
1774
|
|
|
1775
|
+
/* Header: compact layout */
|
|
1776
|
+
.header { padding: 0 10px; gap: 6px; }
|
|
1777
|
+
.header-left { gap: 8px; }
|
|
1778
|
+
.header-actions { gap: 4px; }
|
|
1779
|
+
|
|
1780
|
+
/* Hide non-essential header buttons on mobile */
|
|
1781
|
+
.header-actions .connection { display: none; }
|
|
1782
|
+
.header-actions .btn { display: none; }
|
|
1783
|
+
.header-actions .phone-btn,
|
|
1784
|
+
.header-actions .theme-toggle,
|
|
1785
|
+
.header-actions .notif-toggle { display: inline-flex; }
|
|
1786
|
+
.header-actions .sound-toggle { display: none; }
|
|
1787
|
+
|
|
1788
|
+
/* Show mobile project selector in header */
|
|
1789
|
+
#mobile-project-name { display: none !important; }
|
|
1790
|
+
.mobile-project-select {
|
|
1791
|
+
display: inline-block !important;
|
|
1792
|
+
background: var(--surface-2);
|
|
1793
|
+
color: var(--orange);
|
|
1794
|
+
border: 1px solid var(--orange-dim);
|
|
1795
|
+
border-radius: 8px;
|
|
1796
|
+
padding: 3px 6px;
|
|
1797
|
+
font-size: 11px;
|
|
1798
|
+
font-weight: 600;
|
|
1799
|
+
max-width: 140px;
|
|
1800
|
+
cursor: pointer;
|
|
1801
|
+
outline: none;
|
|
1802
|
+
}
|
|
1803
|
+
.mobile-project-select:focus { border-color: var(--orange); }
|
|
1804
|
+
|
|
1805
|
+
/* Messages: better mobile spacing */
|
|
1806
|
+
.messages-area { padding: 8px; gap: 1px; }
|
|
1807
|
+
.message { padding: 8px 10px; gap: 8px; }
|
|
1808
|
+
.msg-avatar { width: 28px; height: 28px; font-size: 11px; }
|
|
1809
|
+
.msg-from { font-size: 12px; }
|
|
1810
|
+
.msg-content { font-size: 13px; line-height: 1.5; }
|
|
1811
|
+
.msg-time { font-size: 9px; }
|
|
1812
|
+
|
|
1813
|
+
/* Message actions: always visible on mobile (no hover) */
|
|
1814
|
+
.message .msg-actions { display: flex; opacity: 0.4; }
|
|
1815
|
+
.message .msg-actions .msg-action-btn { opacity: 1; padding: 4px 6px; font-size: 14px; }
|
|
1816
|
+
|
|
1817
|
+
/* Input bar: stack vertically */
|
|
1772
1818
|
.msg-input-bar {
|
|
1773
|
-
flex-
|
|
1819
|
+
flex-direction: column;
|
|
1820
|
+
padding: 8px 10px;
|
|
1821
|
+
gap: 6px;
|
|
1774
1822
|
}
|
|
1823
|
+
.input-target {
|
|
1824
|
+
flex-direction: row;
|
|
1825
|
+
align-items: center;
|
|
1826
|
+
min-width: unset;
|
|
1827
|
+
}
|
|
1828
|
+
.input-target label { display: none; }
|
|
1829
|
+
.input-target select { flex: 1; padding: 8px 10px; font-size: 13px; }
|
|
1830
|
+
.input-msg label { display: none; }
|
|
1831
|
+
.input-msg textarea { padding: 10px; font-size: 14px; min-height: 40px; }
|
|
1832
|
+
.send-btn { padding: 10px 16px; font-size: 13px; align-self: flex-end; }
|
|
1833
|
+
|
|
1834
|
+
/* View tabs: scrollable on mobile */
|
|
1835
|
+
.view-tabs {
|
|
1836
|
+
overflow-x: auto;
|
|
1837
|
+
-webkit-overflow-scrolling: touch;
|
|
1838
|
+
scrollbar-width: none;
|
|
1839
|
+
flex-wrap: nowrap;
|
|
1840
|
+
}
|
|
1841
|
+
.view-tabs::-webkit-scrollbar { display: none; }
|
|
1842
|
+
.view-tab { white-space: nowrap; flex-shrink: 0; flex: none; padding: 8px 14px; font-size: 12px; }
|
|
1843
|
+
|
|
1844
|
+
/* Search bar */
|
|
1845
|
+
.search-input { font-size: 14px; padding: 8px 10px; }
|
|
1775
1846
|
|
|
1776
|
-
|
|
1847
|
+
/* Phone modal: full-width on mobile */
|
|
1848
|
+
.phone-modal { width: 95vw; max-width: 95vw; }
|
|
1777
1849
|
|
|
1778
|
-
|
|
1850
|
+
/* Scroll-to-bottom button: bigger touch target */
|
|
1851
|
+
.scroll-bottom { width: 44px; height: 44px; font-size: 18px; bottom: 80px; }
|
|
1779
1852
|
}
|
|
1780
1853
|
|
|
1854
|
+
/* ===== MOBILE: PHONE (480px) ===== */
|
|
1781
1855
|
@media (max-width: 480px) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1856
|
+
:root {
|
|
1857
|
+
--header-h: 46px;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
.header { padding: 0 8px; }
|
|
1861
|
+
.logo { font-size: 14px; }
|
|
1862
|
+
.header-left { gap: 6px; }
|
|
1863
|
+
|
|
1864
|
+
/* Compact header buttons */
|
|
1865
|
+
.header-actions { gap: 2px; }
|
|
1866
|
+
.phone-btn { padding: 4px 6px; font-size: 13px; }
|
|
1867
|
+
.theme-toggle, .notif-toggle { font-size: 16px; padding: 4px; }
|
|
1868
|
+
|
|
1869
|
+
/* Mobile project select: compact */
|
|
1870
|
+
.mobile-project-select { font-size: 10px; padding: 2px 4px; max-width: 110px; }
|
|
1871
|
+
|
|
1872
|
+
/* Messages: tighter for small screens */
|
|
1873
|
+
.messages-area { padding: 6px; }
|
|
1874
|
+
.message { padding: 6px 8px; gap: 6px; border-radius: 6px; }
|
|
1875
|
+
.msg-avatar { width: 24px; height: 24px; font-size: 10px; }
|
|
1876
|
+
.msg-header { gap: 4px; }
|
|
1877
|
+
.msg-from { font-size: 11px; }
|
|
1878
|
+
.msg-to { font-size: 11px; }
|
|
1879
|
+
.msg-content { font-size: 12px; }
|
|
1880
|
+
.msg-content pre { font-size: 11px; padding: 8px; }
|
|
1881
|
+
.msg-content code { font-size: 11px; }
|
|
1882
|
+
|
|
1883
|
+
/* Reactions: smaller */
|
|
1884
|
+
.reaction-chip { font-size: 11px; padding: 1px 5px; }
|
|
1885
|
+
|
|
1886
|
+
/* Input bar: full-width, compact */
|
|
1887
|
+
.msg-input-bar { padding: 6px 8px; gap: 4px; }
|
|
1888
|
+
.input-target select { padding: 7px 8px; font-size: 12px; }
|
|
1889
|
+
.input-msg textarea { padding: 8px; font-size: 13px; }
|
|
1890
|
+
.send-btn { padding: 8px 14px; font-size: 12px; }
|
|
1891
|
+
|
|
1892
|
+
/* Sidebar: full-width on very small phones */
|
|
1893
|
+
.sidebar { width: 100vw; }
|
|
1894
|
+
|
|
1895
|
+
/* Thread items */
|
|
1896
|
+
.thread-item { padding: 6px 8px; font-size: 11px; }
|
|
1897
|
+
|
|
1898
|
+
/* Pinned messages */
|
|
1899
|
+
.pinned-section .message { padding: 4px 10px; }
|
|
1900
|
+
.pinned-section .msg-content { font-size: 11px; }
|
|
1901
|
+
|
|
1902
|
+
/* View tabs */
|
|
1903
|
+
.view-tab { padding: 7px 12px; font-size: 11px; }
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
/* ===== MOBILE: VERY SMALL PHONE (360px) ===== */
|
|
1907
|
+
@media (max-width: 360px) {
|
|
1908
|
+
.logo { font-size: 13px; }
|
|
1909
|
+
.mobile-project-select { max-width: 80px; font-size: 9px; }
|
|
1910
|
+
.msg-avatar { display: none; }
|
|
1911
|
+
.message { gap: 0; }
|
|
1785
1912
|
}
|
|
1786
1913
|
|
|
1787
1914
|
/* ===== v3.0: PROFILE AVATAR ===== */
|
|
@@ -2308,6 +2435,10 @@
|
|
|
2308
2435
|
<button class="mobile-toggle" onclick="toggleSidebar()" aria-label="Menu">☰</button>
|
|
2309
2436
|
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect rx='20' width='100' height='100' fill='%230d1117'/><path d='M20 30 Q20 20 30 20 H70 Q80 20 80 30 V55 Q80 65 70 65 H55 L40 80 V65 H30 Q20 65 20 55Z' fill='%2358a6ff'/><circle cx='38' cy='42' r='5' fill='%230d1117'/><circle cx='55' cy='42' r='5' fill='%230d1117'/></svg>" alt="" style="height:28px;margin-right:4px;vertical-align:middle">
|
|
2310
2437
|
<div class="logo">Let Them Talk</div>
|
|
2438
|
+
<span id="mobile-project-name" style="display:none;font-size:11px;color:var(--orange);font-weight:600;background:var(--orange-dim);padding:2px 8px;border-radius:10px" onclick="toggleSidebar()"></span>
|
|
2439
|
+
<select id="mobile-project-select" class="mobile-project-select" onchange="mobileSelectProject(this.value)" style="display:none">
|
|
2440
|
+
<option value="">Select project...</option>
|
|
2441
|
+
</select>
|
|
2311
2442
|
<div class="header-stats">
|
|
2312
2443
|
<div class="h-stat"><span class="h-stat-val" id="stat-messages">0</span> msgs</div>
|
|
2313
2444
|
<div class="h-stat"><span class="h-stat-val" id="stat-agents">0</span> agents</div>
|
|
@@ -2471,7 +2602,7 @@
|
|
|
2471
2602
|
</div>
|
|
2472
2603
|
</div>
|
|
2473
2604
|
<div class="app-footer">
|
|
2474
|
-
<span>Let Them Talk v3.3.
|
|
2605
|
+
<span>Let Them Talk v3.3.3</span>
|
|
2475
2606
|
</div>
|
|
2476
2607
|
<div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
|
|
2477
2608
|
<div class="profile-popup-header">
|
|
@@ -2530,6 +2661,8 @@ var activeThread = null;
|
|
|
2530
2661
|
var activeProject = ''; // empty = default/local
|
|
2531
2662
|
var cachedHistory = [];
|
|
2532
2663
|
var cachedAgents = {};
|
|
2664
|
+
var isMobile = window.innerWidth <= 768 || /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);
|
|
2665
|
+
window.addEventListener('resize', function() { isMobile = window.innerWidth <= 768; });
|
|
2533
2666
|
|
|
2534
2667
|
// Agent color palette
|
|
2535
2668
|
var COLORS = [
|
|
@@ -2969,20 +3102,32 @@ function renderThreads(messages) {
|
|
|
2969
3102
|
function renderMessages(messages) {
|
|
2970
3103
|
var el = document.getElementById('messages');
|
|
2971
3104
|
|
|
3105
|
+
|
|
2972
3106
|
if (!messages.length) {
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
'<div class="
|
|
2978
|
-
'<div class="
|
|
2979
|
-
'<div class="
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3107
|
+
if (isMobile) {
|
|
3108
|
+
var mobileHint = !activeProject
|
|
3109
|
+
? '<div class="empty-sub">Select a project above to see agent conversations</div>'
|
|
3110
|
+
: '<div class="empty-sub">Waiting for messages in: ' + activeProject.split(/[/\\]/).pop() + '</div>';
|
|
3111
|
+
el.innerHTML = '<div class="empty-state">' +
|
|
3112
|
+
'<div class="empty-icon">💬</div>' +
|
|
3113
|
+
'<div class="empty-text">No messages yet</div>' +
|
|
3114
|
+
mobileHint +
|
|
3115
|
+
'</div>';
|
|
3116
|
+
} else {
|
|
3117
|
+
el.innerHTML = '<div class="empty-state">' +
|
|
3118
|
+
'<div class="empty-icon">💬</div>' +
|
|
3119
|
+
'<div class="empty-text">No messages yet</div>' +
|
|
3120
|
+
'<div class="empty-sub">Get two AI agents talking in minutes</div>' +
|
|
3121
|
+
'<div class="onboard-steps">' +
|
|
3122
|
+
'<div class="onboard-step"><span class="onboard-num">1</span><span>Open two terminals and run <code>claude</code> in each</span></div>' +
|
|
3123
|
+
'<div class="onboard-step"><span class="onboard-num">2</span><span>Paste this in Terminal 1:</span></div>' +
|
|
3124
|
+
'<div class="copy-block" onclick="copyText(this)" data-text="You are Agent A. Register as "A". Say hello to Agent B, then call listen()."><span class="copy-hint">click to copy</span>You are Agent A. Register as "A". Say hello to Agent B, then call listen().</div>' +
|
|
3125
|
+
'<div class="onboard-step"><span class="onboard-num">3</span><span>Paste this in Terminal 2:</span></div>' +
|
|
3126
|
+
'<div class="copy-block" onclick="copyText(this)" data-text="You are Agent B. Register as "B". Call listen() to wait for messages. When you receive one, respond, then listen() again."><span class="copy-hint">click to copy</span>You are Agent B. Register as "B". Call listen() to wait for messages. When you receive one, respond, then listen() again.</div>' +
|
|
3127
|
+
'<div class="onboard-step"><span class="onboard-num">4</span><span>Watch them talk here in real time!</span></div>' +
|
|
3128
|
+
'</div>' +
|
|
3129
|
+
'</div>';
|
|
3130
|
+
}
|
|
2986
3131
|
return;
|
|
2987
3132
|
}
|
|
2988
3133
|
|
|
@@ -3141,6 +3286,7 @@ function filterThread(tid) {
|
|
|
3141
3286
|
lastMessageCount = 0;
|
|
3142
3287
|
renderThreads(cachedHistory);
|
|
3143
3288
|
renderMessages(cachedHistory);
|
|
3289
|
+
if (isMobile) closeSidebar();
|
|
3144
3290
|
}
|
|
3145
3291
|
|
|
3146
3292
|
function clearThreadFilter() {
|
|
@@ -3155,6 +3301,11 @@ function toggleSidebar() {
|
|
|
3155
3301
|
document.getElementById('sidebar-overlay').classList.toggle('open');
|
|
3156
3302
|
}
|
|
3157
3303
|
|
|
3304
|
+
function closeSidebar() {
|
|
3305
|
+
document.getElementById('sidebar').classList.remove('open');
|
|
3306
|
+
document.getElementById('sidebar-overlay').classList.remove('open');
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3158
3309
|
var newWhileScrolled = 0;
|
|
3159
3310
|
|
|
3160
3311
|
document.getElementById('messages').addEventListener('scroll', function() {
|
|
@@ -3610,6 +3761,8 @@ function switchView(view) {
|
|
|
3610
3761
|
if (view === 'workspaces') fetchWorkspaces();
|
|
3611
3762
|
if (view === 'workflows') fetchWorkflows();
|
|
3612
3763
|
if (view === 'launch') renderLaunchPanel();
|
|
3764
|
+
// Auto-close sidebar on mobile after view switch
|
|
3765
|
+
if (isMobile) closeSidebar();
|
|
3613
3766
|
}
|
|
3614
3767
|
|
|
3615
3768
|
// ==================== TASKS ====================
|
|
@@ -4095,6 +4248,7 @@ function poll() {
|
|
|
4095
4248
|
fetch('/api/agents' + pq).then(function(r) { return r.json(); }),
|
|
4096
4249
|
fetch('/api/status' + pq).then(function(r) { return r.json(); }),
|
|
4097
4250
|
]).then(function(results) {
|
|
4251
|
+
console.log('[LTT] poll ok — history:' + results[0].length + ' agents:' + Object.keys(results[1]).length + ' project:' + (activeProject || 'default'));
|
|
4098
4252
|
updateConnectionInfo(Date.now() - pollStart);
|
|
4099
4253
|
var prevHistLen = cachedHistory.length;
|
|
4100
4254
|
cachedHistory = results[0];
|
|
@@ -4140,7 +4294,7 @@ function poll() {
|
|
|
4140
4294
|
if (activeView === 'workflows') fetchWorkflows();
|
|
4141
4295
|
}).catch(function(e) {
|
|
4142
4296
|
console.error('Poll failed:', e);
|
|
4143
|
-
document.getElementById('conn-detail').textContent = '
|
|
4297
|
+
document.getElementById('conn-detail').textContent = ' ERR: ' + e.message;
|
|
4144
4298
|
});
|
|
4145
4299
|
}
|
|
4146
4300
|
|
|
@@ -4158,7 +4312,8 @@ function doReset() {
|
|
|
4158
4312
|
// ==================== PROJECT MANAGEMENT ====================
|
|
4159
4313
|
|
|
4160
4314
|
function loadProjects() {
|
|
4161
|
-
fetch('/api/projects').then(function(r) { return r.json(); }).then(function(projects) {
|
|
4315
|
+
return fetch('/api/projects').then(function(r) { return r.json(); }).then(function(projects) {
|
|
4316
|
+
console.log('[LTT] loadProjects:', projects.length, 'projects, activeProject:', activeProject);
|
|
4162
4317
|
var sel = document.getElementById('project-select');
|
|
4163
4318
|
// Keep the first option (Default)
|
|
4164
4319
|
while (sel.options.length > 1) sel.remove(1);
|
|
@@ -4168,18 +4323,76 @@ function loadProjects() {
|
|
|
4168
4323
|
opt.textContent = projects[i].name;
|
|
4169
4324
|
sel.appendChild(opt);
|
|
4170
4325
|
}
|
|
4171
|
-
|
|
4326
|
+
|
|
4327
|
+
// Auto-select first project if none selected
|
|
4328
|
+
if (!activeProject && projects.length > 0) {
|
|
4329
|
+
activeProject = projects[0].path;
|
|
4330
|
+
console.log('[LTT] auto-selected first project:', activeProject);
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
// If project came from URL param, try fuzzy match (handles path separator differences)
|
|
4334
|
+
if (activeProject && sel.value !== activeProject) {
|
|
4335
|
+
var normalizedActive = activeProject.replace(/\\/g, '/').toLowerCase();
|
|
4336
|
+
for (var j = 0; j < projects.length; j++) {
|
|
4337
|
+
if (projects[j].path.replace(/\\/g, '/').toLowerCase() === normalizedActive) {
|
|
4338
|
+
activeProject = projects[j].path; // use server's canonical path
|
|
4339
|
+
break;
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
if (activeProject) {
|
|
4345
|
+
sel.value = activeProject;
|
|
4346
|
+
document.getElementById('remove-project-btn').style.display = '';
|
|
4347
|
+
// Update header project indicator for mobile
|
|
4348
|
+
var indicator = document.getElementById('mobile-project-name');
|
|
4349
|
+
if (indicator) {
|
|
4350
|
+
var proj = projects.find(function(p) { return p.path === activeProject; });
|
|
4351
|
+
indicator.textContent = proj ? proj.name : '';
|
|
4352
|
+
indicator.style.display = proj ? '' : 'none';
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4172
4355
|
|
|
4173
4356
|
// Show/hide remove button
|
|
4174
4357
|
document.getElementById('remove-project-btn').style.display = activeProject ? '' : 'none';
|
|
4175
|
-
|
|
4358
|
+
// Sync mobile header project select
|
|
4359
|
+
syncMobileProjectSelect();
|
|
4360
|
+
}).catch(function(e) {
|
|
4361
|
+
console.error('[LTT] loadProjects failed:', e);
|
|
4362
|
+
});
|
|
4176
4363
|
}
|
|
4177
4364
|
|
|
4178
4365
|
function switchProject() {
|
|
4179
4366
|
activeProject = document.getElementById('project-select').value;
|
|
4180
4367
|
lastMessageCount = 0;
|
|
4181
4368
|
document.getElementById('remove-project-btn').style.display = activeProject ? '' : 'none';
|
|
4369
|
+
syncMobileProjectSelect();
|
|
4182
4370
|
poll();
|
|
4371
|
+
if (isMobile) closeSidebar();
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
function mobileSelectProject(val) {
|
|
4375
|
+
activeProject = val;
|
|
4376
|
+
lastMessageCount = 0;
|
|
4377
|
+
// Sync sidebar select
|
|
4378
|
+
document.getElementById('project-select').value = val;
|
|
4379
|
+
document.getElementById('remove-project-btn').style.display = val ? '' : 'none';
|
|
4380
|
+
poll();
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
function syncMobileProjectSelect() {
|
|
4384
|
+
var mobileSel = document.getElementById('mobile-project-select');
|
|
4385
|
+
if (!mobileSel) return;
|
|
4386
|
+
var sidebarSel = document.getElementById('project-select');
|
|
4387
|
+
// Copy options from sidebar select to mobile select
|
|
4388
|
+
while (mobileSel.options.length > 1) mobileSel.remove(1);
|
|
4389
|
+
for (var i = 1; i < sidebarSel.options.length; i++) {
|
|
4390
|
+
var opt = document.createElement('option');
|
|
4391
|
+
opt.value = sidebarSel.options[i].value;
|
|
4392
|
+
opt.textContent = sidebarSel.options[i].textContent;
|
|
4393
|
+
mobileSel.appendChild(opt);
|
|
4394
|
+
}
|
|
4395
|
+
mobileSel.value = activeProject || '';
|
|
4183
4396
|
}
|
|
4184
4397
|
|
|
4185
4398
|
function showAddProject() {
|
|
@@ -4225,7 +4438,7 @@ function discoverProjects() {
|
|
|
4225
4438
|
|
|
4226
4439
|
fetch('/api/discover').then(function(r) { return r.json(); }).then(function(found) {
|
|
4227
4440
|
if (!found.length) {
|
|
4228
|
-
resultsEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted);padding:4px;">No new projects found
|
|
4441
|
+
resultsEl.innerHTML = '<div style="font-size:11px;color:var(--text-muted);padding:4px;">No new projects found (all discovered projects already added)</div>';
|
|
4229
4442
|
setTimeout(function() { resultsEl.style.display = 'none'; }, 3000);
|
|
4230
4443
|
return;
|
|
4231
4444
|
}
|
|
@@ -4471,9 +4684,10 @@ function renderReplayMessages() {
|
|
|
4471
4684
|
// ==================== BROWSER NOTIFICATIONS ====================
|
|
4472
4685
|
|
|
4473
4686
|
var notifEnabled = localStorage.getItem('ltt-notif') === 'true';
|
|
4474
|
-
var notifPermission = Notification ? Notification.permission : 'denied';
|
|
4687
|
+
var notifPermission = (typeof Notification !== 'undefined') ? Notification.permission : 'denied';
|
|
4475
4688
|
|
|
4476
4689
|
function toggleNotifications() {
|
|
4690
|
+
if (typeof Notification === 'undefined') return;
|
|
4477
4691
|
if (!notifEnabled) {
|
|
4478
4692
|
if (notifPermission !== 'granted') {
|
|
4479
4693
|
Notification.requestPermission().then(function(perm) {
|
|
@@ -4507,7 +4721,7 @@ function updateNotifBtn() {
|
|
|
4507
4721
|
}
|
|
4508
4722
|
|
|
4509
4723
|
function sendBrowserNotification(msg) {
|
|
4510
|
-
if (!notifEnabled || notifPermission !== 'granted') return;
|
|
4724
|
+
if (typeof Notification === 'undefined' || !notifEnabled || notifPermission !== 'granted') return;
|
|
4511
4725
|
if (document.hasFocus()) return; // Only notify when tab is in background
|
|
4512
4726
|
try {
|
|
4513
4727
|
var preview = msg.content.substring(0, 80);
|
|
@@ -4802,12 +5016,20 @@ function initSSE() {
|
|
|
4802
5016
|
var projectFromUrl = params.get('project');
|
|
4803
5017
|
if (projectFromUrl) {
|
|
4804
5018
|
activeProject = projectFromUrl;
|
|
5019
|
+
console.log('[LTT] project from URL:', projectFromUrl);
|
|
4805
5020
|
}
|
|
4806
5021
|
})();
|
|
4807
5022
|
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
5023
|
+
// Load projects first, then poll (so auto-select works before first data fetch)
|
|
5024
|
+
loadProjects().then(function() {
|
|
5025
|
+
console.log('[LTT] init: projects loaded, starting poll + SSE');
|
|
5026
|
+
poll();
|
|
5027
|
+
initSSE();
|
|
5028
|
+
}).catch(function(e) {
|
|
5029
|
+
console.error('[LTT] init: loadProjects failed, polling anyway:', e);
|
|
5030
|
+
poll();
|
|
5031
|
+
initSSE();
|
|
5032
|
+
});
|
|
4811
5033
|
// Safety-net poll at 10s (SSE handles real-time, this catches any missed updates)
|
|
4812
5034
|
setInterval(poll, 10000);
|
|
4813
5035
|
</script>
|
package/dashboard.js
CHANGED
|
@@ -6,7 +6,12 @@ const os = require('os');
|
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const PORT = parseInt(process.env.AGENT_BRIDGE_PORT || '3000', 10);
|
|
9
|
-
|
|
9
|
+
const LAN_STATE_FILE = path.join(__dirname, '.lan-mode');
|
|
10
|
+
let LAN_MODE = process.env.AGENT_BRIDGE_LAN === 'true' || (fs.existsSync(LAN_STATE_FILE) && fs.readFileSync(LAN_STATE_FILE, 'utf8').trim() === 'true');
|
|
11
|
+
|
|
12
|
+
function persistLanMode() {
|
|
13
|
+
try { fs.writeFileSync(LAN_STATE_FILE, LAN_MODE ? 'true' : 'false'); } catch {}
|
|
14
|
+
}
|
|
10
15
|
|
|
11
16
|
function getLanIP() {
|
|
12
17
|
const interfaces = os.networkInterfaces();
|
|
@@ -422,7 +427,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
|
|
|
422
427
|
<div class="agent-chips" id="agent-chips"></div>
|
|
423
428
|
</div>
|
|
424
429
|
<div class="messages" id="messages"></div>
|
|
425
|
-
<div class="footer">Generated by <a href="https://github.com/Dekelelz/let-them-talk" target="_blank">Let Them Talk</a> ·
|
|
430
|
+
<div class="footer">Generated by <a href="https://github.com/Dekelelz/let-them-talk" target="_blank">Let Them Talk</a> · BSL 1.1</div>
|
|
426
431
|
<script>
|
|
427
432
|
var COLORS=['#58a6ff','#3fb950','#d29922','#f85149','#bc8cff','#f778ba','#79c0ff','#7ee787','#e3b341','#ffa198'];
|
|
428
433
|
var colorMap={},ci=0;
|
|
@@ -539,8 +544,9 @@ function apiDiscover() {
|
|
|
539
544
|
const checked = new Set();
|
|
540
545
|
const existing = new Set(getProjects().map(p => p.path));
|
|
541
546
|
|
|
542
|
-
function scanDir(dir, depth) {
|
|
543
|
-
|
|
547
|
+
function scanDir(dir, depth, maxDepth) {
|
|
548
|
+
maxDepth = maxDepth || 3;
|
|
549
|
+
if (depth > maxDepth || checked.has(dir)) return;
|
|
544
550
|
checked.add(dir);
|
|
545
551
|
try {
|
|
546
552
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -554,17 +560,26 @@ function apiDiscover() {
|
|
|
554
560
|
if (!existing.has(projectPath)) {
|
|
555
561
|
found.push({ name: path.basename(projectPath), path: projectPath, dataDir: fullPath });
|
|
556
562
|
}
|
|
557
|
-
} else if (depth <
|
|
558
|
-
scanDir(fullPath, depth + 1);
|
|
563
|
+
} else if (depth < maxDepth) {
|
|
564
|
+
scanDir(fullPath, depth + 1, maxDepth);
|
|
559
565
|
}
|
|
560
566
|
}
|
|
561
567
|
} catch {}
|
|
562
568
|
}
|
|
563
569
|
|
|
564
|
-
// Scan from cwd, parent, and
|
|
570
|
+
// Scan from cwd, parent, home, Desktop, and common project locations
|
|
565
571
|
const cwd = process.cwd();
|
|
572
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
566
573
|
scanDir(cwd, 0);
|
|
567
574
|
scanDir(path.dirname(cwd), 1);
|
|
575
|
+
if (home) {
|
|
576
|
+
scanDir(home, 0);
|
|
577
|
+
scanDir(path.join(home, 'Desktop'), 0);
|
|
578
|
+
scanDir(path.join(home, 'Documents'), 0);
|
|
579
|
+
scanDir(path.join(home, 'Projects'), 0);
|
|
580
|
+
scanDir(path.join(home, 'Desktop', 'Claude Projects'), 0);
|
|
581
|
+
scanDir(path.join(home, 'Desktop', 'Projects'), 0);
|
|
582
|
+
}
|
|
568
583
|
|
|
569
584
|
return found;
|
|
570
585
|
}
|
|
@@ -736,12 +751,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
736
751
|
return;
|
|
737
752
|
}
|
|
738
753
|
|
|
739
|
-
// Serve dashboard HTML (re-read
|
|
754
|
+
// Serve dashboard HTML (always re-read for hot reload)
|
|
740
755
|
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
741
|
-
const html =
|
|
742
|
-
|
|
743
|
-
:
|
|
744
|
-
|
|
756
|
+
const html = fs.readFileSync(HTML_FILE, 'utf8');
|
|
757
|
+
res.writeHead(200, {
|
|
758
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
759
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
760
|
+
'Pragma': 'no-cache',
|
|
761
|
+
'Expires': '0'
|
|
762
|
+
});
|
|
745
763
|
res.end(html);
|
|
746
764
|
}
|
|
747
765
|
// Existing APIs (now with ?project= param support)
|
|
@@ -945,6 +963,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
945
963
|
const newMode = !LAN_MODE;
|
|
946
964
|
const lanIP = getLanIP();
|
|
947
965
|
LAN_MODE = newMode;
|
|
966
|
+
persistLanMode();
|
|
948
967
|
// Send response first
|
|
949
968
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
950
969
|
res.end(JSON.stringify({ lan_mode: newMode, lan_ip: lanIP, port: PORT }));
|
|
@@ -1062,7 +1081,7 @@ server.listen(PORT, LAN_MODE ? '0.0.0.0' : '127.0.0.1', () => {
|
|
|
1062
1081
|
const dataDir = resolveDataDir();
|
|
1063
1082
|
const lanIP = getLanIP();
|
|
1064
1083
|
console.log('');
|
|
1065
|
-
console.log(' Let Them Talk - Agent Bridge Dashboard v3.
|
|
1084
|
+
console.log(' Let Them Talk - Agent Bridge Dashboard v3.3.3');
|
|
1066
1085
|
console.log(' ============================================');
|
|
1067
1086
|
console.log(' Dashboard: http://localhost:' + PORT);
|
|
1068
1087
|
if (LAN_MODE && lanIP) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "let-them-talk",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
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": {
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"bugs": {
|
|
48
48
|
"url": "https://github.com/Dekelelz/let-them-talk/issues"
|
|
49
49
|
},
|
|
50
|
-
"author": "Dekelelz",
|
|
51
|
-
"license": "
|
|
50
|
+
"author": "Dekelelz <contact@talk.unrealai.studio>",
|
|
51
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "1.27.1"
|
|
54
54
|
}
|
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.3.
|
|
2024
|
+
console.error('Agent Bridge MCP server v3.3.3 running (' + (27 + loadedPlugins.length) + ' tools)');
|
|
2025
2025
|
}
|
|
2026
2026
|
|
|
2027
2027
|
main().catch(console.error);
|