clay-server 2.26.0-beta.9 → 2.26.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.
@@ -6,7 +6,7 @@ import { isSchedulerOpen, closeScheduler } from './scheduler.js';
6
6
  import { hideNotes } from './sticky-notes.js';
7
7
  import { openSearch as openSessionSearch } from './session-search.js';
8
8
 
9
- var getMateWs = null;
9
+ var getWs = null;
10
10
  var currentMateId = null;
11
11
  var currentMate = null;
12
12
  var cachedSessions = [];
@@ -27,8 +27,8 @@ var searchQuery = "";
27
27
  var searchMatchIds = null;
28
28
  var searchDebounce = null;
29
29
 
30
- export function initMateSidebar(mateWsGetter) {
31
- getMateWs = mateWsGetter;
30
+ export function initMateSidebar(wsGetter) {
31
+ getWs = wsGetter;
32
32
  columnEl = document.getElementById("mate-sidebar-column");
33
33
  listEl = document.getElementById("mate-session-list");
34
34
  avatarEl = document.getElementById("mate-sidebar-avatar");
@@ -37,7 +37,7 @@ export function initMateSidebar(mateWsGetter) {
37
37
 
38
38
  if (newSessionBtn) {
39
39
  newSessionBtn.addEventListener("click", function () {
40
- var ws = getMateWs ? getMateWs() : null;
40
+ var ws = getWs ? getWs() : null;
41
41
  if (ws && ws.readyState === 1) {
42
42
  ws.send(JSON.stringify({ type: "new_session" }));
43
43
  }
@@ -73,7 +73,7 @@ export function initMateSidebar(mateWsGetter) {
73
73
  return;
74
74
  }
75
75
  searchDebounce = setTimeout(function () {
76
- var ws = getMateWs ? getMateWs() : null;
76
+ var ws = getWs ? getWs() : null;
77
77
  if (ws && ws.readyState === 1) {
78
78
  ws.send(JSON.stringify({ type: "search_sessions", query: q }));
79
79
  }
@@ -316,6 +316,10 @@ function spawnSparks(cx, cy) {
316
316
  }
317
317
  }
318
318
 
319
+ export function getMateSessions() {
320
+ return cachedSessions;
321
+ }
322
+
319
323
  export function hideMateSidebar() {
320
324
  currentMateId = null;
321
325
  currentMate = null;
@@ -498,7 +502,7 @@ function renderDebateGroup(children, groupKey) {
498
502
  hideKnowledge();
499
503
  if (isSchedulerOpen()) closeScheduler();
500
504
  hideNotes();
501
- var ws = getMateWs ? getMateWs() : null;
505
+ var ws = getWs ? getWs() : null;
502
506
  if (ws && ws.readyState === 1) {
503
507
  ws.send(JSON.stringify({ type: "switch_session", id: id }));
504
508
  }
@@ -534,7 +538,7 @@ function renderDebateGroup(children, groupKey) {
534
538
  hideKnowledge();
535
539
  if (isSchedulerOpen()) closeScheduler();
536
540
  hideNotes();
537
- var ws = getMateWs ? getMateWs() : null;
541
+ var ws = getWs ? getWs() : null;
538
542
  if (ws && ws.readyState === 1) {
539
543
  ws.send(JSON.stringify({ type: "switch_session", id: id }));
540
544
  }
@@ -580,7 +584,7 @@ function renderMateSessionItem(s) {
580
584
  if (isSchedulerOpen()) closeScheduler();
581
585
  hideNotes();
582
586
  var pendingQuery = searchQuery || "";
583
- var ws = getMateWs ? getMateWs() : null;
587
+ var ws = getWs ? getWs() : null;
584
588
  if (ws && ws.readyState === 1) {
585
589
  ws.send(JSON.stringify({ type: "switch_session", id: id }));
586
590
  }
@@ -642,7 +646,7 @@ function showMateSessionCtxMenu(anchorEl, sessionId, title) {
642
646
  overlay.querySelector(".mate-confirm-cancel").addEventListener("click", function () { overlay.remove(); });
643
647
  overlay.querySelector(".mate-confirm-delete").addEventListener("click", function () {
644
648
  overlay.remove();
645
- var ws = getMateWs ? getMateWs() : null;
649
+ var ws = getWs ? getWs() : null;
646
650
  if (ws && ws.readyState === 1) {
647
651
  ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
648
652
  }
@@ -10,6 +10,7 @@ import { closeArchive } from './sticky-notes.js';
10
10
  import { closeScheduler } from './scheduler.js';
11
11
  import { openSearch as openSessionSearch } from './session-search.js';
12
12
  import { openCommandPalette } from './command-palette.js';
13
+ import { getMateSessions } from './mate-sidebar.js';
13
14
 
14
15
  var ctx;
15
16
 
@@ -351,6 +352,7 @@ function renderLoopGroup(loopId, children, groupKey) {
351
352
 
352
353
  var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
353
354
  var isRalph = children[0].loop && children[0].loop.source === "ralph";
355
+ var isDebate = children[0].loop && children[0].loop.source === "debate";
354
356
  var isCrafting = false;
355
357
  for (var j = 0; j < children.length; j++) {
356
358
  if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
@@ -363,7 +365,10 @@ function renderLoopGroup(loopId, children, groupKey) {
363
365
 
364
366
  // Group header row
365
367
  var el = document.createElement("div");
366
- el.className = "session-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
368
+ var groupClass = "session-loop-group" + (hasActive ? " active" : "") + (expanded ? " expanded" : "");
369
+ if (isDebate) groupClass += " debate";
370
+ else if (!isRalph) groupClass += " scheduled";
371
+ el.className = groupClass;
367
372
  el.dataset.loopId = loopId;
368
373
 
369
374
  var chevron = document.createElement("button");
@@ -388,14 +393,16 @@ function renderLoopGroup(loopId, children, groupKey) {
388
393
  if (anyProcessing) {
389
394
  textHtml += '<span class="session-processing"></span>';
390
395
  }
391
- var groupIcon = isRalph ? "repeat" : "calendar-clock";
392
- textHtml += '<span class="session-loop-icon' + (isRalph ? "" : " scheduled") + '">' + iconHtml(groupIcon) + '</span>';
396
+ var groupIcon = isDebate ? "mic" : (isRalph ? "repeat" : "calendar-clock");
397
+ var iconClass = isDebate ? " debate" : (isRalph ? "" : " scheduled");
398
+ textHtml += '<span class="session-loop-icon' + iconClass + '">' + iconHtml(groupIcon) + '</span>';
393
399
  textHtml += '<span class="session-loop-name">' + escapeHtml(loopName) + '</span>';
394
400
  if (isCrafting && children.length === 1) {
395
401
  textHtml += '<span class="session-loop-badge crafting">Crafting</span>';
396
402
  } else {
397
403
  var countLabel = runCount === 1 ? children.length : runCount + (runCount === 1 ? " run" : " runs");
398
- textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + countLabel + '</span>';
404
+ var countClass = isDebate ? " debate" : (isRalph ? "" : " scheduled");
405
+ textHtml += '<span class="session-loop-count' + countClass + '">' + countLabel + '</span>';
399
406
  }
400
407
  textSpan.innerHTML = textHtml;
401
408
  el.appendChild(textSpan);
@@ -538,6 +545,9 @@ function renderSessionItem(s) {
538
545
  if (s.isProcessing) {
539
546
  textHtml += '<span class="session-processing"></span>';
540
547
  }
548
+ if (s.loop && s.loop.source === "debate") {
549
+ textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
550
+ }
541
551
  if (ctx.multiUser && s.sessionVisibility === "private") {
542
552
  textHtml += '<span class="session-private-icon" title="Private session">' + iconHtml("lock") + '</span>';
543
553
  }
@@ -598,8 +608,8 @@ export function renderSessionList(sessions) {
598
608
  var normalSessions = [];
599
609
  for (var i = 0; i < cachedSessions.length; i++) {
600
610
  var s = cachedSessions[i];
601
- if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
602
- // Task crafting sessions live in the scheduler calendar, not the main list
611
+ if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
612
+ // Task crafting sessions live in the scheduler calendar, not the main list (except debate)
603
613
  continue;
604
614
  } else if (s.loop && s.loop.loopId) {
605
615
  var startedAt = s.loop.startedAt || 0;
@@ -923,7 +933,8 @@ function renderSheetSessions(listEl) {
923
933
  (function (p) {
924
934
  var chip = document.createElement("button");
925
935
  chip.className = "mobile-chat-chip";
926
- if (p.slug === cachedCurrentSlug) chip.classList.add("active");
936
+ var isDmActive = document.body.classList.contains("mate-dm-active");
937
+ if (p.slug === cachedCurrentSlug && !isDmActive) chip.classList.add("active");
927
938
  chip.dataset.type = "project";
928
939
  chip.dataset.slug = p.slug;
929
940
 
@@ -975,6 +986,7 @@ function renderSheetSessions(listEl) {
975
986
  var mp = mate.profile || {};
976
987
  var chip = document.createElement("button");
977
988
  chip.className = "mobile-chat-chip";
989
+ if (currentDmUserId === mate.id) chip.classList.add("active");
978
990
  chip.dataset.type = "mate";
979
991
  chip.dataset.mateId = mate.id;
980
992
 
@@ -1029,10 +1041,9 @@ function renderSheetSessions(listEl) {
1029
1041
  if (type === "project") {
1030
1042
  renderMobileSessionsInto(sessionListEl);
1031
1043
  } else if (type === "mate") {
1032
- // Mate DM: just open the DM
1044
+ // Mate DM: open the DM and show mate actions
1033
1045
  if (ctx.openDm) ctx.openDm(mateId);
1034
- closeMobileSheet();
1035
- return;
1046
+ renderMateMobileActions(sessionListEl);
1036
1047
  }
1037
1048
 
1038
1049
  refreshIcons();
@@ -1051,15 +1062,23 @@ function renderSheetSessions(listEl) {
1051
1062
  var type = chip.dataset.type;
1052
1063
  if (type === "project") {
1053
1064
  var slug = chip.dataset.slug;
1054
- if (slug !== cachedCurrentSlug) {
1055
- // Switch project, show loading, keep sheet open
1065
+ var isDmNow = !!currentDmUserId;
1066
+ if (slug !== cachedCurrentSlug || isDmNow) {
1067
+ // Switch project (or exit DM back to same project)
1056
1068
  sessionListEl.innerHTML = "";
1057
- var loading = document.createElement("div");
1058
- loading.className = "mobile-chat-context-note";
1059
- loading.textContent = "Loading sessions...";
1060
- sessionListEl.appendChild(loading);
1069
+ if (slug !== cachedCurrentSlug) {
1070
+ var loading = document.createElement("div");
1071
+ loading.className = "mobile-chat-context-note";
1072
+ loading.textContent = "Loading sessions...";
1073
+ sessionListEl.appendChild(loading);
1074
+ }
1061
1075
  if (ctx.switchProject) ctx.switchProject(slug);
1062
- // renderSessionList will be called by WS, which calls refreshMobileChatSheet
1076
+ if (!isDmNow || slug !== cachedCurrentSlug) {
1077
+ // renderSessionList will be called by WS, which calls refreshMobileChatSheet
1078
+ } else {
1079
+ // Exited DM, same project - render sessions now
1080
+ renderSessionsForContext("project", slug, null);
1081
+ }
1063
1082
  } else {
1064
1083
  renderSessionsForContext("project", slug, null);
1065
1084
  }
@@ -1073,8 +1092,12 @@ function renderSheetSessions(listEl) {
1073
1092
  // Track that chat sheet is open
1074
1093
  mobileChatSheetOpen = true;
1075
1094
 
1076
- // --- Initial render: current project sessions ---
1077
- renderSessionsForContext("project", cachedCurrentSlug, null);
1095
+ // --- Initial render: show mate actions if DM active, otherwise project sessions ---
1096
+ if (currentDmUserId) {
1097
+ renderSessionsForContext("mate", null, currentDmUserId);
1098
+ } else {
1099
+ renderSessionsForContext("project", cachedCurrentSlug, null);
1100
+ }
1078
1101
  }
1079
1102
 
1080
1103
  // Helper: create a mobile session item element
@@ -1350,6 +1373,54 @@ function createMobileLoopGroup(loopId, children, groupKey) {
1350
1373
  return wrapper;
1351
1374
  }
1352
1375
 
1376
+ function renderMateMobileActions(container) {
1377
+ var newSessionBtn = document.createElement("button");
1378
+ newSessionBtn.className = "mobile-session-new";
1379
+ newSessionBtn.innerHTML = '<i data-lucide="plus" style="width:16px;height:16px"></i> New session';
1380
+ newSessionBtn.addEventListener("click", function () {
1381
+ if (ctx.ws && ctx.connected) {
1382
+ ctx.ws.send(JSON.stringify({ type: "new_session" }));
1383
+ }
1384
+ closeMobileSheet();
1385
+ });
1386
+ container.appendChild(newSessionBtn);
1387
+
1388
+ var debateBtn = document.createElement("button");
1389
+ debateBtn.className = "mobile-session-new";
1390
+ debateBtn.innerHTML = '<i data-lucide="mic" style="width:16px;height:16px"></i> New debate';
1391
+ debateBtn.addEventListener("click", function () {
1392
+ closeMobileSheet();
1393
+ var targetBtn = document.getElementById("mate-debate-btn");
1394
+ if (targetBtn) setTimeout(function () { targetBtn.click(); }, 250);
1395
+ });
1396
+ container.appendChild(debateBtn);
1397
+
1398
+ // Render mate session list
1399
+ var mateSessions = getMateSessions();
1400
+ if (mateSessions.length > 0) {
1401
+ var sorted = mateSessions.slice().sort(function (a, b) {
1402
+ return (b.lastActivity || 0) - (a.lastActivity || 0);
1403
+ });
1404
+
1405
+ var currentGroup = "";
1406
+ for (var i = 0; i < sorted.length; i++) {
1407
+ var s = sorted[i];
1408
+ var group = getDateGroup(s.lastActivity || 0);
1409
+ if (group !== currentGroup) {
1410
+ currentGroup = group;
1411
+ var header = document.createElement("div");
1412
+ header.className = "mobile-sheet-group";
1413
+ header.textContent = group;
1414
+ container.appendChild(header);
1415
+ }
1416
+ var mateItem = createMobileSessionItem(s);
1417
+ container.appendChild(mateItem);
1418
+ }
1419
+ }
1420
+
1421
+ refreshIcons();
1422
+ }
1423
+
1353
1424
  // Helper: render sorted sessions into a container with date groups (with loop session grouping)
1354
1425
  function renderMobileSessionsInto(container) {
1355
1426
  var newBtn = document.createElement("button");
@@ -1368,7 +1439,7 @@ function renderMobileSessionsInto(container) {
1368
1439
  var normalSessions = [];
1369
1440
  for (var i = 0; i < cachedSessions.length; i++) {
1370
1441
  var s = cachedSessions[i];
1371
- if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
1442
+ if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
1372
1443
  continue;
1373
1444
  } else if (s.loop && s.loop.loopId) {
1374
1445
  var startedAt = s.loop.startedAt || 0;
@@ -1424,7 +1495,7 @@ function renderMobileSessionsInto(container) {
1424
1495
  }
1425
1496
 
1426
1497
  // Refresh mobile chat sheet when session data updates (called from renderSessionList)
1427
- function refreshMobileChatSheet() {
1498
+ export function refreshMobileChatSheet() {
1428
1499
  if (!mobileChatSheetOpen) return;
1429
1500
  var sheet = document.getElementById("mobile-sheet");
1430
1501
  if (!sheet || sheet.classList.contains("hidden")) {
@@ -1441,7 +1512,10 @@ function refreshMobileChatSheet() {
1441
1512
  chip.classList.remove("active");
1442
1513
 
1443
1514
  // Update active state
1444
- if (chip.dataset.type === "project" && chip.dataset.slug === cachedCurrentSlug) {
1515
+ var isDmActive = !!currentDmUserId;
1516
+ if (chip.dataset.type === "project" && chip.dataset.slug === cachedCurrentSlug && !isDmActive) {
1517
+ chip.classList.add("active");
1518
+ } else if (chip.dataset.type === "mate" && chip.dataset.mateId === currentDmUserId) {
1445
1519
  chip.classList.add("active");
1446
1520
  }
1447
1521
 
@@ -1461,9 +1535,13 @@ function refreshMobileChatSheet() {
1461
1535
  }
1462
1536
  }
1463
1537
 
1464
- // Re-render sessions for current project
1538
+ // Re-render sessions for current context
1465
1539
  sessionListEl.innerHTML = "";
1466
- renderMobileSessionsInto(sessionListEl);
1540
+ if (currentDmUserId) {
1541
+ renderMateMobileActions(sessionListEl);
1542
+ } else {
1543
+ renderMobileSessionsInto(sessionListEl);
1544
+ }
1467
1545
 
1468
1546
  refreshIcons();
1469
1547
  }
@@ -1619,8 +1697,7 @@ function renderSheetTools(listEl) {
1619
1697
  { icon: "book-open", label: "Knowledge", action: "mate-knowledge" },
1620
1698
  { icon: "sticky-note", label: "Sticky Notes", action: "mate-sticky" },
1621
1699
  { icon: "puzzle", label: "Skills", action: "mate-skills" },
1622
- { icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" },
1623
- { icon: "mic", label: "Debate", action: "mate-debate" }
1700
+ { icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" }
1624
1701
  ] : [
1625
1702
  { icon: "folder-tree", label: "Files", action: "files" },
1626
1703
  { icon: "square-terminal", label: "Terminal", action: "terminal" },
@@ -4062,6 +4139,8 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
4062
4139
  avatar.className = "icon-strip-user-avatar" + (mate.primary ? " icon-strip-primary-mate" : "");
4063
4140
  avatar.src = mateAvatarUrl(mate, 34);
4064
4141
  avatar.alt = mp.displayName || mate.name || "Mate";
4142
+ var mateColor = (mp.avatarColor) || mate.avatarColor || "#7c3aed";
4143
+ avatar.style.background = mateColor + "30";
4065
4144
  el.appendChild(avatar);
4066
4145
 
4067
4146
  // Processing status dot (IO blink)
@@ -352,6 +352,16 @@ function createXtermForTab(tab) {
352
352
 
353
353
  xterm.open(bodyEl);
354
354
 
355
+ // WebGL addon: pixel-perfect rendering (eliminates gaps in block characters)
356
+ // Must be loaded after xterm.open() so the rendering context is available.
357
+ if (typeof WebglAddon !== "undefined") {
358
+ try {
359
+ xterm.loadAddon(new WebglAddon.WebglAddon());
360
+ } catch (e) {
361
+ // WebGL not available, fall back to DOM renderer
362
+ }
363
+ }
364
+
355
365
  // Route input to server
356
366
  xterm.onData(function (data) {
357
367
  if (ctx.ws && ctx.connected) {
@@ -481,14 +491,14 @@ function renderTabBar() {
481
491
  startRenameTab(t, label);
482
492
  });
483
493
 
484
- var closeBtn = document.createElement("button");
485
- closeBtn.className = "terminal-tab-close";
486
- closeBtn.innerHTML = '<i data-lucide="trash-2" style="width:12px;height:12px"></i>';
487
- closeBtn.addEventListener("click", function (e) {
494
+ var moreBtn = document.createElement("button");
495
+ moreBtn.className = "terminal-tab-more";
496
+ moreBtn.innerHTML = '<i data-lucide="ellipsis" style="width:12px;height:12px"></i>';
497
+ moreBtn.addEventListener("click", function (e) {
488
498
  e.stopPropagation();
489
- closeTab(t.id);
499
+ showTabContextMenu(e, t, label);
490
500
  });
491
- el.appendChild(closeBtn);
501
+ el.appendChild(moreBtn);
492
502
 
493
503
  el.addEventListener("click", function () {
494
504
  if (t.id !== activeTabId) {
@@ -504,6 +514,52 @@ function renderTabBar() {
504
514
  refreshIcons();
505
515
  }
506
516
 
517
+ // --- Tab context menu (three-dot) ---
518
+ function showTabContextMenu(e, tab, labelEl) {
519
+ var existing = document.querySelector(".terminal-tab-ctx");
520
+ if (existing) existing.remove();
521
+
522
+ var menu = document.createElement("div");
523
+ menu.className = "terminal-tab-ctx";
524
+
525
+ var renameItem = document.createElement("button");
526
+ renameItem.className = "terminal-tab-ctx-item";
527
+ renameItem.innerHTML = '<i data-lucide="pencil" style="width:13px;height:13px"></i> Rename';
528
+ renameItem.addEventListener("click", function () {
529
+ menu.remove();
530
+ startRenameTab(tab, labelEl);
531
+ });
532
+ menu.appendChild(renameItem);
533
+
534
+ var closeItem = document.createElement("button");
535
+ closeItem.className = "terminal-tab-ctx-item terminal-tab-ctx-danger";
536
+ closeItem.innerHTML = '<i data-lucide="trash-2" style="width:13px;height:13px"></i> Close';
537
+ closeItem.addEventListener("click", function () {
538
+ menu.remove();
539
+ closeTab(tab.id);
540
+ });
541
+ menu.appendChild(closeItem);
542
+
543
+ document.body.appendChild(menu);
544
+ refreshIcons();
545
+
546
+ // Position near the button
547
+ var rect = e.currentTarget.getBoundingClientRect();
548
+ menu.style.top = (rect.bottom + 4) + "px";
549
+ menu.style.left = rect.left + "px";
550
+
551
+ // Dismiss on outside click
552
+ function dismiss(ev) {
553
+ if (!menu.contains(ev.target)) {
554
+ menu.remove();
555
+ document.removeEventListener("mousedown", dismiss, true);
556
+ }
557
+ }
558
+ setTimeout(function () {
559
+ document.addEventListener("mousedown", dismiss, true);
560
+ }, 0);
561
+ }
562
+
507
563
  // --- Rename tab inline ---
508
564
  function startRenameTab(tab, labelEl) {
509
565
  var input = document.createElement("input");
@@ -205,7 +205,7 @@ export function renderAskUserQuestion(toolId, input) {
205
205
 
206
206
  var avi = document.createElement("img");
207
207
  avi.className = "dm-bubble-avatar";
208
- avi.src = identity.avatar;
208
+ avi.src = mateAvatar;
209
209
  container.appendChild(avi);
210
210
 
211
211
  mateContentWrap = document.createElement("div");
@@ -214,7 +214,7 @@ export function renderAskUserQuestion(toolId, input) {
214
214
  var headerEl = document.createElement("div");
215
215
  headerEl.className = "dm-bubble-header";
216
216
  headerEl.innerHTML =
217
- '<span class="dm-bubble-name">' + escapeHtml(identity.name) + '</span>' +
217
+ '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
218
218
  '<span class="dm-bubble-time">' + String(new Date().getHours()).padStart(2, "0") + ":" + String(new Date().getMinutes()).padStart(2, "0") + '</span>';
219
219
  mateContentWrap.appendChild(headerEl);
220
220
  }