clay-server 2.12.0 → 2.13.0-beta.1
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/lib/daemon.js +14 -0
- package/lib/dm.js +3 -3
- package/lib/mates.js +222 -0
- package/lib/project.js +83 -2
- package/lib/public/app.js +273 -10
- package/lib/public/css/base.css +1 -0
- package/lib/public/css/mates.css +1018 -0
- package/lib/public/index.html +238 -0
- package/lib/public/modules/mate-knowledge.js +222 -0
- package/lib/public/modules/mate-sidebar.js +329 -0
- package/lib/public/modules/mate-wizard.js +265 -0
- package/lib/public/modules/profile.js +207 -0
- package/lib/public/modules/sidebar.js +143 -3
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +4 -0
- package/lib/server.js +110 -3
- package/lib/sessions.js +27 -0
- package/package.json +1 -1
|
@@ -376,3 +376,210 @@ export function getProfile() {
|
|
|
376
376
|
export function getProfileLang() {
|
|
377
377
|
return profile.lang;
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
// --- Mate profile popover (reuses same UI minus language) ---
|
|
381
|
+
var matePopoverEl = null;
|
|
382
|
+
var mateSaveTimer = null;
|
|
383
|
+
var matePreviewSeed = '';
|
|
384
|
+
|
|
385
|
+
export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
|
|
386
|
+
if (matePopoverEl) {
|
|
387
|
+
hideMatePopover();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
var mp = mateData.profile || {};
|
|
392
|
+
var mateName = mp.displayName || mateData.name || '';
|
|
393
|
+
var mateColor = mp.avatarColor || '#7c3aed';
|
|
394
|
+
var mateStyle = mp.avatarStyle || 'bottts';
|
|
395
|
+
var mateSeed = mp.avatarSeed || mateData.id || 'mate';
|
|
396
|
+
matePreviewSeed = mateSeed;
|
|
397
|
+
|
|
398
|
+
matePopoverEl = document.createElement('div');
|
|
399
|
+
matePopoverEl.className = 'profile-popover mate-profile-popover';
|
|
400
|
+
|
|
401
|
+
var html = '';
|
|
402
|
+
|
|
403
|
+
// Banner + close
|
|
404
|
+
html += '<div class="profile-banner" style="background:' + mateColor + '">';
|
|
405
|
+
html += '<button class="profile-close-btn">×</button>';
|
|
406
|
+
html += '</div>';
|
|
407
|
+
|
|
408
|
+
// Avatar row
|
|
409
|
+
html += '<div class="profile-avatar-row">';
|
|
410
|
+
html += '<div class="profile-popover-avatar">';
|
|
411
|
+
html += '<img class="profile-popover-avatar-img" src="' + avatarUrl(mateStyle, mateSeed, 80) + '" alt="avatar">';
|
|
412
|
+
html += '</div>';
|
|
413
|
+
html += '<div class="profile-name-display">' + escapeAttr(mateName || 'New Mate') + '</div>';
|
|
414
|
+
html += '</div>';
|
|
415
|
+
|
|
416
|
+
// Body
|
|
417
|
+
html += '<div class="profile-popover-body">';
|
|
418
|
+
|
|
419
|
+
// Name
|
|
420
|
+
html += '<div class="profile-field">';
|
|
421
|
+
html += '<label class="profile-field-label">Display Name</label>';
|
|
422
|
+
html += '<input type="text" class="profile-field-input" id="mate-profile-name" value="' + escapeAttr(mateName) + '" placeholder="Name your mate..." maxlength="50" spellcheck="false" autocomplete="off">';
|
|
423
|
+
html += '</div>';
|
|
424
|
+
|
|
425
|
+
// Avatar picker
|
|
426
|
+
html += '<div class="profile-field">';
|
|
427
|
+
html += '<label class="profile-field-label">Avatar <button class="profile-shuffle-btn" title="Shuffle">' + iconHtml('shuffle') + '</button></label>';
|
|
428
|
+
html += '<div class="profile-avatar-grid">';
|
|
429
|
+
for (var j = 0; j < AVATAR_STYLES.length; j++) {
|
|
430
|
+
var st = AVATAR_STYLES[j];
|
|
431
|
+
var activeS = (mateStyle === st.id) ? ' profile-avatar-option-active' : '';
|
|
432
|
+
html += '<button class="profile-avatar-option' + activeS + '" data-style="' + st.id + '" title="' + st.name + '">';
|
|
433
|
+
html += '<img src="' + avatarUrl(st.id, mateSeed, 40) + '" alt="' + st.name + '">';
|
|
434
|
+
html += '</button>';
|
|
435
|
+
}
|
|
436
|
+
html += '</div>';
|
|
437
|
+
html += '</div>';
|
|
438
|
+
|
|
439
|
+
// Color
|
|
440
|
+
html += '<div class="profile-field">';
|
|
441
|
+
html += '<label class="profile-field-label">Color</label>';
|
|
442
|
+
html += '<div class="profile-color-grid">';
|
|
443
|
+
for (var k = 0; k < COLORS.length; k++) {
|
|
444
|
+
var c = COLORS[k];
|
|
445
|
+
var activeC = (mateColor === c) ? ' profile-color-active' : '';
|
|
446
|
+
html += '<button class="profile-color-swatch' + activeC + '" data-color="' + c + '" style="background:' + c + '"></button>';
|
|
447
|
+
}
|
|
448
|
+
html += '</div>';
|
|
449
|
+
html += '</div>';
|
|
450
|
+
|
|
451
|
+
html += '</div>'; // close body
|
|
452
|
+
|
|
453
|
+
matePopoverEl.innerHTML = html;
|
|
454
|
+
|
|
455
|
+
// State tracker
|
|
456
|
+
var mateProfile = {
|
|
457
|
+
displayName: mateName,
|
|
458
|
+
avatarStyle: mateStyle,
|
|
459
|
+
avatarSeed: mateSeed,
|
|
460
|
+
avatarColor: mateColor,
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
function debouncedMateUpdate() {
|
|
464
|
+
if (mateSaveTimer) clearTimeout(mateSaveTimer);
|
|
465
|
+
mateSaveTimer = setTimeout(function() {
|
|
466
|
+
if (onUpdate) onUpdate({
|
|
467
|
+
name: mateProfile.displayName,
|
|
468
|
+
profile: {
|
|
469
|
+
displayName: mateProfile.displayName,
|
|
470
|
+
avatarStyle: mateProfile.avatarStyle,
|
|
471
|
+
avatarSeed: mateProfile.avatarSeed,
|
|
472
|
+
avatarColor: mateProfile.avatarColor,
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
mateSaveTimer = null;
|
|
476
|
+
}, 400);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function updateMatePopoverHeader() {
|
|
480
|
+
if (!matePopoverEl) return;
|
|
481
|
+
var img = matePopoverEl.querySelector('.profile-popover-avatar-img');
|
|
482
|
+
var nd = matePopoverEl.querySelector('.profile-name-display');
|
|
483
|
+
if (img) img.src = avatarUrl(mateProfile.avatarStyle, mateProfile.avatarSeed, 80);
|
|
484
|
+
if (nd) nd.textContent = mateProfile.displayName || 'New Mate';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Events
|
|
488
|
+
matePopoverEl.querySelector('.profile-close-btn').addEventListener('click', function(e) {
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
hideMatePopover();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
var nameInput = matePopoverEl.querySelector('#mate-profile-name');
|
|
494
|
+
nameInput.addEventListener('input', function() {
|
|
495
|
+
mateProfile.displayName = nameInput.value.trim();
|
|
496
|
+
updateMatePopoverHeader();
|
|
497
|
+
debouncedMateUpdate();
|
|
498
|
+
});
|
|
499
|
+
nameInput.addEventListener('keydown', function(e) {
|
|
500
|
+
if (e.key === 'Enter') { e.preventDefault(); hideMatePopover(); }
|
|
501
|
+
e.stopPropagation();
|
|
502
|
+
});
|
|
503
|
+
nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
|
|
504
|
+
nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
|
|
505
|
+
|
|
506
|
+
// Avatar style
|
|
507
|
+
matePopoverEl.querySelectorAll('.profile-avatar-option[data-style]').forEach(function(btn) {
|
|
508
|
+
btn.addEventListener('click', function() {
|
|
509
|
+
mateProfile.avatarStyle = btn.dataset.style;
|
|
510
|
+
mateProfile.avatarSeed = matePreviewSeed;
|
|
511
|
+
updateMatePopoverHeader();
|
|
512
|
+
matePopoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
|
|
513
|
+
b.classList.remove('profile-avatar-option-active');
|
|
514
|
+
});
|
|
515
|
+
btn.classList.add('profile-avatar-option-active');
|
|
516
|
+
debouncedMateUpdate();
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Shuffle
|
|
521
|
+
matePopoverEl.querySelector('.profile-shuffle-btn').addEventListener('click', function(e) {
|
|
522
|
+
e.stopPropagation();
|
|
523
|
+
matePreviewSeed = Math.random().toString(36).substring(2, 10);
|
|
524
|
+
if (!matePopoverEl) return;
|
|
525
|
+
matePopoverEl.querySelectorAll('.profile-avatar-option[data-style] img').forEach(function(img) {
|
|
526
|
+
var style = img.closest('.profile-avatar-option').dataset.style;
|
|
527
|
+
img.src = avatarUrl(style, matePreviewSeed, 40);
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Color swatches
|
|
532
|
+
matePopoverEl.querySelectorAll('.profile-color-swatch').forEach(function(btn) {
|
|
533
|
+
btn.addEventListener('click', function() {
|
|
534
|
+
mateProfile.avatarColor = btn.dataset.color;
|
|
535
|
+
var bannerEl = matePopoverEl.querySelector('.profile-banner');
|
|
536
|
+
if (bannerEl) bannerEl.style.background = mateProfile.avatarColor;
|
|
537
|
+
matePopoverEl.querySelectorAll('.profile-color-swatch').forEach(function(b) {
|
|
538
|
+
b.classList.remove('profile-color-active');
|
|
539
|
+
});
|
|
540
|
+
btn.classList.add('profile-color-active');
|
|
541
|
+
debouncedMateUpdate();
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
matePopoverEl.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
546
|
+
|
|
547
|
+
// Position near anchor
|
|
548
|
+
document.body.appendChild(matePopoverEl);
|
|
549
|
+
refreshIcons();
|
|
550
|
+
|
|
551
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
552
|
+
matePopoverEl.style.position = 'fixed';
|
|
553
|
+
matePopoverEl.style.left = (rect.right + 8) + 'px';
|
|
554
|
+
matePopoverEl.style.zIndex = '9999';
|
|
555
|
+
// Align bottom of popover with bottom of anchor icon
|
|
556
|
+
var popHeight = matePopoverEl.offsetHeight;
|
|
557
|
+
var bottomAligned = rect.bottom - popHeight;
|
|
558
|
+
matePopoverEl.style.top = Math.max(8, bottomAligned) + 'px';
|
|
559
|
+
|
|
560
|
+
setTimeout(function() {
|
|
561
|
+
document.addEventListener('click', closeMateOnOutside);
|
|
562
|
+
document.addEventListener('keydown', closeMateOnEscape);
|
|
563
|
+
}, 0);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function closeMateOnOutside(e) {
|
|
567
|
+
if (matePopoverEl && !matePopoverEl.contains(e.target)) {
|
|
568
|
+
hideMatePopover();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function closeMateOnEscape(e) {
|
|
573
|
+
if (e.key === 'Escape' && matePopoverEl) {
|
|
574
|
+
hideMatePopover();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function hideMatePopover() {
|
|
579
|
+
if (matePopoverEl) {
|
|
580
|
+
matePopoverEl.remove();
|
|
581
|
+
matePopoverEl = null;
|
|
582
|
+
}
|
|
583
|
+
document.removeEventListener('click', closeMateOnOutside);
|
|
584
|
+
document.removeEventListener('keydown', closeMateOnEscape);
|
|
585
|
+
}
|
|
@@ -3,6 +3,7 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
3
3
|
import { openProjectSettings } from './project-settings.js';
|
|
4
4
|
import { triggerShare } from './qrcode.js';
|
|
5
5
|
import { parseEmojis } from './markdown.js';
|
|
6
|
+
import { showMateProfilePopover } from './profile.js';
|
|
6
7
|
|
|
7
8
|
var ctx;
|
|
8
9
|
|
|
@@ -1485,7 +1486,7 @@ function showUserCtxMenu(anchorEl, user) {
|
|
|
1485
1486
|
// even if the user was only visible via cachedDmConversations (not favorites)
|
|
1486
1487
|
cachedDmRemovedUsers[user.id] = true;
|
|
1487
1488
|
if (ctx.onDmRemoveUser) ctx.onDmRemoveUser(user.id);
|
|
1488
|
-
renderUserStrip(cachedAllUsers, cachedOnlineUserIds, cachedMyUserId, cachedDmFavorites, cachedDmConversations, cachedDmUnread, cachedDmRemovedUsers);
|
|
1489
|
+
renderUserStrip(cachedAllUsers, cachedOnlineUserIds, cachedMyUserId, cachedDmFavorites, cachedDmConversations, cachedDmUnread, cachedDmRemovedUsers, cachedMates);
|
|
1489
1490
|
if (ctx.sendWs) {
|
|
1490
1491
|
ctx.sendWs({ type: "dm_remove_favorite", targetUserId: user.id });
|
|
1491
1492
|
}
|
|
@@ -1522,6 +1523,65 @@ function handleUserCtxOutsideClick(e) {
|
|
|
1522
1523
|
}
|
|
1523
1524
|
}
|
|
1524
1525
|
|
|
1526
|
+
function showMateCtxMenu(anchorEl, mate) {
|
|
1527
|
+
closeUserCtxMenu();
|
|
1528
|
+
closeProjectCtxMenu();
|
|
1529
|
+
|
|
1530
|
+
var menu = document.createElement("div");
|
|
1531
|
+
menu.className = "project-ctx-menu";
|
|
1532
|
+
|
|
1533
|
+
// Edit Profile item
|
|
1534
|
+
var editItem = document.createElement("button");
|
|
1535
|
+
editItem.className = "project-ctx-item";
|
|
1536
|
+
editItem.innerHTML = iconHtml("edit-2") + " <span>Edit Profile</span>";
|
|
1537
|
+
editItem.addEventListener("click", function (e) {
|
|
1538
|
+
e.stopPropagation();
|
|
1539
|
+
closeUserCtxMenu();
|
|
1540
|
+
showMateProfilePopover(anchorEl, mate, function (updates) {
|
|
1541
|
+
if (ctx.sendWs) {
|
|
1542
|
+
ctx.sendWs({ type: "mate_update", mateId: mate.id, updates: updates });
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
});
|
|
1546
|
+
menu.appendChild(editItem);
|
|
1547
|
+
|
|
1548
|
+
var removeItem = document.createElement("button");
|
|
1549
|
+
removeItem.className = "project-ctx-item project-ctx-delete";
|
|
1550
|
+
removeItem.innerHTML = iconHtml("trash-2") + " <span>Remove Mate</span>";
|
|
1551
|
+
removeItem.addEventListener("click", function (e) {
|
|
1552
|
+
e.stopPropagation();
|
|
1553
|
+
var iconRect = anchorEl.getBoundingClientRect();
|
|
1554
|
+
spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
|
|
1555
|
+
closeUserCtxMenu();
|
|
1556
|
+
if (ctx.sendWs) {
|
|
1557
|
+
ctx.sendWs({ type: "mate_delete", mateId: mate.id });
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
menu.appendChild(removeItem);
|
|
1561
|
+
|
|
1562
|
+
document.body.appendChild(menu);
|
|
1563
|
+
userCtxMenu = menu;
|
|
1564
|
+
refreshIcons();
|
|
1565
|
+
|
|
1566
|
+
requestAnimationFrame(function () {
|
|
1567
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
1568
|
+
menu.style.position = "fixed";
|
|
1569
|
+
menu.style.left = (rect.right + 6) + "px";
|
|
1570
|
+
menu.style.top = rect.top + "px";
|
|
1571
|
+
var menuRect = menu.getBoundingClientRect();
|
|
1572
|
+
if (menuRect.right > window.innerWidth - 8) {
|
|
1573
|
+
menu.style.left = (rect.left - menuRect.width - 6) + "px";
|
|
1574
|
+
}
|
|
1575
|
+
if (menuRect.bottom > window.innerHeight - 8) {
|
|
1576
|
+
menu.style.top = (window.innerHeight - menuRect.height - 8) + "px";
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
setTimeout(function () {
|
|
1581
|
+
document.addEventListener("click", handleUserCtxOutsideClick, true);
|
|
1582
|
+
}, 0);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1525
1585
|
// --- Project context menu ---
|
|
1526
1586
|
var projectCtxMenu = null;
|
|
1527
1587
|
|
|
@@ -2743,8 +2803,10 @@ var currentDmUserId = null;
|
|
|
2743
2803
|
var dmPickerOpen = false;
|
|
2744
2804
|
|
|
2745
2805
|
var cachedDmRemovedUsers = {};
|
|
2806
|
+
var cachedMates = [];
|
|
2746
2807
|
|
|
2747
|
-
export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites, dmConversations, dmUnread, dmRemovedUsers) {
|
|
2808
|
+
export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites, dmConversations, dmUnread, dmRemovedUsers, matesList) {
|
|
2809
|
+
cachedMates = matesList || cachedMates || [];
|
|
2748
2810
|
cachedAllUsers = allUsers || [];
|
|
2749
2811
|
cachedOnlineUserIds = onlineUserIds || [];
|
|
2750
2812
|
cachedDmFavorites = dmFavorites || [];
|
|
@@ -2825,6 +2887,62 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
2825
2887
|
})(others[i]);
|
|
2826
2888
|
}
|
|
2827
2889
|
|
|
2890
|
+
// Render mates
|
|
2891
|
+
for (var mi = 0; mi < cachedMates.length; mi++) {
|
|
2892
|
+
(function (mate) {
|
|
2893
|
+
var mp = mate.profile || {};
|
|
2894
|
+
var el = document.createElement("div");
|
|
2895
|
+
el.className = "icon-strip-user icon-strip-mate";
|
|
2896
|
+
el.dataset.userId = mate.id;
|
|
2897
|
+
if (mate.id === currentDmUserId) el.classList.add("active");
|
|
2898
|
+
|
|
2899
|
+
var pill = document.createElement("span");
|
|
2900
|
+
pill.className = "icon-strip-pill";
|
|
2901
|
+
el.appendChild(pill);
|
|
2902
|
+
|
|
2903
|
+
var avatar = document.createElement("img");
|
|
2904
|
+
avatar.className = "icon-strip-user-avatar";
|
|
2905
|
+
avatar.src = "https://api.dicebear.com/9.x/" + (mp.avatarStyle || "bottts") + "/svg?seed=" + encodeURIComponent(mp.avatarSeed || mate.id) + "&size=34";
|
|
2906
|
+
avatar.alt = mp.displayName || mate.name || "Mate";
|
|
2907
|
+
el.appendChild(avatar);
|
|
2908
|
+
|
|
2909
|
+
// Mate badge (bot icon)
|
|
2910
|
+
var mateBadge = document.createElement("span");
|
|
2911
|
+
mateBadge.className = "icon-strip-user-mate-badge";
|
|
2912
|
+
mateBadge.innerHTML = iconHtml("bot");
|
|
2913
|
+
el.appendChild(mateBadge);
|
|
2914
|
+
|
|
2915
|
+
var badge = document.createElement("span");
|
|
2916
|
+
badge.className = "icon-strip-user-badge";
|
|
2917
|
+
badge.dataset.userId = mate.id;
|
|
2918
|
+
el.appendChild(badge);
|
|
2919
|
+
|
|
2920
|
+
// Tooltip
|
|
2921
|
+
var displayName = mp.displayName || mate.name || "New Mate";
|
|
2922
|
+
el.addEventListener("mouseenter", function () { showIconTooltip(el, displayName); });
|
|
2923
|
+
el.addEventListener("mouseleave", hideIconTooltip);
|
|
2924
|
+
|
|
2925
|
+
// Click: open DM with mate
|
|
2926
|
+
el.addEventListener("click", function () {
|
|
2927
|
+
if (ctx.openDm) ctx.openDm(mate.id);
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
// Right-click: context menu for mate
|
|
2931
|
+
el.addEventListener("contextmenu", function (e) {
|
|
2932
|
+
e.preventDefault();
|
|
2933
|
+
e.stopPropagation();
|
|
2934
|
+
showMateCtxMenu(el, mate);
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
container.appendChild(el);
|
|
2938
|
+
})(cachedMates[mi]);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// Show container if we have mates even with no other users
|
|
2942
|
+
if (cachedMates.length > 0) {
|
|
2943
|
+
container.classList.remove("hidden");
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2828
2946
|
// Add user (+) button
|
|
2829
2947
|
var addBtn = document.createElement("button");
|
|
2830
2948
|
addBtn.className = "icon-strip-invite";
|
|
@@ -2833,7 +2951,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
2833
2951
|
e.stopPropagation();
|
|
2834
2952
|
toggleDmUserPicker(addBtn);
|
|
2835
2953
|
});
|
|
2836
|
-
addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add
|
|
2954
|
+
addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add user or create mate"); });
|
|
2837
2955
|
addBtn.addEventListener("mouseleave", hideIconTooltip);
|
|
2838
2956
|
container.appendChild(addBtn);
|
|
2839
2957
|
refreshIcons();
|
|
@@ -2917,6 +3035,28 @@ function toggleDmUserPicker(anchorEl) {
|
|
|
2917
3035
|
}
|
|
2918
3036
|
}
|
|
2919
3037
|
|
|
3038
|
+
// Create Mate option
|
|
3039
|
+
var createMateEl = document.createElement("div");
|
|
3040
|
+
createMateEl.className = "dm-user-picker-create-mate";
|
|
3041
|
+
createMateEl.innerHTML = iconHtml("bot") + " <span>Create a Mate</span>";
|
|
3042
|
+
createMateEl.addEventListener("click", function () {
|
|
3043
|
+
closeDmUserPicker();
|
|
3044
|
+
if (ctx.openMateWizard) ctx.openMateWizard();
|
|
3045
|
+
});
|
|
3046
|
+
picker.appendChild(createMateEl);
|
|
3047
|
+
|
|
3048
|
+
// Divider
|
|
3049
|
+
var divider = document.createElement("div");
|
|
3050
|
+
divider.style.borderTop = "1px solid var(--border, #333)";
|
|
3051
|
+
divider.style.margin = "4px 0";
|
|
3052
|
+
picker.appendChild(divider);
|
|
3053
|
+
|
|
3054
|
+
// Section label for users
|
|
3055
|
+
var sectionLabel = document.createElement("div");
|
|
3056
|
+
sectionLabel.className = "dm-user-picker-section";
|
|
3057
|
+
sectionLabel.textContent = "Users";
|
|
3058
|
+
picker.appendChild(sectionLabel);
|
|
3059
|
+
|
|
2920
3060
|
renderPickerList("");
|
|
2921
3061
|
searchInput.addEventListener("input", function () {
|
|
2922
3062
|
renderPickerList(searchInput.value);
|
package/lib/public/style.css
CHANGED
package/lib/sdk-bridge.js
CHANGED
|
@@ -189,6 +189,10 @@ function createSDKBridge(opts) {
|
|
|
189
189
|
if (session.responsePreview.length < 200) {
|
|
190
190
|
session.responsePreview += evt.delta.text;
|
|
191
191
|
}
|
|
192
|
+
// Accumulate text for mate DM response
|
|
193
|
+
if (typeof session._mateDmResponseText === "string") {
|
|
194
|
+
session._mateDmResponseText += evt.delta.text;
|
|
195
|
+
}
|
|
192
196
|
sendAndRecord(session, { type: "delta", text: evt.delta.text });
|
|
193
197
|
} else if (evt.delta.type === "input_json_delta" && session.blocks[idx]) {
|
|
194
198
|
session.blocks[idx].inputJson += evt.delta.partial_json;
|
package/lib/server.js
CHANGED
|
@@ -8,6 +8,7 @@ var smtp = require("./smtp");
|
|
|
8
8
|
var { createProjectContext } = require("./project");
|
|
9
9
|
var users = require("./users");
|
|
10
10
|
var dm = require("./dm");
|
|
11
|
+
var mates = require("./mates");
|
|
11
12
|
|
|
12
13
|
var { CONFIG_DIR } = require("./config");
|
|
13
14
|
var { provisionLinuxUser } = require("./os-users");
|
|
@@ -2135,8 +2136,9 @@ function createServer(opts) {
|
|
|
2135
2136
|
}
|
|
2136
2137
|
|
|
2137
2138
|
// --- Project management ---
|
|
2138
|
-
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta) {
|
|
2139
|
+
function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
|
|
2139
2140
|
if (projects.has(slug)) return false;
|
|
2141
|
+
var extra = extraOpts || {};
|
|
2140
2142
|
var ctx = createProjectContext({
|
|
2141
2143
|
cwd: cwd,
|
|
2142
2144
|
slug: slug,
|
|
@@ -2144,6 +2146,7 @@ function createServer(opts) {
|
|
|
2144
2146
|
icon: icon || null,
|
|
2145
2147
|
projectOwnerId: projectOwnerId || null,
|
|
2146
2148
|
worktreeMeta: worktreeMeta || null,
|
|
2149
|
+
isMate: extra.isMate || false,
|
|
2147
2150
|
pushModule: pushModule,
|
|
2148
2151
|
debug: debug,
|
|
2149
2152
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -2285,12 +2288,41 @@ function createServer(opts) {
|
|
|
2285
2288
|
};
|
|
2286
2289
|
}
|
|
2287
2290
|
}
|
|
2288
|
-
|
|
2291
|
+
// Include mates in the list
|
|
2292
|
+
var mateList = mates.getAllMates();
|
|
2293
|
+
ws.send(JSON.stringify({ type: "dm_list", dms: dmList, mates: mateList }));
|
|
2289
2294
|
return;
|
|
2290
2295
|
}
|
|
2291
2296
|
|
|
2292
2297
|
if (msg.type === "dm_open") {
|
|
2293
2298
|
if (!msg.targetUserId) return;
|
|
2299
|
+
|
|
2300
|
+
// Check if target is a mate
|
|
2301
|
+
if (mates.isMate(msg.targetUserId)) {
|
|
2302
|
+
var mate = mates.getMate(msg.targetUserId);
|
|
2303
|
+
if (!mate) return;
|
|
2304
|
+
var mp = mate.profile || {};
|
|
2305
|
+
ws.send(JSON.stringify({
|
|
2306
|
+
type: "dm_history",
|
|
2307
|
+
dmKey: "mate:" + mate.id,
|
|
2308
|
+
messages: dm.loadHistory("mate:" + mate.id),
|
|
2309
|
+
isMate: true,
|
|
2310
|
+
projectSlug: "mate-" + mate.id,
|
|
2311
|
+
targetUser: {
|
|
2312
|
+
id: mate.id,
|
|
2313
|
+
displayName: mp.displayName || mate.name || "New Mate",
|
|
2314
|
+
username: mate.id,
|
|
2315
|
+
avatarStyle: mp.avatarStyle || "bottts",
|
|
2316
|
+
avatarSeed: mp.avatarSeed || mate.id,
|
|
2317
|
+
avatarColor: mp.avatarColor || "#6c5ce7",
|
|
2318
|
+
isMate: true,
|
|
2319
|
+
mateStatus: mate.status,
|
|
2320
|
+
seedData: mate.seedData || {},
|
|
2321
|
+
},
|
|
2322
|
+
}));
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2294
2326
|
var result = dm.openDm(userId, msg.targetUserId);
|
|
2295
2327
|
var targetUser = users.findUserById(msg.targetUserId);
|
|
2296
2328
|
var tp = targetUser ? (targetUser.profile || {}) : {};
|
|
@@ -2330,8 +2362,20 @@ function createServer(opts) {
|
|
|
2330
2362
|
|
|
2331
2363
|
if (msg.type === "dm_send") {
|
|
2332
2364
|
if (!msg.dmKey || !msg.text) return;
|
|
2333
|
-
// Verify sender is a participant
|
|
2334
2365
|
var parts = msg.dmKey.split(":");
|
|
2366
|
+
|
|
2367
|
+
// Handle mate DM: dmKey is "mate:mate_xxx"
|
|
2368
|
+
if (parts[0] === "mate" && mates.isMate(parts[1])) {
|
|
2369
|
+
var mate = mates.getMate(parts[1]);
|
|
2370
|
+
if (!mate) return;
|
|
2371
|
+
// Verify sender is the mate's creator
|
|
2372
|
+
if (mate.createdBy !== userId) return;
|
|
2373
|
+
var message = dm.sendMessage(msg.dmKey, userId, msg.text);
|
|
2374
|
+
ws.send(JSON.stringify({ type: "dm_message", dmKey: msg.dmKey, message: message }));
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// Regular DM: verify sender is a participant
|
|
2335
2379
|
if (parts.indexOf(userId) === -1) return;
|
|
2336
2380
|
var message = dm.sendMessage(msg.dmKey, userId, msg.text);
|
|
2337
2381
|
// Send confirmation to sender
|
|
@@ -2383,6 +2427,69 @@ function createServer(opts) {
|
|
|
2383
2427
|
}));
|
|
2384
2428
|
return;
|
|
2385
2429
|
}
|
|
2430
|
+
|
|
2431
|
+
// --- Mate handlers ---
|
|
2432
|
+
|
|
2433
|
+
if (msg.type === "mate_create") {
|
|
2434
|
+
if (!msg.seedData) return;
|
|
2435
|
+
try {
|
|
2436
|
+
var mate = mates.createMate(msg.seedData, userId);
|
|
2437
|
+
// Register mate as a project
|
|
2438
|
+
var mateDir = path.join(mates.MATES_DIR, mate.id);
|
|
2439
|
+
var mateSlug = "mate-" + mate.id;
|
|
2440
|
+
var mateName = (mate.profile && mate.profile.displayName) || mate.name || "New Mate";
|
|
2441
|
+
addProject(mateDir, mateSlug, mateName, null, mate.createdBy, null, { isMate: true });
|
|
2442
|
+
ws.send(JSON.stringify({ type: "mate_created", mate: mate, projectSlug: mateSlug }));
|
|
2443
|
+
} catch (e) {
|
|
2444
|
+
ws.send(JSON.stringify({ type: "mate_error", error: "Failed to create mate: " + e.message }));
|
|
2445
|
+
}
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
if (msg.type === "mate_list") {
|
|
2450
|
+
var mateList = mates.getAllMates();
|
|
2451
|
+
ws.send(JSON.stringify({ type: "mate_list", mates: mateList }));
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
if (msg.type === "mate_delete") {
|
|
2456
|
+
if (!msg.mateId) return;
|
|
2457
|
+
var result = mates.deleteMate(msg.mateId);
|
|
2458
|
+
if (result.error) {
|
|
2459
|
+
ws.send(JSON.stringify({ type: "mate_error", error: result.error }));
|
|
2460
|
+
} else {
|
|
2461
|
+
removeProject("mate-" + msg.mateId);
|
|
2462
|
+
ws.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId }));
|
|
2463
|
+
// Broadcast to all clients so strips update
|
|
2464
|
+
projects.forEach(function (ctx) {
|
|
2465
|
+
ctx.forEachClient(function (otherWs) {
|
|
2466
|
+
if (otherWs === ws) return;
|
|
2467
|
+
if (otherWs.readyState !== 1) return;
|
|
2468
|
+
otherWs.send(JSON.stringify({ type: "mate_deleted", mateId: msg.mateId }));
|
|
2469
|
+
});
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
if (msg.type === "mate_update") {
|
|
2476
|
+
if (!msg.mateId || !msg.updates) return;
|
|
2477
|
+
var updated = mates.updateMate(msg.mateId, msg.updates);
|
|
2478
|
+
if (updated) {
|
|
2479
|
+
ws.send(JSON.stringify({ type: "mate_updated", mate: updated }));
|
|
2480
|
+
// Broadcast update
|
|
2481
|
+
projects.forEach(function (ctx) {
|
|
2482
|
+
ctx.forEachClient(function (otherWs) {
|
|
2483
|
+
if (otherWs === ws) return;
|
|
2484
|
+
if (otherWs.readyState !== 1) return;
|
|
2485
|
+
otherWs.send(JSON.stringify({ type: "mate_updated", mate: updated }));
|
|
2486
|
+
});
|
|
2487
|
+
});
|
|
2488
|
+
} else {
|
|
2489
|
+
ws.send(JSON.stringify({ type: "mate_error", error: "Mate not found" }));
|
|
2490
|
+
}
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2386
2493
|
}
|
|
2387
2494
|
|
|
2388
2495
|
function removeProject(slug) {
|
package/lib/sessions.js
CHANGED
|
@@ -280,6 +280,32 @@ function createSessionManager(opts) {
|
|
|
280
280
|
return session;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
// Create a session without switching to it (used for mate/background sessions)
|
|
284
|
+
function createSessionRaw(sessionOpts) {
|
|
285
|
+
var localId = nextLocalId++;
|
|
286
|
+
var session = {
|
|
287
|
+
localId: localId,
|
|
288
|
+
queryInstance: null,
|
|
289
|
+
messageQueue: null,
|
|
290
|
+
cliSessionId: null,
|
|
291
|
+
blocks: {},
|
|
292
|
+
sentToolResults: {},
|
|
293
|
+
pendingPermissions: {},
|
|
294
|
+
pendingAskUser: {},
|
|
295
|
+
allowedTools: {},
|
|
296
|
+
isProcessing: false,
|
|
297
|
+
title: "",
|
|
298
|
+
createdAt: Date.now(),
|
|
299
|
+
lastActivity: Date.now(),
|
|
300
|
+
history: [],
|
|
301
|
+
messageUUIDs: [],
|
|
302
|
+
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
303
|
+
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility) || "shared",
|
|
304
|
+
};
|
|
305
|
+
sessions.set(localId, session);
|
|
306
|
+
return session;
|
|
307
|
+
}
|
|
308
|
+
|
|
283
309
|
var HISTORY_PAGE_SIZE = 200;
|
|
284
310
|
|
|
285
311
|
function findTurnBoundary(history, targetIndex) {
|
|
@@ -613,6 +639,7 @@ function createSessionManager(opts) {
|
|
|
613
639
|
HISTORY_PAGE_SIZE: HISTORY_PAGE_SIZE,
|
|
614
640
|
getActiveSession: getActiveSession,
|
|
615
641
|
createSession: createSession,
|
|
642
|
+
createSessionRaw: createSessionRaw,
|
|
616
643
|
switchSession: switchSession,
|
|
617
644
|
deleteSession: deleteSession,
|
|
618
645
|
deleteSessionQuiet: deleteSessionQuiet,
|