let-them-talk 4.0.2 → 4.3.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 +86 -0
- package/cli.js +1 -1
- package/dashboard.html +497 -13
- package/dashboard.js +318 -3
- package/office/agents.js +148 -4
- package/office/animation.js +68 -0
- package/office/assets.js +431 -0
- package/office/builder.js +355 -0
- package/office/campus-env.js +119 -23
- package/office/face.js +65 -0
- package/office/index.js +623 -0
- package/office/player.js +228 -6
- package/office/world-save.js +91 -0
- package/package.json +1 -1
- package/server.js +512 -83
package/dashboard.html
CHANGED
|
@@ -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;
|
|
@@ -478,6 +494,90 @@
|
|
|
478
494
|
border-color: var(--red);
|
|
479
495
|
}
|
|
480
496
|
|
|
497
|
+
.respawn-btn {
|
|
498
|
+
background: var(--green-dim);
|
|
499
|
+
color: var(--green);
|
|
500
|
+
border: 1px solid rgba(63, 185, 80, 0.3);
|
|
501
|
+
padding: 4px 10px;
|
|
502
|
+
border-radius: 6px;
|
|
503
|
+
cursor: pointer;
|
|
504
|
+
font-size: 11px;
|
|
505
|
+
font-weight: 500;
|
|
506
|
+
margin-top: 4px;
|
|
507
|
+
width: 100%;
|
|
508
|
+
text-align: center;
|
|
509
|
+
}
|
|
510
|
+
.respawn-btn:hover {
|
|
511
|
+
background: rgba(63, 185, 80, 0.25);
|
|
512
|
+
border-color: var(--green);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Respawn modal */
|
|
516
|
+
.respawn-modal-overlay {
|
|
517
|
+
position: fixed;
|
|
518
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
519
|
+
background: rgba(0,0,0,0.7);
|
|
520
|
+
z-index: 10000;
|
|
521
|
+
display: flex;
|
|
522
|
+
align-items: center;
|
|
523
|
+
justify-content: center;
|
|
524
|
+
}
|
|
525
|
+
.respawn-modal {
|
|
526
|
+
background: var(--bg-secondary);
|
|
527
|
+
border: 1px solid var(--border);
|
|
528
|
+
border-radius: 12px;
|
|
529
|
+
padding: 20px;
|
|
530
|
+
max-width: 700px;
|
|
531
|
+
width: 90%;
|
|
532
|
+
max-height: 80vh;
|
|
533
|
+
display: flex;
|
|
534
|
+
flex-direction: column;
|
|
535
|
+
}
|
|
536
|
+
.respawn-modal h3 {
|
|
537
|
+
margin: 0 0 12px 0;
|
|
538
|
+
color: var(--green);
|
|
539
|
+
font-size: 16px;
|
|
540
|
+
}
|
|
541
|
+
.respawn-modal-prompt {
|
|
542
|
+
background: var(--bg-primary);
|
|
543
|
+
border: 1px solid var(--border);
|
|
544
|
+
border-radius: 8px;
|
|
545
|
+
padding: 12px;
|
|
546
|
+
font-family: monospace;
|
|
547
|
+
font-size: 12px;
|
|
548
|
+
color: var(--text-primary);
|
|
549
|
+
white-space: pre-wrap;
|
|
550
|
+
overflow-y: auto;
|
|
551
|
+
flex: 1;
|
|
552
|
+
max-height: 50vh;
|
|
553
|
+
user-select: all;
|
|
554
|
+
}
|
|
555
|
+
.respawn-modal-actions {
|
|
556
|
+
display: flex;
|
|
557
|
+
gap: 8px;
|
|
558
|
+
margin-top: 12px;
|
|
559
|
+
justify-content: flex-end;
|
|
560
|
+
}
|
|
561
|
+
.respawn-modal-actions button {
|
|
562
|
+
padding: 6px 16px;
|
|
563
|
+
border-radius: 6px;
|
|
564
|
+
border: 1px solid var(--border);
|
|
565
|
+
cursor: pointer;
|
|
566
|
+
font-size: 12px;
|
|
567
|
+
font-weight: 500;
|
|
568
|
+
}
|
|
569
|
+
.respawn-copy-btn {
|
|
570
|
+
background: var(--green-dim);
|
|
571
|
+
color: var(--green);
|
|
572
|
+
border-color: rgba(63, 185, 80, 0.3) !important;
|
|
573
|
+
}
|
|
574
|
+
.respawn-copy-btn:hover { background: rgba(63, 185, 80, 0.25); }
|
|
575
|
+
.respawn-close-btn {
|
|
576
|
+
background: var(--bg-tertiary);
|
|
577
|
+
color: var(--text-secondary);
|
|
578
|
+
}
|
|
579
|
+
.respawn-close-btn:hover { background: var(--border); }
|
|
580
|
+
|
|
481
581
|
/* ===== PROJECT SWITCHER ===== */
|
|
482
582
|
.project-switcher {
|
|
483
583
|
padding: 12px;
|
|
@@ -757,6 +857,49 @@
|
|
|
757
857
|
.msg-to { font-size: 12px; color: var(--text-dim); }
|
|
758
858
|
.msg-time { font-size: 10px; color: var(--text-muted); }
|
|
759
859
|
|
|
860
|
+
.badge-channel {
|
|
861
|
+
background: var(--purple-dim);
|
|
862
|
+
color: var(--purple);
|
|
863
|
+
font-size: 9px;
|
|
864
|
+
padding: 1px 5px;
|
|
865
|
+
border-radius: 6px;
|
|
866
|
+
font-weight: 600;
|
|
867
|
+
cursor: pointer;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.badge-channel:hover { opacity: 0.8; }
|
|
871
|
+
|
|
872
|
+
.channel-filter-bar {
|
|
873
|
+
display: flex;
|
|
874
|
+
gap: 4px;
|
|
875
|
+
padding: 4px 12px;
|
|
876
|
+
overflow-x: auto;
|
|
877
|
+
flex-wrap: nowrap;
|
|
878
|
+
scrollbar-width: none;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.channel-filter-bar::-webkit-scrollbar { display: none; }
|
|
882
|
+
|
|
883
|
+
.channel-tab {
|
|
884
|
+
font-size: 11px;
|
|
885
|
+
padding: 3px 10px;
|
|
886
|
+
border-radius: 12px;
|
|
887
|
+
border: 1px solid var(--border);
|
|
888
|
+
background: var(--surface-2);
|
|
889
|
+
color: var(--text-dim);
|
|
890
|
+
cursor: pointer;
|
|
891
|
+
white-space: nowrap;
|
|
892
|
+
transition: all 0.15s;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.channel-tab:hover { border-color: var(--border-light); color: var(--text); }
|
|
896
|
+
|
|
897
|
+
.channel-tab.active {
|
|
898
|
+
background: var(--purple-dim);
|
|
899
|
+
color: var(--purple);
|
|
900
|
+
border-color: var(--purple);
|
|
901
|
+
}
|
|
902
|
+
|
|
760
903
|
.msg-badges {
|
|
761
904
|
display: flex;
|
|
762
905
|
gap: 4px;
|
|
@@ -2254,6 +2397,28 @@
|
|
|
2254
2397
|
color: var(--text-dim);
|
|
2255
2398
|
}
|
|
2256
2399
|
|
|
2400
|
+
.pipeline-bar {
|
|
2401
|
+
height: 4px;
|
|
2402
|
+
background: var(--surface-3);
|
|
2403
|
+
border-radius: 2px;
|
|
2404
|
+
margin-bottom: 10px;
|
|
2405
|
+
overflow: hidden;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
.pipeline-bar-fill {
|
|
2409
|
+
height: 100%;
|
|
2410
|
+
border-radius: 2px;
|
|
2411
|
+
transition: width 0.3s ease;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
.pipeline-meta {
|
|
2415
|
+
font-size: 10px;
|
|
2416
|
+
color: var(--text-muted);
|
|
2417
|
+
display: flex;
|
|
2418
|
+
gap: 12px;
|
|
2419
|
+
margin-bottom: 8px;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2257
2422
|
.pipeline-steps {
|
|
2258
2423
|
display: flex;
|
|
2259
2424
|
gap: 0;
|
|
@@ -2379,6 +2544,63 @@
|
|
|
2379
2544
|
.docs-section { padding: 14px 16px; }
|
|
2380
2545
|
}
|
|
2381
2546
|
|
|
2547
|
+
/* ===== DECISIONS LOG ===== */
|
|
2548
|
+
.decisions-section { margin-bottom: 16px; }
|
|
2549
|
+
.decisions-header {
|
|
2550
|
+
display: flex;
|
|
2551
|
+
align-items: center;
|
|
2552
|
+
justify-content: space-between;
|
|
2553
|
+
margin-bottom: 10px;
|
|
2554
|
+
}
|
|
2555
|
+
.decisions-header h3 { font-size: 15px; font-weight: 700; color: var(--accent); margin: 0; }
|
|
2556
|
+
.decisions-count { font-size: 11px; color: var(--text-muted); }
|
|
2557
|
+
|
|
2558
|
+
.decision-card {
|
|
2559
|
+
background: var(--surface);
|
|
2560
|
+
border: 1px solid var(--border);
|
|
2561
|
+
border-left: 3px solid var(--accent);
|
|
2562
|
+
border-radius: 8px;
|
|
2563
|
+
padding: 12px 14px;
|
|
2564
|
+
margin-bottom: 8px;
|
|
2565
|
+
transition: border-color 0.15s;
|
|
2566
|
+
}
|
|
2567
|
+
.decision-card:hover { border-color: var(--border-light); }
|
|
2568
|
+
|
|
2569
|
+
.decision-topic {
|
|
2570
|
+
font-size: 9px;
|
|
2571
|
+
font-weight: 700;
|
|
2572
|
+
text-transform: uppercase;
|
|
2573
|
+
letter-spacing: 0.5px;
|
|
2574
|
+
color: var(--purple);
|
|
2575
|
+
background: var(--purple-dim);
|
|
2576
|
+
padding: 1px 6px;
|
|
2577
|
+
border-radius: 6px;
|
|
2578
|
+
display: inline-block;
|
|
2579
|
+
margin-bottom: 4px;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
.decision-text {
|
|
2583
|
+
font-size: 13px;
|
|
2584
|
+
font-weight: 600;
|
|
2585
|
+
color: var(--text);
|
|
2586
|
+
margin-bottom: 4px;
|
|
2587
|
+
line-height: 1.4;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
.decision-reasoning {
|
|
2591
|
+
font-size: 12px;
|
|
2592
|
+
color: var(--text-dim);
|
|
2593
|
+
line-height: 1.5;
|
|
2594
|
+
margin-bottom: 6px;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
.decision-meta {
|
|
2598
|
+
font-size: 10px;
|
|
2599
|
+
color: var(--text-muted);
|
|
2600
|
+
display: flex;
|
|
2601
|
+
gap: 10px;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2382
2604
|
/* ===== VIRTUAL OFFICE ===== */
|
|
2383
2605
|
.office-area {
|
|
2384
2606
|
flex: 1;
|
|
@@ -3294,9 +3516,11 @@
|
|
|
3294
3516
|
<div class="search-bar" id="search-bar">
|
|
3295
3517
|
<input class="search-input" id="search-input" placeholder="Search messages... ( / )" oninput="onSearch()">
|
|
3296
3518
|
<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>
|
|
3519
|
+
<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
3520
|
<span class="search-count" id="search-count"></span>
|
|
3298
3521
|
<button class="compact-toggle" id="compact-toggle" onclick="toggleCompactMode()" title="Toggle compact view">Compact</button>
|
|
3299
3522
|
</div>
|
|
3523
|
+
<div class="channel-filter-bar" id="channel-filter-bar" style="display:none"></div>
|
|
3300
3524
|
<div class="pinned-section" id="pinned-section">
|
|
3301
3525
|
<div class="pinned-header" onclick="togglePinnedSection()"><span>Pinned Messages</span><span class="pinned-toggle" id="pinned-toggle">Hide</span></div>
|
|
3302
3526
|
<div id="pinned-list"></div>
|
|
@@ -3320,6 +3544,7 @@
|
|
|
3320
3544
|
<span style="color:var(--text-dim);font-size:9px;min-width:16px" id="office-speed-val">4</span>
|
|
3321
3545
|
<span style="color:var(--text-muted);font-size:9px;opacity:0.6;margin-left:8px" title="WASD=Move, Right-Drag=Look, Scroll=Dolly, Shift=Fast, Q/E=Down/Up">WASD + Mouse</span>
|
|
3322
3546
|
<span style="color:var(--text-dim);font-size:10px" id="office-fps"></span>
|
|
3547
|
+
<button id="fullscreen-btn" onclick="toggleFullscreen()" style="background:var(--surface-2);border:1px solid var(--border);color:var(--text-dim);padding:3px 10px;border-radius:6px;font-size:10px;cursor:pointer;font-family:inherit;margin-left:8px;transition:all 0.2s" title="Toggle fullscreen (End key to exit)">⛶ Fullscreen</button>
|
|
3323
3548
|
</div>
|
|
3324
3549
|
<div class="office-canvas-wrap">
|
|
3325
3550
|
<div id="office-3d-container"></div>
|
|
@@ -3332,7 +3557,7 @@
|
|
|
3332
3557
|
</div>
|
|
3333
3558
|
<div class="launch-area" id="launch-area"></div>
|
|
3334
3559
|
<div class="stats-area" id="stats-area"></div>
|
|
3335
|
-
<div class="docs-area" id="docs-area"></div>
|
|
3560
|
+
<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
3561
|
<button class="scroll-bottom" id="scroll-bottom" onclick="scrollToBottom()">↓<span class="new-count" id="new-msg-count" style="display:none">0</span></button>
|
|
3337
3562
|
<div class="typing-bar" id="typing-bar"></div>
|
|
3338
3563
|
|
|
@@ -3765,6 +3990,7 @@ function lttFetch(url, opts) {
|
|
|
3765
3990
|
}
|
|
3766
3991
|
|
|
3767
3992
|
var activeThread = null;
|
|
3993
|
+
var activeChannel = null; // null = all channels
|
|
3768
3994
|
var activeProject = ''; // empty = default/local
|
|
3769
3995
|
var cachedHistory = [];
|
|
3770
3996
|
var cachedAgents = {};
|
|
@@ -4036,8 +4262,10 @@ function renderAgents(agents) {
|
|
|
4036
4262
|
nudgeHtml = '<button class="nudge-btn" onclick="sendNudge(\'' + escapeHtml(name) + '\')">Send Nudge</button>';
|
|
4037
4263
|
}
|
|
4038
4264
|
var removeHtml = '';
|
|
4265
|
+
var respawnHtml = '';
|
|
4039
4266
|
if (state === 'dead') {
|
|
4040
4267
|
removeHtml = '<button class="remove-agent-btn" onclick="event.stopPropagation();removeAgent(\'' + escapeHtml(name) + '\')" title="Remove this agent">Remove</button>';
|
|
4268
|
+
respawnHtml = '<button class="respawn-btn" onclick="event.stopPropagation();respawnAgent(\'' + escapeHtml(name) + '\')" title="Generate resume prompt for this agent">🔄 Respawn</button>';
|
|
4041
4269
|
}
|
|
4042
4270
|
|
|
4043
4271
|
// Listening status — simplified: skip for dead (already shown via badge)
|
|
@@ -4077,8 +4305,10 @@ function renderAgents(agents) {
|
|
|
4077
4305
|
'<span class="agent-activity-icon ' + state + '"></span>' +
|
|
4078
4306
|
activityText +
|
|
4079
4307
|
'</div>' +
|
|
4308
|
+
(info.current_status ? '<div class="agent-status-intent" title="' + escapeHtml(info.current_status) + '">' + escapeHtml(info.current_status) + '</div>' : '') +
|
|
4080
4309
|
listenHtml +
|
|
4081
4310
|
nudgeHtml +
|
|
4311
|
+
respawnHtml +
|
|
4082
4312
|
removeHtml +
|
|
4083
4313
|
'</div>';
|
|
4084
4314
|
}
|
|
@@ -4123,6 +4353,83 @@ function removeAgent(agentName) {
|
|
|
4123
4353
|
}).catch(function(e) { console.error('Remove agent failed:', e); });
|
|
4124
4354
|
}
|
|
4125
4355
|
|
|
4356
|
+
// ==================== RESPAWN AGENT ====================
|
|
4357
|
+
|
|
4358
|
+
function respawnAgent(agentName) {
|
|
4359
|
+
var pq = projectParam();
|
|
4360
|
+
var sep = pq ? '&' : '?';
|
|
4361
|
+
lttFetch('/api/agents/' + encodeURIComponent(agentName) + '/respawn-prompt' + pq, {
|
|
4362
|
+
method: 'GET'
|
|
4363
|
+
}).then(function(r) {
|
|
4364
|
+
if (!r.ok) throw new Error('API returned ' + r.status);
|
|
4365
|
+
return r.json();
|
|
4366
|
+
}).then(function(res) {
|
|
4367
|
+
if (res.error) {
|
|
4368
|
+
alert('Respawn failed: ' + res.error);
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
showRespawnModal(agentName, res.prompt || res.resume_prompt || 'No prompt available');
|
|
4372
|
+
}).catch(function(e) {
|
|
4373
|
+
// Fallback: generate a basic prompt client-side if API not ready
|
|
4374
|
+
var fallbackPrompt = generateFallbackRespawnPrompt(agentName);
|
|
4375
|
+
showRespawnModal(agentName, fallbackPrompt);
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
function generateFallbackRespawnPrompt(agentName) {
|
|
4380
|
+
return 'Register as \'' + agentName + '\', then call get_briefing() and listen_group() to rejoin the conversation. ' +
|
|
4381
|
+
'You are resuming a previous session — call get_compressed_history() to catch up on what you missed. ' +
|
|
4382
|
+
'Check your workspace with workspace_read() for any saved state. ' +
|
|
4383
|
+
'Then call listen_group() and respond to any pending messages.';
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
function showRespawnModal(agentName, prompt) {
|
|
4387
|
+
// Remove existing modal if any
|
|
4388
|
+
dismissRespawnModal();
|
|
4389
|
+
|
|
4390
|
+
var overlay = document.createElement('div');
|
|
4391
|
+
overlay.className = 'respawn-modal-overlay';
|
|
4392
|
+
overlay.id = 'respawn-modal';
|
|
4393
|
+
overlay.onclick = function(e) { if (e.target === overlay) dismissRespawnModal(); };
|
|
4394
|
+
|
|
4395
|
+
overlay.innerHTML =
|
|
4396
|
+
'<div class="respawn-modal">' +
|
|
4397
|
+
'<h3>🔄 Respawn ' + escapeHtml(agentName) + '</h3>' +
|
|
4398
|
+
'<p style="color:var(--text-secondary);font-size:12px;margin:0 0 8px">Copy this prompt and paste it into a fresh CLI terminal to respawn the agent:</p>' +
|
|
4399
|
+
'<div class="respawn-modal-prompt" id="respawn-prompt-text">' + escapeHtml(prompt) + '</div>' +
|
|
4400
|
+
'<div class="respawn-modal-actions">' +
|
|
4401
|
+
'<button class="respawn-close-btn" onclick="dismissRespawnModal()">Close</button>' +
|
|
4402
|
+
'<button class="respawn-copy-btn" onclick="copyRespawnPrompt()">📋 Copy to Clipboard</button>' +
|
|
4403
|
+
'</div>' +
|
|
4404
|
+
'</div>';
|
|
4405
|
+
|
|
4406
|
+
document.body.appendChild(overlay);
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
function dismissRespawnModal() {
|
|
4410
|
+
var modal = document.getElementById('respawn-modal');
|
|
4411
|
+
if (modal) modal.remove();
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
function copyRespawnPrompt() {
|
|
4415
|
+
var el = document.getElementById('respawn-prompt-text');
|
|
4416
|
+
if (!el) return;
|
|
4417
|
+
var text = el.textContent;
|
|
4418
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
4419
|
+
var btn = document.querySelector('.respawn-copy-btn');
|
|
4420
|
+
if (btn) {
|
|
4421
|
+
btn.textContent = '\u2705 Copied!';
|
|
4422
|
+
setTimeout(function() { btn.innerHTML = '📋 Copy to Clipboard'; }, 2000);
|
|
4423
|
+
}
|
|
4424
|
+
}).catch(function() {
|
|
4425
|
+
// Fallback: select all text
|
|
4426
|
+
var range = document.createRange();
|
|
4427
|
+
range.selectNodeContents(el);
|
|
4428
|
+
window.getSelection().removeAllRanges();
|
|
4429
|
+
window.getSelection().addRange(range);
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4126
4433
|
// ==================== INJECT TARGETS ====================
|
|
4127
4434
|
|
|
4128
4435
|
var lastAgentKeys = '';
|
|
@@ -4273,6 +4580,14 @@ function renderMessages(messages) {
|
|
|
4273
4580
|
filtered = filtered.filter(function(m) { return !!bookmarks[m.id]; });
|
|
4274
4581
|
}
|
|
4275
4582
|
|
|
4583
|
+
// Channel filter
|
|
4584
|
+
if (activeChannel) {
|
|
4585
|
+
filtered = filtered.filter(function(m) {
|
|
4586
|
+
if (activeChannel === 'general') return !m.channel || m.channel === 'general';
|
|
4587
|
+
return m.channel === activeChannel;
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4276
4591
|
// Search filter
|
|
4277
4592
|
if (searchQuery) {
|
|
4278
4593
|
filtered = filtered.filter(function(m) {
|
|
@@ -4333,6 +4648,7 @@ function renderMessages(messages) {
|
|
|
4333
4648
|
var groupClass = isGrouped ? ' grouped' : '';
|
|
4334
4649
|
|
|
4335
4650
|
var badges = '';
|
|
4651
|
+
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
4652
|
if (m.acked) badges += '<span class="badge badge-ack">ACK</span>';
|
|
4337
4653
|
if (m.thread_id) badges += '<span class="badge badge-thread">Thread</span>';
|
|
4338
4654
|
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 +4746,39 @@ function clearThreadFilter() {
|
|
|
4430
4746
|
renderMessages(cachedHistory);
|
|
4431
4747
|
}
|
|
4432
4748
|
|
|
4749
|
+
function filterChannel(ch) {
|
|
4750
|
+
activeChannel = activeChannel === ch ? null : ch;
|
|
4751
|
+
lastMessageCount = 0;
|
|
4752
|
+
renderChannelBar(cachedHistory);
|
|
4753
|
+
renderMessages(cachedHistory);
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
function renderChannelBar(messages) {
|
|
4757
|
+
var bar = document.getElementById('channel-filter-bar');
|
|
4758
|
+
if (!messages || !messages.length) { bar.style.display = 'none'; return; }
|
|
4759
|
+
|
|
4760
|
+
// Collect unique channels from messages
|
|
4761
|
+
var channels = {};
|
|
4762
|
+
for (var i = 0; i < messages.length; i++) {
|
|
4763
|
+
var ch = messages[i].channel || 'general';
|
|
4764
|
+
channels[ch] = (channels[ch] || 0) + 1;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
var keys = Object.keys(channels);
|
|
4768
|
+
// Only show channel bar if there are non-general channels
|
|
4769
|
+
if (keys.length <= 1 && keys[0] === 'general') { bar.style.display = 'none'; return; }
|
|
4770
|
+
|
|
4771
|
+
bar.style.display = 'flex';
|
|
4772
|
+
var html = '<div class="channel-tab' + (!activeChannel ? ' active' : '') + '" onclick="filterChannel(null)">All</div>';
|
|
4773
|
+
keys.sort();
|
|
4774
|
+
for (var j = 0; j < keys.length; j++) {
|
|
4775
|
+
var name = keys[j];
|
|
4776
|
+
var active = activeChannel === name ? ' active' : '';
|
|
4777
|
+
html += '<div class="channel-tab' + active + '" onclick="filterChannel(\'' + escapeHtml(name) + '\')">#' + escapeHtml(name) + ' <span style="opacity:0.5;font-size:9px">' + channels[name] + '</span></div>';
|
|
4778
|
+
}
|
|
4779
|
+
bar.innerHTML = html;
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4433
4782
|
function toggleSidebar() {
|
|
4434
4783
|
document.getElementById('sidebar').classList.toggle('open');
|
|
4435
4784
|
document.getElementById('sidebar-overlay').classList.toggle('open');
|
|
@@ -4500,6 +4849,33 @@ function onSearch() {
|
|
|
4500
4849
|
renderMessages(cachedHistory);
|
|
4501
4850
|
}
|
|
4502
4851
|
|
|
4852
|
+
function deepSearch() {
|
|
4853
|
+
var query = document.getElementById('search-input').value.trim();
|
|
4854
|
+
if (query.length < 2) return;
|
|
4855
|
+
var pq = activeProject ? '&project=' + encodeURIComponent(activeProject) : '';
|
|
4856
|
+
var countEl = document.getElementById('search-count');
|
|
4857
|
+
countEl.textContent = 'Searching...';
|
|
4858
|
+
lttFetch('/api/search?q=' + encodeURIComponent(query) + '&limit=100' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
4859
|
+
if (data.error) { countEl.textContent = data.error; return; }
|
|
4860
|
+
countEl.textContent = data.results_count + ' deep result' + (data.results_count !== 1 ? 's' : '');
|
|
4861
|
+
// Convert search results to message format for renderMessages
|
|
4862
|
+
var messages = data.results.map(function(r) {
|
|
4863
|
+
return { id: r.id, from: r.from, to: r.to, content: r.preview, timestamp: r.timestamp, channel: r.channel || null };
|
|
4864
|
+
});
|
|
4865
|
+
lastMessageCount = 0;
|
|
4866
|
+
var el = document.getElementById('messages');
|
|
4867
|
+
if (!messages.length) {
|
|
4868
|
+
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>';
|
|
4869
|
+
return;
|
|
4870
|
+
}
|
|
4871
|
+
// Render search results directly (don't overwrite cachedHistory)
|
|
4872
|
+
searchQuery = query.toLowerCase();
|
|
4873
|
+
renderMessages(messages);
|
|
4874
|
+
}).catch(function(e) {
|
|
4875
|
+
countEl.textContent = 'Search failed';
|
|
4876
|
+
});
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4503
4879
|
// ==================== READ RECEIPTS ====================
|
|
4504
4880
|
|
|
4505
4881
|
var cachedReadReceipts = {};
|
|
@@ -5025,17 +5401,11 @@ function exportConversation() {
|
|
|
5025
5401
|
|
|
5026
5402
|
function exportJSON() {
|
|
5027
5403
|
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);
|
|
5404
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
5034
5405
|
var a = document.createElement('a');
|
|
5035
|
-
a.href =
|
|
5036
|
-
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
5406
|
+
a.href = '/api/export-json' + pq;
|
|
5407
|
+
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '-full.json';
|
|
5037
5408
|
a.click();
|
|
5038
|
-
URL.revokeObjectURL(url);
|
|
5039
5409
|
}
|
|
5040
5410
|
|
|
5041
5411
|
// ==================== VIEW SWITCHING ====================
|
|
@@ -5061,12 +5431,33 @@ function switchView(view) {
|
|
|
5061
5431
|
document.getElementById('stats-area').classList.toggle('visible', view === 'stats');
|
|
5062
5432
|
document.getElementById('docs-area').classList.toggle('visible', view === 'docs');
|
|
5063
5433
|
document.getElementById('search-bar').style.display = view === 'messages' ? 'flex' : 'none';
|
|
5434
|
+
document.getElementById('channel-filter-bar').style.display = view === 'messages' && activeChannel !== undefined ? '' : 'none';
|
|
5435
|
+
if (view === 'messages') renderChannelBar(cachedHistory);
|
|
5064
5436
|
if (view === 'tasks') fetchTasks();
|
|
5065
5437
|
if (view === 'workspaces') fetchWorkspaces();
|
|
5066
5438
|
if (view === 'workflows') fetchWorkflows();
|
|
5067
5439
|
if (view === 'docs') renderDocs();
|
|
5068
5440
|
if (view === 'office') {
|
|
5069
|
-
if (window.office3dStart)
|
|
5441
|
+
if (window.office3dStart) {
|
|
5442
|
+
window.office3dStart();
|
|
5443
|
+
} else {
|
|
5444
|
+
// Show helpful error if 3D engine failed to load (Three.js missing)
|
|
5445
|
+
var officeEl = document.getElementById('office-area');
|
|
5446
|
+
if (officeEl && !officeEl.querySelector('.office-error')) {
|
|
5447
|
+
var errDiv = document.createElement('div');
|
|
5448
|
+
errDiv.className = 'office-error';
|
|
5449
|
+
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';
|
|
5450
|
+
errDiv.innerHTML = '<div style="font-size:48px;margin-bottom:16px">🏗</div>' +
|
|
5451
|
+
'<div style="font-size:16px;font-weight:600;color:var(--text);margin-bottom:8px">3D Hub loading...</div>' +
|
|
5452
|
+
'<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>';
|
|
5453
|
+
officeEl.appendChild(errDiv);
|
|
5454
|
+
// Remove error message once 3D loads
|
|
5455
|
+
var checkInterval = setInterval(function() {
|
|
5456
|
+
if (window.office3dStart) { errDiv.remove(); clearInterval(checkInterval); window.office3dStart(); }
|
|
5457
|
+
}, 2000);
|
|
5458
|
+
setTimeout(function() { clearInterval(checkInterval); }, 30000);
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5070
5461
|
}
|
|
5071
5462
|
if (view === 'launch') renderLaunchPanel();
|
|
5072
5463
|
if (view === 'stats') fetchStats();
|
|
@@ -5162,9 +5553,20 @@ function buildTaskCard(t) {
|
|
|
5162
5553
|
'<option value="blocked"' + (t.status === 'blocked' ? ' selected' : '') + '>Blocked</option>' +
|
|
5163
5554
|
'</select>';
|
|
5164
5555
|
|
|
5556
|
+
var badgesHtml = '';
|
|
5557
|
+
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>';
|
|
5558
|
+
if (t.channel) badgesHtml += '<span class="badge-channel" onclick="filterChannel(\'' + escapeHtml(t.channel) + '\')">#' + escapeHtml(t.channel) + '</span>';
|
|
5559
|
+
|
|
5560
|
+
var notesHtml = '';
|
|
5561
|
+
if (t.notes && t.notes.length) {
|
|
5562
|
+
var lastNote = t.notes[t.notes.length - 1];
|
|
5563
|
+
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>';
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5165
5566
|
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>' +
|
|
5567
|
+
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + (badgesHtml ? ' ' + badgesHtml : '') + '</div>' +
|
|
5167
5568
|
(t.description ? '<div class="task-desc">' + escapeHtml(t.description) + '</div>' : '') +
|
|
5569
|
+
notesHtml +
|
|
5168
5570
|
'<div class="task-footer">' +
|
|
5169
5571
|
assigneeHtml +
|
|
5170
5572
|
statusOpts +
|
|
@@ -5815,22 +6217,41 @@ function renderWorkflows(workflows) {
|
|
|
5815
6217
|
var pct = Math.round((doneCount / wf.steps.length) * 100);
|
|
5816
6218
|
var statusColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
5817
6219
|
|
|
6220
|
+
var barColor = wf.status === 'completed' ? 'var(--green)' : 'var(--accent)';
|
|
6221
|
+
var metaHtml = '<div class="pipeline-meta">';
|
|
6222
|
+
if (wf.created_by) metaHtml += '<span>Created by ' + escapeHtml(wf.created_by) + '</span>';
|
|
6223
|
+
if (wf.created_at) metaHtml += '<span>' + timeAgo(wf.created_at) + '</span>';
|
|
6224
|
+
if (wf.updated_at && wf.updated_at !== wf.created_at) metaHtml += '<span>Updated ' + timeAgo(wf.updated_at) + '</span>';
|
|
6225
|
+
metaHtml += '</div>';
|
|
6226
|
+
|
|
5818
6227
|
html += '<div class="pipeline">' +
|
|
5819
6228
|
'<div class="pipeline-header">' +
|
|
5820
6229
|
'<div class="pipeline-name">' + escapeHtml(wf.name) + ' <span style="font-size:11px;color:' + statusColor + ';font-weight:600">' + escapeHtml(wf.status) + '</span></div>' +
|
|
5821
6230
|
'<div class="pipeline-progress">' + doneCount + '/' + wf.steps.length + ' (' + pct + '%)</div>' +
|
|
5822
6231
|
'</div>' +
|
|
6232
|
+
metaHtml +
|
|
6233
|
+
'<div class="pipeline-bar"><div class="pipeline-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div></div>' +
|
|
5823
6234
|
'<div class="pipeline-steps">';
|
|
5824
6235
|
|
|
5825
6236
|
for (var j = 0; j < wf.steps.length; j++) {
|
|
5826
6237
|
var s = wf.steps[j];
|
|
5827
6238
|
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
5828
6239
|
if (j > 0) html += '<span class="step-arrow">→</span>';
|
|
6240
|
+
var stepTimeHtml = '';
|
|
6241
|
+
if (s.status === 'done' && s.started_at && s.completed_at) {
|
|
6242
|
+
var dur = Math.round((new Date(s.completed_at) - new Date(s.started_at)) / 60000);
|
|
6243
|
+
stepTimeHtml = '<div style="font-size:9px;color:var(--text-muted);margin-top:2px">' + (dur > 0 ? dur + 'm' : '<1m') + '</div>';
|
|
6244
|
+
} else if (s.status === 'in_progress' && s.started_at) {
|
|
6245
|
+
var elapsed = Math.round((Date.now() - new Date(s.started_at)) / 60000);
|
|
6246
|
+
stepTimeHtml = '<div style="font-size:9px;color:var(--accent);margin-top:2px">' + elapsed + 'm elapsed</div>';
|
|
6247
|
+
}
|
|
6248
|
+
|
|
5829
6249
|
html += '<div class="step-card ' + s.status + '" title="' + escapeHtml(s.notes || '') + '">' +
|
|
5830
6250
|
'<span class="step-num">' + s.id + '</span>' +
|
|
5831
6251
|
'<span style="font-size:10px;color:' + stepColor + ';font-weight:600;text-transform:uppercase">' + s.status.replace('_', ' ') + '</span>' +
|
|
5832
6252
|
'<div class="step-desc">' + escapeHtml(s.description) + '</div>' +
|
|
5833
6253
|
(s.assignee ? '<div class="step-assignee">👤 ' + escapeHtml(s.assignee) + '</div>' : '') +
|
|
6254
|
+
stepTimeHtml +
|
|
5834
6255
|
'</div>';
|
|
5835
6256
|
}
|
|
5836
6257
|
html += '</div>';
|
|
@@ -6111,6 +6532,7 @@ function poll() {
|
|
|
6111
6532
|
renderAgentStats();
|
|
6112
6533
|
renderThreads(cachedHistory);
|
|
6113
6534
|
if (!replayActive) renderMessages(cachedHistory);
|
|
6535
|
+
renderChannelBar(cachedHistory);
|
|
6114
6536
|
renderPinnedMessages();
|
|
6115
6537
|
renderBookmarksSidebar();
|
|
6116
6538
|
fetchActivity();
|
|
@@ -6751,8 +7173,42 @@ function showConvTemplate(tid){var pq=activeProject?'?project='+encodeURICompone
|
|
|
6751
7173
|
|
|
6752
7174
|
// ==================== v3.6: DOCS VIEW ====================
|
|
6753
7175
|
|
|
7176
|
+
function fetchDecisions() {
|
|
7177
|
+
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
7178
|
+
lttFetch('/api/decisions' + pq).then(function(r) { return r.json(); }).then(function(decisions) {
|
|
7179
|
+
var el = document.getElementById('decisions-panel');
|
|
7180
|
+
if (!decisions || !decisions.length) {
|
|
7181
|
+
el.innerHTML = '';
|
|
7182
|
+
return;
|
|
7183
|
+
}
|
|
7184
|
+
// Show most recent first
|
|
7185
|
+
var sorted = decisions.slice().reverse();
|
|
7186
|
+
var html = '<div class="decisions-section">' +
|
|
7187
|
+
'<div class="decisions-header">' +
|
|
7188
|
+
'<h3>Team Decisions</h3>' +
|
|
7189
|
+
'<span class="decisions-count">' + decisions.length + ' decision' + (decisions.length !== 1 ? 's' : '') + '</span>' +
|
|
7190
|
+
'</div>';
|
|
7191
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
7192
|
+
var d = sorted[i];
|
|
7193
|
+
var dateStr = d.decided_at ? new Date(d.decided_at).toLocaleString() : '';
|
|
7194
|
+
html += '<div class="decision-card">' +
|
|
7195
|
+
(d.topic ? '<span class="decision-topic">' + escapeHtml(d.topic) + '</span>' : '') +
|
|
7196
|
+
'<div class="decision-text">' + escapeHtml(d.decision) + '</div>' +
|
|
7197
|
+
(d.reasoning ? '<div class="decision-reasoning">' + escapeHtml(d.reasoning) + '</div>' : '') +
|
|
7198
|
+
'<div class="decision-meta">' +
|
|
7199
|
+
'<span>By ' + escapeHtml(d.decided_by || 'unknown') + '</span>' +
|
|
7200
|
+
'<span>' + dateStr + '</span>' +
|
|
7201
|
+
'</div>' +
|
|
7202
|
+
'</div>';
|
|
7203
|
+
}
|
|
7204
|
+
html += '</div>';
|
|
7205
|
+
el.innerHTML = html;
|
|
7206
|
+
}).catch(function() {});
|
|
7207
|
+
}
|
|
7208
|
+
|
|
6754
7209
|
function renderDocs() {
|
|
6755
|
-
|
|
7210
|
+
fetchDecisions();
|
|
7211
|
+
var el = document.getElementById('docs-content');
|
|
6756
7212
|
if (el.dataset.rendered) return; // already rendered
|
|
6757
7213
|
el.dataset.rendered = '1';
|
|
6758
7214
|
|
|
@@ -7148,6 +7604,34 @@ function officeCamSpeed(val) {
|
|
|
7148
7604
|
}
|
|
7149
7605
|
|
|
7150
7606
|
// Player avatar mode
|
|
7607
|
+
// --- Fullscreen toggle ---
|
|
7608
|
+
function toggleFullscreen() {
|
|
7609
|
+
if (document.fullscreenElement) {
|
|
7610
|
+
document.exitFullscreen();
|
|
7611
|
+
} else {
|
|
7612
|
+
// If on 3D Hub, fullscreen only the 3D container (game mode)
|
|
7613
|
+
var officeContainer = document.getElementById('office-3d-container');
|
|
7614
|
+
if (window.activeView === 'office' && officeContainer) {
|
|
7615
|
+
officeContainer.requestFullscreen().catch(function() {});
|
|
7616
|
+
} else {
|
|
7617
|
+
document.documentElement.requestFullscreen().catch(function() {});
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
// End key exits fullscreen
|
|
7622
|
+
document.addEventListener('keydown', function(e) {
|
|
7623
|
+
if (e.code === 'End' && document.fullscreenElement) {
|
|
7624
|
+
document.exitFullscreen();
|
|
7625
|
+
}
|
|
7626
|
+
});
|
|
7627
|
+
// Update button text on fullscreen change
|
|
7628
|
+
document.addEventListener('fullscreenchange', function() {
|
|
7629
|
+
var btn = document.getElementById('fullscreen-btn');
|
|
7630
|
+
if (btn) {
|
|
7631
|
+
btn.innerHTML = document.fullscreenElement ? '✖ Exit Fullscreen' : '⛶ Fullscreen';
|
|
7632
|
+
}
|
|
7633
|
+
});
|
|
7634
|
+
|
|
7151
7635
|
function togglePlayerMode() {
|
|
7152
7636
|
var btn = document.getElementById('player-mode-btn');
|
|
7153
7637
|
if (window.office3dIsPlayerMode && window.office3dIsPlayerMode()) {
|