let-them-talk 4.0.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/cli.js +1 -1
- package/dashboard.html +306 -15
- package/dashboard.js +163 -4
- package/package.json +3 -2
- package/server.js +512 -83
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.2.0] - 2026-03-17
|
|
4
|
+
|
|
5
|
+
### Major — Team Intelligence, Dashboard Upgrade, Performance
|
|
6
|
+
|
|
7
|
+
Built by a 4-agent team (Architect, Tester, Protocol, Builder) working in parallel.
|
|
8
|
+
|
|
9
|
+
### Added — Team Automation
|
|
10
|
+
- **Auto-escalation** — blocked tasks auto-broadcast `[ESCALATION]` to team after 5 minutes. File-based dedup via `task.escalated_at` field (cross-process safe). Clears on unblock.
|
|
11
|
+
- **Stand-up meetings** — config-driven periodic team check-ins (`standup_interval_hours` in config.json). File-based dedup, 5+ agent gate. Broadcasts task summary with in-progress/blocked/done counts.
|
|
12
|
+
- **Quality gates** — `update_task(done)` auto-broadcasts `[REVIEW NEEDED]` (from v4.1.0, now with auto-escalation integration).
|
|
13
|
+
|
|
14
|
+
### Added — Agent Intelligence
|
|
15
|
+
- **Workload metrics** — reputation tracks `task_times[]` (completion seconds), leaderboard shows `avg_task_time_sec` per agent.
|
|
16
|
+
- **Smarter suggest_task** — caps at 3 in-progress tasks ("finish first"), suggests blocked tasks when no pending ones, workload-aware.
|
|
17
|
+
- **KB hints in listen_group** — batch messages checked against KB keys, returns `kb_hints` with relevant entries.
|
|
18
|
+
- **Thread reply context** — `listen_group` includes `_reply_context` preview of parent message for threaded replies.
|
|
19
|
+
- **Decision overlap hints** — `send_message` checks content against logged decisions, returns `_decision_hint` to prevent re-debating.
|
|
20
|
+
- **Auto-status board** — `update_task` auto-writes `_status` to agent workspace ("Working on: X"). `list_agents` includes `current_status` field.
|
|
21
|
+
|
|
22
|
+
### Added — Dashboard
|
|
23
|
+
- **Agent intent display** — dashboard shows what each agent is currently working on (from workspace `_status`)
|
|
24
|
+
- **Channel badges** — messages show colored `#channel` badges
|
|
25
|
+
- **Channel filter bar** — horizontal scrollable tabs to filter messages by channel
|
|
26
|
+
- **Channel history merging** — `/api/history` merges channel-specific + general history files
|
|
27
|
+
- **`/api/channels` endpoint** — channel list with member counts for dashboard
|
|
28
|
+
- **`/api/decisions` endpoint** — decision log display in dashboard
|
|
29
|
+
- **Decision log UI** — chronological cards with topic, decision, reasoning, author
|
|
30
|
+
|
|
31
|
+
### Improved — Performance & Safety
|
|
32
|
+
- **Escalation dedup fix** — replaced in-memory `_escalatedTasks` Set with file-based `task.escalated_at` field (cross-process safe for 10 agents)
|
|
33
|
+
- **Dashboard current_status API** — `/api/agents` includes workspace `_status` for agent intent board
|
|
34
|
+
|
|
35
|
+
## [4.1.0] - 2026-03-17
|
|
36
|
+
|
|
37
|
+
### Added — Agent Reliability & Intelligence
|
|
38
|
+
|
|
39
|
+
- **Auto-recovery (crash resume)** — when an agent's process dies, the server snapshots its state (active tasks, locked files, channels, workspace keys, last 5 messages) to `recovery-{name}.json`. When a replacement registers with the same name, the snapshot is included in the register response with instructions to resume, not restart. 1-hour TTL, auto-deletes after load.
|
|
40
|
+
- **Quality gates** — `update_task(id, "done")` auto-broadcasts `[REVIEW NEEDED]` to all alive agents. Teams get automatic review cycles without manually calling `request_review()`.
|
|
41
|
+
- **Decision overlap hints** — `send_message` in group mode checks content against existing logged decisions. Returns `_decision_hint` if a related decision exists, preventing teams from re-debating settled topics.
|
|
42
|
+
- **Enhanced `check_messages`** — now returns rich summary: `senders`, `addressed_to_you`, `preview`, `urgency` level. The proactive counterpart to the enhanced nudge.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **Recovery lock notes** — snapshot correctly labels locked files as `locked_files_released` with note that locks were auto-released.
|
|
46
|
+
|
|
3
47
|
## [4.0.0] - 2026-03-17
|
|
4
48
|
|
|
5
49
|
### Major Release — 10-Agent Free Group Mode
|
package/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const command = process.argv[2];
|
|
|
9
9
|
|
|
10
10
|
function printUsage() {
|
|
11
11
|
console.log(`
|
|
12
|
-
Let Them Talk — Agent Bridge v4.0
|
|
12
|
+
Let Them Talk — Agent Bridge v4.2.0
|
|
13
13
|
MCP message broker for inter-agent communication
|
|
14
14
|
Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
|
|
15
15
|
|
package/dashboard.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<script type="importmap">
|
|
12
12
|
{
|
|
13
13
|
"imports": {
|
|
14
|
-
"three": "
|
|
15
|
-
"three/addons/": "
|
|
14
|
+
"three": "/lib/three/build/three.module.js",
|
|
15
|
+
"three/addons/": "/lib/three/examples/jsm/"
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
</script>
|
|
@@ -356,6 +356,22 @@
|
|
|
356
356
|
.agent-badge.sleeping { background: var(--orange-dim); color: var(--orange); }
|
|
357
357
|
.agent-badge.dead { background: var(--red-dim); color: var(--red); }
|
|
358
358
|
|
|
359
|
+
.agent-status-intent {
|
|
360
|
+
font-size: 11px;
|
|
361
|
+
color: var(--text-dim);
|
|
362
|
+
padding: 3px 0;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
overflow: hidden;
|
|
365
|
+
text-overflow: ellipsis;
|
|
366
|
+
font-style: italic;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.agent-status-intent::before {
|
|
370
|
+
content: '\1F4AD';
|
|
371
|
+
margin-right: 4px;
|
|
372
|
+
font-style: normal;
|
|
373
|
+
}
|
|
374
|
+
|
|
359
375
|
.listen-badge {
|
|
360
376
|
font-size: 9px;
|
|
361
377
|
padding: 2px 6px;
|
|
@@ -757,6 +773,49 @@
|
|
|
757
773
|
.msg-to { font-size: 12px; color: var(--text-dim); }
|
|
758
774
|
.msg-time { font-size: 10px; color: var(--text-muted); }
|
|
759
775
|
|
|
776
|
+
.badge-channel {
|
|
777
|
+
background: var(--purple-dim);
|
|
778
|
+
color: var(--purple);
|
|
779
|
+
font-size: 9px;
|
|
780
|
+
padding: 1px 5px;
|
|
781
|
+
border-radius: 6px;
|
|
782
|
+
font-weight: 600;
|
|
783
|
+
cursor: pointer;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.badge-channel:hover { opacity: 0.8; }
|
|
787
|
+
|
|
788
|
+
.channel-filter-bar {
|
|
789
|
+
display: flex;
|
|
790
|
+
gap: 4px;
|
|
791
|
+
padding: 4px 12px;
|
|
792
|
+
overflow-x: auto;
|
|
793
|
+
flex-wrap: nowrap;
|
|
794
|
+
scrollbar-width: none;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.channel-filter-bar::-webkit-scrollbar { display: none; }
|
|
798
|
+
|
|
799
|
+
.channel-tab {
|
|
800
|
+
font-size: 11px;
|
|
801
|
+
padding: 3px 10px;
|
|
802
|
+
border-radius: 12px;
|
|
803
|
+
border: 1px solid var(--border);
|
|
804
|
+
background: var(--surface-2);
|
|
805
|
+
color: var(--text-dim);
|
|
806
|
+
cursor: pointer;
|
|
807
|
+
white-space: nowrap;
|
|
808
|
+
transition: all 0.15s;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.channel-tab:hover { border-color: var(--border-light); color: var(--text); }
|
|
812
|
+
|
|
813
|
+
.channel-tab.active {
|
|
814
|
+
background: var(--purple-dim);
|
|
815
|
+
color: var(--purple);
|
|
816
|
+
border-color: var(--purple);
|
|
817
|
+
}
|
|
818
|
+
|
|
760
819
|
.msg-badges {
|
|
761
820
|
display: flex;
|
|
762
821
|
gap: 4px;
|
|
@@ -2254,6 +2313,28 @@
|
|
|
2254
2313
|
color: var(--text-dim);
|
|
2255
2314
|
}
|
|
2256
2315
|
|
|
2316
|
+
.pipeline-bar {
|
|
2317
|
+
height: 4px;
|
|
2318
|
+
background: var(--surface-3);
|
|
2319
|
+
border-radius: 2px;
|
|
2320
|
+
margin-bottom: 10px;
|
|
2321
|
+
overflow: hidden;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
.pipeline-bar-fill {
|
|
2325
|
+
height: 100%;
|
|
2326
|
+
border-radius: 2px;
|
|
2327
|
+
transition: width 0.3s ease;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
.pipeline-meta {
|
|
2331
|
+
font-size: 10px;
|
|
2332
|
+
color: var(--text-muted);
|
|
2333
|
+
display: flex;
|
|
2334
|
+
gap: 12px;
|
|
2335
|
+
margin-bottom: 8px;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2257
2338
|
.pipeline-steps {
|
|
2258
2339
|
display: flex;
|
|
2259
2340
|
gap: 0;
|
|
@@ -2379,6 +2460,63 @@
|
|
|
2379
2460
|
.docs-section { padding: 14px 16px; }
|
|
2380
2461
|
}
|
|
2381
2462
|
|
|
2463
|
+
/* ===== DECISIONS LOG ===== */
|
|
2464
|
+
.decisions-section { margin-bottom: 16px; }
|
|
2465
|
+
.decisions-header {
|
|
2466
|
+
display: flex;
|
|
2467
|
+
align-items: center;
|
|
2468
|
+
justify-content: space-between;
|
|
2469
|
+
margin-bottom: 10px;
|
|
2470
|
+
}
|
|
2471
|
+
.decisions-header h3 { font-size: 15px; font-weight: 700; color: var(--accent); margin: 0; }
|
|
2472
|
+
.decisions-count { font-size: 11px; color: var(--text-muted); }
|
|
2473
|
+
|
|
2474
|
+
.decision-card {
|
|
2475
|
+
background: var(--surface);
|
|
2476
|
+
border: 1px solid var(--border);
|
|
2477
|
+
border-left: 3px solid var(--accent);
|
|
2478
|
+
border-radius: 8px;
|
|
2479
|
+
padding: 12px 14px;
|
|
2480
|
+
margin-bottom: 8px;
|
|
2481
|
+
transition: border-color 0.15s;
|
|
2482
|
+
}
|
|
2483
|
+
.decision-card:hover { border-color: var(--border-light); }
|
|
2484
|
+
|
|
2485
|
+
.decision-topic {
|
|
2486
|
+
font-size: 9px;
|
|
2487
|
+
font-weight: 700;
|
|
2488
|
+
text-transform: uppercase;
|
|
2489
|
+
letter-spacing: 0.5px;
|
|
2490
|
+
color: var(--purple);
|
|
2491
|
+
background: var(--purple-dim);
|
|
2492
|
+
padding: 1px 6px;
|
|
2493
|
+
border-radius: 6px;
|
|
2494
|
+
display: inline-block;
|
|
2495
|
+
margin-bottom: 4px;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
.decision-text {
|
|
2499
|
+
font-size: 13px;
|
|
2500
|
+
font-weight: 600;
|
|
2501
|
+
color: var(--text);
|
|
2502
|
+
margin-bottom: 4px;
|
|
2503
|
+
line-height: 1.4;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
.decision-reasoning {
|
|
2507
|
+
font-size: 12px;
|
|
2508
|
+
color: var(--text-dim);
|
|
2509
|
+
line-height: 1.5;
|
|
2510
|
+
margin-bottom: 6px;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
.decision-meta {
|
|
2514
|
+
font-size: 10px;
|
|
2515
|
+
color: var(--text-muted);
|
|
2516
|
+
display: flex;
|
|
2517
|
+
gap: 10px;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2382
2520
|
/* ===== VIRTUAL OFFICE ===== */
|
|
2383
2521
|
.office-area {
|
|
2384
2522
|
flex: 1;
|
|
@@ -3294,9 +3432,11 @@
|
|
|
3294
3432
|
<div class="search-bar" id="search-bar">
|
|
3295
3433
|
<input class="search-input" id="search-input" placeholder="Search messages... ( / )" oninput="onSearch()">
|
|
3296
3434
|
<button id="search-all-btn" onclick="toggleSearchAll()" title="Search across all projects" style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:var(--text-muted);white-space:nowrap;transition:all 0.2s">All Projects</button>
|
|
3435
|
+
<button id="deep-search-btn" onclick="deepSearch()" title="Search full history (server-side, includes compacted messages and channels)" style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:var(--text-muted);white-space:nowrap;transition:all 0.2s">Deep Search</button>
|
|
3297
3436
|
<span class="search-count" id="search-count"></span>
|
|
3298
3437
|
<button class="compact-toggle" id="compact-toggle" onclick="toggleCompactMode()" title="Toggle compact view">Compact</button>
|
|
3299
3438
|
</div>
|
|
3439
|
+
<div class="channel-filter-bar" id="channel-filter-bar" style="display:none"></div>
|
|
3300
3440
|
<div class="pinned-section" id="pinned-section">
|
|
3301
3441
|
<div class="pinned-header" onclick="togglePinnedSection()"><span>Pinned Messages</span><span class="pinned-toggle" id="pinned-toggle">Hide</span></div>
|
|
3302
3442
|
<div id="pinned-list"></div>
|
|
@@ -3332,7 +3472,7 @@
|
|
|
3332
3472
|
</div>
|
|
3333
3473
|
<div class="launch-area" id="launch-area"></div>
|
|
3334
3474
|
<div class="stats-area" id="stats-area"></div>
|
|
3335
|
-
<div class="docs-area" id="docs-area"></div>
|
|
3475
|
+
<div class="docs-area" id="docs-area"><div id="decisions-panel" style="max-width:820px;margin:0 auto"></div><div id="docs-content"></div></div>
|
|
3336
3476
|
<button class="scroll-bottom" id="scroll-bottom" onclick="scrollToBottom()">↓<span class="new-count" id="new-msg-count" style="display:none">0</span></button>
|
|
3337
3477
|
<div class="typing-bar" id="typing-bar"></div>
|
|
3338
3478
|
|
|
@@ -3765,6 +3905,7 @@ function lttFetch(url, opts) {
|
|
|
3765
3905
|
}
|
|
3766
3906
|
|
|
3767
3907
|
var activeThread = null;
|
|
3908
|
+
var activeChannel = null; // null = all channels
|
|
3768
3909
|
var activeProject = ''; // empty = default/local
|
|
3769
3910
|
var cachedHistory = [];
|
|
3770
3911
|
var cachedAgents = {};
|
|
@@ -4077,6 +4218,7 @@ function renderAgents(agents) {
|
|
|
4077
4218
|
'<span class="agent-activity-icon ' + state + '"></span>' +
|
|
4078
4219
|
activityText +
|
|
4079
4220
|
'</div>' +
|
|
4221
|
+
(info.current_status ? '<div class="agent-status-intent" title="' + escapeHtml(info.current_status) + '">' + escapeHtml(info.current_status) + '</div>' : '') +
|
|
4080
4222
|
listenHtml +
|
|
4081
4223
|
nudgeHtml +
|
|
4082
4224
|
removeHtml +
|
|
@@ -4273,6 +4415,14 @@ function renderMessages(messages) {
|
|
|
4273
4415
|
filtered = filtered.filter(function(m) { return !!bookmarks[m.id]; });
|
|
4274
4416
|
}
|
|
4275
4417
|
|
|
4418
|
+
// Channel filter
|
|
4419
|
+
if (activeChannel) {
|
|
4420
|
+
filtered = filtered.filter(function(m) {
|
|
4421
|
+
if (activeChannel === 'general') return !m.channel || m.channel === 'general';
|
|
4422
|
+
return m.channel === activeChannel;
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4276
4426
|
// Search filter
|
|
4277
4427
|
if (searchQuery) {
|
|
4278
4428
|
filtered = filtered.filter(function(m) {
|
|
@@ -4333,6 +4483,7 @@ function renderMessages(messages) {
|
|
|
4333
4483
|
var groupClass = isGrouped ? ' grouped' : '';
|
|
4334
4484
|
|
|
4335
4485
|
var badges = '';
|
|
4486
|
+
if (m.channel && m.channel !== 'general') badges += '<span class="badge-channel" onclick="filterChannel(\'' + escapeHtml(m.channel) + '\')" title="Channel: #' + escapeHtml(m.channel) + '">#' + escapeHtml(m.channel) + '</span>';
|
|
4336
4487
|
if (m.acked) badges += '<span class="badge badge-ack">ACK</span>';
|
|
4337
4488
|
if (m.thread_id) badges += '<span class="badge badge-thread">Thread</span>';
|
|
4338
4489
|
if (m.edited) badges += '<span class="badge" style="background:var(--orange-dim);color:var(--orange)" title="Edited ' + (m.edited_at ? new Date(m.edited_at).toLocaleString() : '') + '">edited</span>';
|
|
@@ -4430,6 +4581,39 @@ function clearThreadFilter() {
|
|
|
4430
4581
|
renderMessages(cachedHistory);
|
|
4431
4582
|
}
|
|
4432
4583
|
|
|
4584
|
+
function filterChannel(ch) {
|
|
4585
|
+
activeChannel = activeChannel === ch ? null : ch;
|
|
4586
|
+
lastMessageCount = 0;
|
|
4587
|
+
renderChannelBar(cachedHistory);
|
|
4588
|
+
renderMessages(cachedHistory);
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
function renderChannelBar(messages) {
|
|
4592
|
+
var bar = document.getElementById('channel-filter-bar');
|
|
4593
|
+
if (!messages || !messages.length) { bar.style.display = 'none'; return; }
|
|
4594
|
+
|
|
4595
|
+
// Collect unique channels from messages
|
|
4596
|
+
var channels = {};
|
|
4597
|
+
for (var i = 0; i < messages.length; i++) {
|
|
4598
|
+
var ch = messages[i].channel || 'general';
|
|
4599
|
+
channels[ch] = (channels[ch] || 0) + 1;
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
var keys = Object.keys(channels);
|
|
4603
|
+
// Only show channel bar if there are non-general channels
|
|
4604
|
+
if (keys.length <= 1 && keys[0] === 'general') { bar.style.display = 'none'; return; }
|
|
4605
|
+
|
|
4606
|
+
bar.style.display = 'flex';
|
|
4607
|
+
var html = '<div class="channel-tab' + (!activeChannel ? ' active' : '') + '" onclick="filterChannel(null)">All</div>';
|
|
4608
|
+
keys.sort();
|
|
4609
|
+
for (var j = 0; j < keys.length; j++) {
|
|
4610
|
+
var name = keys[j];
|
|
4611
|
+
var active = activeChannel === name ? ' active' : '';
|
|
4612
|
+
html += '<div class="channel-tab' + active + '" onclick="filterChannel(\'' + escapeHtml(name) + '\')">#' + escapeHtml(name) + ' <span style="opacity:0.5;font-size:9px">' + channels[name] + '</span></div>';
|
|
4613
|
+
}
|
|
4614
|
+
bar.innerHTML = html;
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4433
4617
|
function toggleSidebar() {
|
|
4434
4618
|
document.getElementById('sidebar').classList.toggle('open');
|
|
4435
4619
|
document.getElementById('sidebar-overlay').classList.toggle('open');
|
|
@@ -4500,6 +4684,33 @@ function onSearch() {
|
|
|
4500
4684
|
renderMessages(cachedHistory);
|
|
4501
4685
|
}
|
|
4502
4686
|
|
|
4687
|
+
function deepSearch() {
|
|
4688
|
+
var query = document.getElementById('search-input').value.trim();
|
|
4689
|
+
if (query.length < 2) return;
|
|
4690
|
+
var pq = activeProject ? '&project=' + encodeURIComponent(activeProject) : '';
|
|
4691
|
+
var countEl = document.getElementById('search-count');
|
|
4692
|
+
countEl.textContent = 'Searching...';
|
|
4693
|
+
lttFetch('/api/search?q=' + encodeURIComponent(query) + '&limit=100' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4694
|
+
if (data.error) { countEl.textContent = data.error; return; }
|
|
4695
|
+
countEl.textContent = data.results_count + ' deep result' + (data.results_count !== 1 ? 's' : '');
|
|
4696
|
+
// Convert search results to message format for renderMessages
|
|
4697
|
+
var messages = data.results.map(function(r) {
|
|
4698
|
+
return { id: r.id, from: r.from, to: r.to, content: r.preview, timestamp: r.timestamp, channel: r.channel || null };
|
|
4699
|
+
});
|
|
4700
|
+
lastMessageCount = 0;
|
|
4701
|
+
var el = document.getElementById('messages');
|
|
4702
|
+
if (!messages.length) {
|
|
4703
|
+
el.innerHTML = '<div class="empty-state"><div class="empty-icon">🔍</div><div class="empty-text">No results for "' + escapeHtml(query) + '"</div><div class="empty-sub">Searched full history including channels</div></div>';
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
// Render search results directly (don't overwrite cachedHistory)
|
|
4707
|
+
searchQuery = query.toLowerCase();
|
|
4708
|
+
renderMessages(messages);
|
|
4709
|
+
}).catch(function(e) {
|
|
4710
|
+
countEl.textContent = 'Search failed';
|
|
4711
|
+
});
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4503
4714
|
// ==================== READ RECEIPTS ====================
|
|
4504
4715
|
|
|
4505
4716
|
var cachedReadReceipts = {};
|
|
@@ -5025,17 +5236,11 @@ function exportConversation() {
|
|
|
5025
5236
|
|
|
5026
5237
|
function exportJSON() {
|
|
5027
5238
|
if (!cachedHistory.length) return;
|
|
5028
|
-
var
|
|
5029
|
-
return { id: m.id, from: m.from, to: m.to, content: m.content, timestamp: m.timestamp, thread_id: m.thread_id || null };
|
|
5030
|
-
});
|
|
5031
|
-
var json = JSON.stringify(data, null, 2);
|
|
5032
|
-
var blob = new Blob([json], { type: 'application/json' });
|
|
5033
|
-
var url = URL.createObjectURL(blob);
|
|
5239
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
5034
5240
|
var a = document.createElement('a');
|
|
5035
|
-
a.href =
|
|
5036
|
-
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
5241
|
+
a.href = '/api/export-json' + pq;
|
|
5242
|
+
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '-full.json';
|
|
5037
5243
|
a.click();
|
|
5038
|
-
URL.revokeObjectURL(url);
|
|
5039
5244
|
}
|
|
5040
5245
|
|
|
5041
5246
|
// ==================== VIEW SWITCHING ====================
|
|
@@ -5061,12 +5266,33 @@ function switchView(view) {
|
|
|
5061
5266
|
document.getElementById('stats-area').classList.toggle('visible', view === 'stats');
|
|
5062
5267
|
document.getElementById('docs-area').classList.toggle('visible', view === 'docs');
|
|
5063
5268
|
document.getElementById('search-bar').style.display = view === 'messages' ? 'flex' : 'none';
|
|
5269
|
+
document.getElementById('channel-filter-bar').style.display = view === 'messages' && activeChannel !== undefined ? '' : 'none';
|
|
5270
|
+
if (view === 'messages') renderChannelBar(cachedHistory);
|
|
5064
5271
|
if (view === 'tasks') fetchTasks();
|
|
5065
5272
|
if (view === 'workspaces') fetchWorkspaces();
|
|
5066
5273
|
if (view === 'workflows') fetchWorkflows();
|
|
5067
5274
|
if (view === 'docs') renderDocs();
|
|
5068
5275
|
if (view === 'office') {
|
|
5069
|
-
if (window.office3dStart)
|
|
5276
|
+
if (window.office3dStart) {
|
|
5277
|
+
window.office3dStart();
|
|
5278
|
+
} else {
|
|
5279
|
+
// Show helpful error if 3D engine failed to load (Three.js missing)
|
|
5280
|
+
var officeEl = document.getElementById('office-area');
|
|
5281
|
+
if (officeEl && !officeEl.querySelector('.office-error')) {
|
|
5282
|
+
var errDiv = document.createElement('div');
|
|
5283
|
+
errDiv.className = 'office-error';
|
|
5284
|
+
errDiv.style.cssText = 'display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-dim);text-align:center;padding:40px';
|
|
5285
|
+
errDiv.innerHTML = '<div style="font-size:48px;margin-bottom:16px">🏗</div>' +
|
|
5286
|
+
'<div style="font-size:16px;font-weight:600;color:var(--text);margin-bottom:8px">3D Hub loading...</div>' +
|
|
5287
|
+
'<div style="font-size:12px;max-width:400px;line-height:1.6">If the 3D world doesn\'t appear, Three.js may not have loaded correctly. Try refreshing the page. If the problem persists, check the browser console for errors.</div>';
|
|
5288
|
+
officeEl.appendChild(errDiv);
|
|
5289
|
+
// Remove error message once 3D loads
|
|
5290
|
+
var checkInterval = setInterval(function() {
|
|
5291
|
+
if (window.office3dStart) { errDiv.remove(); clearInterval(checkInterval); window.office3dStart(); }
|
|
5292
|
+
}, 2000);
|
|
5293
|
+
setTimeout(function() { clearInterval(checkInterval); }, 30000);
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5070
5296
|
}
|
|
5071
5297
|
if (view === 'launch') renderLaunchPanel();
|
|
5072
5298
|
if (view === 'stats') fetchStats();
|
|
@@ -5162,9 +5388,20 @@ function buildTaskCard(t) {
|
|
|
5162
5388
|
'<option value="blocked"' + (t.status === 'blocked' ? ' selected' : '') + '>Blocked</option>' +
|
|
5163
5389
|
'</select>';
|
|
5164
5390
|
|
|
5391
|
+
var badgesHtml = '';
|
|
5392
|
+
if (t.escalated_at) badgesHtml += '<span style="font-size:9px;padding:1px 5px;border-radius:6px;font-weight:600;background:var(--red-dim);color:var(--red)">ESCALATED</span>';
|
|
5393
|
+
if (t.channel) badgesHtml += '<span class="badge-channel" onclick="filterChannel(\'' + escapeHtml(t.channel) + '\')">#' + escapeHtml(t.channel) + '</span>';
|
|
5394
|
+
|
|
5395
|
+
var notesHtml = '';
|
|
5396
|
+
if (t.notes && t.notes.length) {
|
|
5397
|
+
var lastNote = t.notes[t.notes.length - 1];
|
|
5398
|
+
notesHtml = '<div style="font-size:10px;color:var(--text-muted);margin-top:4px;font-style:italic">' + escapeHtml(lastNote.by) + ': ' + escapeHtml((lastNote.text || '').substring(0, 80)) + '</div>';
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5165
5401
|
return '<div class="task-card" draggable="true" data-task-id="' + t.id + '" ondragstart="onTaskDragStart(event)" ondragend="onTaskDragEnd(event)">' +
|
|
5166
|
-
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + '</div>' +
|
|
5402
|
+
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + (badgesHtml ? ' ' + badgesHtml : '') + '</div>' +
|
|
5167
5403
|
(t.description ? '<div class="task-desc">' + escapeHtml(t.description) + '</div>' : '') +
|
|
5404
|
+
notesHtml +
|
|
5168
5405
|
'<div class="task-footer">' +
|
|
5169
5406
|
assigneeHtml +
|
|
5170
5407
|
statusOpts +
|
|
@@ -5815,22 +6052,41 @@ function renderWorkflows(workflows) {
|
|
|
5815
6052
|
var pct = Math.round((doneCount / wf.steps.length) * 100);
|
|
5816
6053
|
var statusColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
5817
6054
|
|
|
6055
|
+
var barColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
6056
|
+
var metaHtml = '<div class="pipeline-meta">';
|
|
6057
|
+
if (wf.created_by) metaHtml += '<span>Created by ' + escapeHtml(wf.created_by) + '</span>';
|
|
6058
|
+
if (wf.created_at) metaHtml += '<span>' + timeAgo(wf.created_at) + '</span>';
|
|
6059
|
+
if (wf.updated_at && wf.updated_at !== wf.created_at) metaHtml += '<span>Updated ' + timeAgo(wf.updated_at) + '</span>';
|
|
6060
|
+
metaHtml += '</div>';
|
|
6061
|
+
|
|
5818
6062
|
html += '<div class="pipeline">' +
|
|
5819
6063
|
'<div class="pipeline-header">' +
|
|
5820
6064
|
'<div class="pipeline-name">' + escapeHtml(wf.name) + ' <span style="font-size:11px;color:' + statusColor + ';font-weight:600">' + escapeHtml(wf.status) + '</span></div>' +
|
|
5821
6065
|
'<div class="pipeline-progress">' + doneCount + '/' + wf.steps.length + ' (' + pct + '%)</div>' +
|
|
5822
6066
|
'</div>' +
|
|
6067
|
+
metaHtml +
|
|
6068
|
+
'<div class="pipeline-bar"><div class="pipeline-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div></div>' +
|
|
5823
6069
|
'<div class="pipeline-steps">';
|
|
5824
6070
|
|
|
5825
6071
|
for (var j = 0; j < wf.steps.length; j++) {
|
|
5826
6072
|
var s = wf.steps[j];
|
|
5827
6073
|
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
5828
6074
|
if (j > 0) html += '<span class="step-arrow">→</span>';
|
|
6075
|
+
var stepTimeHtml = '';
|
|
6076
|
+
if (s.status === 'done' && s.started_at && s.completed_at) {
|
|
6077
|
+
var dur = Math.round((new Date(s.completed_at) - new Date(s.started_at)) / 60000);
|
|
6078
|
+
stepTimeHtml = '<div style="font-size:9px;color:var(--text-muted);margin-top:2px">' + (dur > 0 ? dur + 'm' : '<1m') + '</div>';
|
|
6079
|
+
} else if (s.status === 'in_progress' && s.started_at) {
|
|
6080
|
+
var elapsed = Math.round((Date.now() - new Date(s.started_at)) / 60000);
|
|
6081
|
+
stepTimeHtml = '<div style="font-size:9px;color:var(--accent);margin-top:2px">' + elapsed + 'm elapsed</div>';
|
|
6082
|
+
}
|
|
6083
|
+
|
|
5829
6084
|
html += '<div class="step-card ' + s.status + '" title="' + escapeHtml(s.notes || '') + '">' +
|
|
5830
6085
|
'<span class="step-num">' + s.id + '</span>' +
|
|
5831
6086
|
'<span style="font-size:10px;color:' + stepColor + ';font-weight:600;text-transform:uppercase">' + s.status.replace('_', ' ') + '</span>' +
|
|
5832
6087
|
'<div class="step-desc">' + escapeHtml(s.description) + '</div>' +
|
|
5833
6088
|
(s.assignee ? '<div class="step-assignee">👤 ' + escapeHtml(s.assignee) + '</div>' : '') +
|
|
6089
|
+
stepTimeHtml +
|
|
5834
6090
|
'</div>';
|
|
5835
6091
|
}
|
|
5836
6092
|
html += '</div>';
|
|
@@ -6111,6 +6367,7 @@ function poll() {
|
|
|
6111
6367
|
renderAgentStats();
|
|
6112
6368
|
renderThreads(cachedHistory);
|
|
6113
6369
|
if (!replayActive) renderMessages(cachedHistory);
|
|
6370
|
+
renderChannelBar(cachedHistory);
|
|
6114
6371
|
renderPinnedMessages();
|
|
6115
6372
|
renderBookmarksSidebar();
|
|
6116
6373
|
fetchActivity();
|
|
@@ -6751,8 +7008,42 @@ function showConvTemplate(tid){var pq=activeProject?'?project='+encodeURICompone
|
|
|
6751
7008
|
|
|
6752
7009
|
// ==================== v3.6: DOCS VIEW ====================
|
|
6753
7010
|
|
|
7011
|
+
function fetchDecisions() {
|
|
7012
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
7013
|
+
lttFetch('/api/decisions' + pq).then(function(r) { return r.json(); }).then(function(decisions) {
|
|
7014
|
+
var el = document.getElementById('decisions-panel');
|
|
7015
|
+
if (!decisions || !decisions.length) {
|
|
7016
|
+
el.innerHTML = '';
|
|
7017
|
+
return;
|
|
7018
|
+
}
|
|
7019
|
+
// Show most recent first
|
|
7020
|
+
var sorted = decisions.slice().reverse();
|
|
7021
|
+
var html = '<div class="decisions-section">' +
|
|
7022
|
+
'<div class="decisions-header">' +
|
|
7023
|
+
'<h3>Team Decisions</h3>' +
|
|
7024
|
+
'<span class="decisions-count">' + decisions.length + ' decision' + (decisions.length !== 1 ? 's' : '') + '</span>' +
|
|
7025
|
+
'</div>';
|
|
7026
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
7027
|
+
var d = sorted[i];
|
|
7028
|
+
var dateStr = d.decided_at ? new Date(d.decided_at).toLocaleString() : '';
|
|
7029
|
+
html += '<div class="decision-card">' +
|
|
7030
|
+
(d.topic ? '<span class="decision-topic">' + escapeHtml(d.topic) + '</span>' : '') +
|
|
7031
|
+
'<div class="decision-text">' + escapeHtml(d.decision) + '</div>' +
|
|
7032
|
+
(d.reasoning ? '<div class="decision-reasoning">' + escapeHtml(d.reasoning) + '</div>' : '') +
|
|
7033
|
+
'<div class="decision-meta">' +
|
|
7034
|
+
'<span>By ' + escapeHtml(d.decided_by || 'unknown') + '</span>' +
|
|
7035
|
+
'<span>' + dateStr + '</span>' +
|
|
7036
|
+
'</div>' +
|
|
7037
|
+
'</div>';
|
|
7038
|
+
}
|
|
7039
|
+
html += '</div>';
|
|
7040
|
+
el.innerHTML = html;
|
|
7041
|
+
}).catch(function() {});
|
|
7042
|
+
}
|
|
7043
|
+
|
|
6754
7044
|
function renderDocs() {
|
|
6755
|
-
|
|
7045
|
+
fetchDecisions();
|
|
7046
|
+
var el = document.getElementById('docs-content');
|
|
6756
7047
|
if (el.dataset.rendered) return; // already rendered
|
|
6757
7048
|
el.dataset.rendered = '1';
|
|
6758
7049
|
|