clay-server 5.0.2-beta.1 → 5.1.0-beta.2

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.
Files changed (162) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-file-path.js +2 -3
  22. package/lib/project-knowledge.js +315 -136
  23. package/lib/project-mate-creation-proposal.js +1 -0
  24. package/lib/project-request-access.js +13 -1
  25. package/lib/project-sessions.js +39 -2
  26. package/lib/project-user-knowledge.js +60 -0
  27. package/lib/project.js +59 -18
  28. package/lib/public/app.js +73 -34
  29. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  30. package/lib/public/css/clay-workspace.css +105 -0
  31. package/lib/public/css/debates-workbench.css +47 -0
  32. package/lib/public/css/filebrowser.css +10 -0
  33. package/lib/public/css/home-debate-live.css +26 -0
  34. package/lib/public/css/home-hub.css +3 -1
  35. package/lib/public/css/home-mate-model-picker.css +15 -131
  36. package/lib/public/css/home-mate-settings.css +168 -16
  37. package/lib/public/css/icon-strip.css +249 -32
  38. package/lib/public/css/mate-conversation.css +176 -0
  39. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  40. package/lib/public/css/mate-workspace-color.css +16 -0
  41. package/lib/public/css/mates.css +28 -28
  42. package/lib/public/css/messages.css +1 -114
  43. package/lib/public/css/mobile-nav.css +16 -16
  44. package/lib/public/css/overlays.css +8 -0
  45. package/lib/public/css/project-logs.css +7 -0
  46. package/lib/public/css/project-mate-creation.css +105 -0
  47. package/lib/public/css/right-workbench.css +3 -3
  48. package/lib/public/css/session-folders.css +80 -21
  49. package/lib/public/css/sidebar.css +183 -107
  50. package/lib/public/css/title-bar.css +151 -0
  51. package/lib/public/css/worker-delegation.css +47 -0
  52. package/lib/public/css/worker-proposal.css +72 -150
  53. package/lib/public/css/you.css +74 -0
  54. package/lib/public/index.html +33 -101
  55. package/lib/public/modules/account-island.js +230 -0
  56. package/lib/public/modules/app-cursors.js +19 -43
  57. package/lib/public/modules/app-debate-ui.js +4 -3
  58. package/lib/public/modules/app-header.js +106 -0
  59. package/lib/public/modules/app-message-router.js +12 -3
  60. package/lib/public/modules/app-messages.js +34 -9
  61. package/lib/public/modules/app-notifications.js +4 -7
  62. package/lib/public/modules/app-projects.js +5 -38
  63. package/lib/public/modules/app-rendering.js +12 -13
  64. package/lib/public/modules/avatar.js +25 -9
  65. package/lib/public/modules/chat-render-runtime.js +16 -4
  66. package/lib/public/modules/command-palette.js +2 -15
  67. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  68. package/lib/public/modules/debate-controls.js +299 -0
  69. package/lib/public/modules/debate.js +2 -0
  70. package/lib/public/modules/debates-workbench.js +339 -0
  71. package/lib/public/modules/default-ai.js +81 -65
  72. package/lib/public/modules/filebrowser.js +28 -8
  73. package/lib/public/modules/home-debate-controls.js +18 -289
  74. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  75. package/lib/public/modules/home-mate-color-picker.js +66 -0
  76. package/lib/public/modules/home-mate-creation.js +1 -1
  77. package/lib/public/modules/home-mate-instructions.js +90 -0
  78. package/lib/public/modules/home-mate-model-picker.js +80 -271
  79. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  80. package/lib/public/modules/home-mate-settings.js +118 -125
  81. package/lib/public/modules/home-shell.js +3 -0
  82. package/lib/public/modules/home-surface-boot.js +1 -9
  83. package/lib/public/modules/html-preview.js +19 -0
  84. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  85. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  86. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  87. package/lib/public/modules/knowledge-history-state.js +55 -0
  88. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  89. package/lib/public/modules/knowledge-preview-links.js +70 -0
  90. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  91. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  92. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  93. package/lib/public/modules/mate-knowledge.js +11 -3
  94. package/lib/public/modules/mate-management.js +54 -10
  95. package/lib/public/modules/mate-sidebar.js +11 -1
  96. package/lib/public/modules/mate-workspace-color.js +33 -0
  97. package/lib/public/modules/mention.js +5 -13
  98. package/lib/public/modules/profile.js +51 -18
  99. package/lib/public/modules/project-chat-identity.js +79 -0
  100. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  101. package/lib/public/modules/project-debate-workspace.js +185 -0
  102. package/lib/public/modules/project-header-identity.js +127 -0
  103. package/lib/public/modules/project-logs-render.js +9 -4
  104. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  105. package/lib/public/modules/project-mate-navigation.js +238 -0
  106. package/lib/public/modules/project-session-navigation.js +29 -0
  107. package/lib/public/modules/project-switcher.js +6 -8
  108. package/lib/public/modules/projects-hub-view.js +5 -6
  109. package/lib/public/modules/projects-hub.js +2 -6
  110. package/lib/public/modules/qrcode.js +27 -8
  111. package/lib/public/modules/search-clay-chat.js +8 -11
  112. package/lib/public/modules/session-activity-tooltip.js +12 -0
  113. package/lib/public/modules/session-create-form.js +86 -14
  114. package/lib/public/modules/session-create.js +50 -7
  115. package/lib/public/modules/session-list-visibility.js +41 -0
  116. package/lib/public/modules/session-presence-ui.js +4 -1
  117. package/lib/public/modules/session-title-marquee.js +51 -0
  118. package/lib/public/modules/sidebar-mates.js +3 -50
  119. package/lib/public/modules/sidebar-mobile.js +63 -60
  120. package/lib/public/modules/sidebar-projects.js +84 -97
  121. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  122. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  123. package/lib/public/modules/sidebar-sessions.js +49 -50
  124. package/lib/public/modules/sidebar.js +11 -13
  125. package/lib/public/modules/thinking-lifecycle.js +7 -6
  126. package/lib/public/modules/tool-palette-order.js +4 -3
  127. package/lib/public/modules/tool-palette.js +13 -3
  128. package/lib/public/modules/tools.js +22 -22
  129. package/lib/public/modules/worker-proposal.js +42 -8
  130. package/lib/public/modules/worker-runtime-picker.js +115 -0
  131. package/lib/public/modules/you-activity.js +121 -0
  132. package/lib/public/modules/you.js +251 -0
  133. package/lib/public/style.css +16 -8
  134. package/lib/public/vendor/codemirror/LICENSE +21 -0
  135. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  136. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  137. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  138. package/lib/public/vendor/codemirror/markdown.js +886 -0
  139. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  140. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  141. package/lib/sdk-bridge.js +15 -3
  142. package/lib/server-admin.js +11 -0
  143. package/lib/server-home-chat-events.js +1 -1
  144. package/lib/server-home-chat.js +25 -3
  145. package/lib/server-home-debate-planning.js +4 -1
  146. package/lib/server-home-debates.js +2 -0
  147. package/lib/server-home-mate-creation.js +4 -1
  148. package/lib/server-mate-instructions.js +44 -0
  149. package/lib/server-mates.js +12 -3
  150. package/lib/server-palette.js +1 -1
  151. package/lib/server-settings.js +65 -1
  152. package/lib/server-user-knowledge.js +119 -0
  153. package/lib/server.js +21 -1
  154. package/lib/session-visibility.js +7 -2
  155. package/lib/sessions.js +32 -6
  156. package/lib/user-knowledge-service.js +167 -0
  157. package/lib/users-permissions.js +7 -0
  158. package/lib/users-project-mate-filter-preferences.js +39 -0
  159. package/lib/users.js +6 -0
  160. package/lib/workspace-query-access.js +4 -1
  161. package/lib/ws-schema.js +50 -0
  162. package/package.json +10 -1
@@ -12,7 +12,6 @@ import { dismissOverlayPanels, closeSidebar, updatePageTitle, spawnDustParticles
12
12
  import { showConfirm } from './app-misc.js';
13
13
  import { getUpcomingSchedules } from './scheduler.js';
14
14
  import { refreshMobileChatSheet } from './sidebar-mobile.js';
15
- import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
16
15
  import { openGroup, openWorkerWithDriver, separateGroup } from './split-view.js';
17
16
  import { groupedSessionIds } from './split-group-helpers.js';
18
17
  import { resolvePreferredVendor } from './default-vendor.js';
@@ -24,6 +23,7 @@ import { renderFolderToolbar, captureFolderInputs, folderToolbarNeedsRender } fr
24
23
  import { captureFolderCollapseAnimations } from './session-folder-collapse.js';
25
24
  import { getListSearchQuery, getListSearchMatchIds, applyListSearchResults, filterItemsBySearch } from './session-list-search.js';
26
25
  import { expectCreatedSession } from './session-create.js';
26
+ import { isMateWorkspace } from './project-mate-navigation.js';
27
27
  import { describeSessionDelete } from './session-delete-scope.js';
28
28
  import { renderSessionPresence, setSessionPresence } from './session-presence-ui.js';
29
29
  import {
@@ -31,6 +31,9 @@ import {
31
31
  renderDesktopDriverHierarchy,
32
32
  renderDesktopOrphanHierarchy
33
33
  } from './sidebar-session-hierarchy.js';
34
+ import { ordinarySessionListSessions } from './session-list-visibility.js';
35
+ import { formatSessionActivity } from './session-activity-tooltip.js';
36
+ import { bindSessionTitleMarquee } from './session-title-marquee.js';
34
37
  import { summarizeSidebarItems } from './sidebar-session-counts.js';
35
38
  import {
36
39
  cancelSessionInlineRename,
@@ -272,7 +275,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
272
275
 
273
276
 
274
277
  // Session visibility toggle (only the session owner can change)
275
- if (store.get('isMultiUserMode') && sessionData && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
278
+ if (!isMateWorkspace(store.snap()) && store.get('isMultiUserMode') && sessionData && !sessionData.isMate && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
276
279
  var currentVis = (sessionData && sessionData.sessionVisibility) || "private";
277
280
  var isPrivate = currentVis === "private";
278
281
  var visItem = document.createElement("button");
@@ -514,6 +517,17 @@ function isSessionVisibleBySearch(sessionId) {
514
517
  return getListSearchMatchIds().has(sessionId);
515
518
  }
516
519
 
520
+ function appendSessionTitleLine(el, textSpan) {
521
+ var processingDot = textSpan.querySelector('.session-processing');
522
+ if (processingDot) processingDot.remove();
523
+ var titleLine = document.createElement('span');
524
+ titleLine.className = 'session-item-title-line';
525
+ titleLine.appendChild(textSpan);
526
+ if (processingDot) titleLine.appendChild(processingDot);
527
+ el.appendChild(titleLine);
528
+ return titleLine;
529
+ }
530
+
517
531
  // --- Loop child / run / group rendering ---
518
532
 
519
533
  function renderLoopChild(s) {
@@ -539,7 +553,7 @@ function renderLoopChild(s) {
539
553
  textHtml += '<span class="session-loop-role-badge' + roleCls + '">' + roleName + iterSuffix + '</span>';
540
554
  }
541
555
  textSpan.innerHTML = textHtml;
542
- el.appendChild(textSpan);
556
+ appendSessionTitleLine(el, textSpan);
543
557
  appendSessionCloseButton(el, s);
544
558
 
545
559
  el.addEventListener("click", (function (id) {
@@ -665,7 +679,7 @@ function renderLoopGroup(loopId, children, groupKey) {
665
679
  textHtml += '<span class="session-loop-count' + countClass + '">' + countLabel + '</span>';
666
680
  }
667
681
  textSpan.innerHTML = textHtml;
668
- el.appendChild(textSpan);
682
+ appendSessionTitleLine(el, textSpan);
669
683
 
670
684
  // More button (ellipsis)
671
685
  var moreBtn = document.createElement("button");
@@ -768,7 +782,7 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
768
782
  textHtml += '<span class="session-loop-run-time">' + escapeHtml(timeLabel) + '</span>';
769
783
  textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + sessions.length + '</span>';
770
784
  textSpan.innerHTML = textHtml;
771
- el.appendChild(textSpan);
785
+ appendSessionTitleLine(el, textSpan);
772
786
 
773
787
  // Click row -> switch to latest session of this run
774
788
  el.addEventListener("click", (function (id) {
@@ -799,26 +813,24 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
799
813
 
800
814
  function renderSessionItem(s, options) {
801
815
  var itemOptions = options || {};
816
+ var displaySession = s;
817
+ if (itemOptions.promotedActive || itemOptions.promotedProcessing || itemOptions.promotedUnread !== undefined) {
818
+ displaySession = Object.assign({}, s, {
819
+ active: !!(s.active || itemOptions.promotedActive),
820
+ isProcessing: !!(s.isProcessing || itemOptions.promotedProcessing),
821
+ unread: (s.unread || 0) + (itemOptions.promotedUnread || 0),
822
+ });
823
+ }
802
824
  var el = document.createElement("div");
803
825
  var isMatch = getListSearchMatchIds() !== null && getListSearchMatchIds().has(s.id);
804
- el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "");
826
+ el.className = "session-item" + (displaySession.active ? " active" : "") + (isMatch ? " search-match" : "");
805
827
  if (itemOptions.worker) el.classList.add("session-worker-item");
806
828
  el.dataset.sessionId = s.id;
807
829
 
808
- // Keep agent identity separate from the title so each row has a stable
809
- // visual anchor instead of mixing a tiny inline logo into the text.
810
- var itemVendor = s.vendor || "claude";
811
- var vendorIcon = document.createElement("img");
812
- vendorIcon.className = "session-vendor-icon session-vendor-mark";
813
- vendorIcon.src = VENDOR_AVATARS[itemVendor] || VENDOR_AVATARS.claude;
814
- vendorIcon.alt = "";
815
- vendorIcon.title = VENDOR_NAMES[itemVendor] || itemVendor;
816
- el.appendChild(vendorIcon);
817
-
818
830
  var textSpan = document.createElement("span");
819
831
  textSpan.className = "session-item-text";
820
832
  var textHtml = "";
821
- if (s.isProcessing) {
833
+ if (displaySession.isProcessing) {
822
834
  textHtml += '<span class="session-processing"></span>';
823
835
  }
824
836
  if (s.backgroundTaskCount) {
@@ -832,8 +844,12 @@ function renderSessionItem(s, options) {
832
844
  }
833
845
  textHtml += '<span class="session-item-title">' + highlightMatch(s.title || "New Session", getListSearchQuery()) + '</span>';
834
846
  textSpan.innerHTML = textHtml;
835
- el.appendChild(textSpan);
836
- appendGithubWork(el, s, textSpan);
847
+ textSpan.setAttribute("aria-label", s.title || "New Session");
848
+ var titleEl = textSpan.querySelector(".session-item-title");
849
+ var activityTitle = formatSessionActivity(s.lastActivity);
850
+ if (titleEl && activityTitle) titleEl.title = activityTitle;
851
+ var titleLine = appendSessionTitleLine(el, textSpan);
852
+ appendGithubWork(el, s, titleLine);
837
853
 
838
854
  if (itemOptions.worker) {
839
855
  var workerLabel = document.createElement("span");
@@ -844,13 +860,6 @@ function renderSessionItem(s, options) {
844
860
 
845
861
  var trailing = document.createElement("span");
846
862
  trailing.className = "session-row-trailing";
847
- var ageText = relativeTime(s.lastActivity || 0);
848
- if (ageText) {
849
- var age = document.createElement("span");
850
- age.className = "session-item-age";
851
- age.textContent = ageText;
852
- trailing.appendChild(age);
853
- }
854
863
 
855
864
  // Right-click / long-press: context menu
856
865
  el.addEventListener("contextmenu", (function(id, title, cliSid, anchor, sData) {
@@ -865,8 +874,8 @@ function renderSessionItem(s, options) {
865
874
  var unreadBadge = document.createElement("span");
866
875
  unreadBadge.className = "session-unread-badge";
867
876
  unreadBadge.dataset.sessionId = s.id;
868
- if (s.unread > 0) {
869
- unreadBadge.textContent = s.unread > 99 ? "99+" : String(s.unread);
877
+ if (displaySession.unread > 0) {
878
+ unreadBadge.textContent = displaySession.unread > 99 ? "99+" : String(displaySession.unread);
870
879
  unreadBadge.classList.add("has-unread");
871
880
  }
872
881
  el.appendChild(unreadBadge);
@@ -877,6 +886,7 @@ function renderSessionItem(s, options) {
877
886
  if (isFavoriteSession(s)) el.classList.add("is-favorite");
878
887
  }
879
888
  appendSessionCloseButton(actions, s);
889
+ el.style.setProperty("--session-actions-width", (actions.childNodes.length * 18 + Math.max(0, actions.childNodes.length - 1) * 4 + 21) + "px");
880
890
  if (actions.childNodes.length) trailing.appendChild(actions);
881
891
  if (trailing.childNodes.length) el.appendChild(trailing);
882
892
 
@@ -898,6 +908,7 @@ function renderSessionItem(s, options) {
898
908
 
899
909
  // Presence avatars (multi-user)
900
910
  renderSessionPresence(el, String(s.id));
911
+ bindSessionTitleMarquee(el, textSpan.querySelector(".session-item-title"), actions);
901
912
  if (!itemOptions.worker) setupFolderDrag(el, s);
902
913
 
903
914
  return el;
@@ -911,14 +922,7 @@ function renderSplitGroupItem(group, members) {
911
922
 
912
923
  var text = document.createElement("span");
913
924
  text.className = "session-item-text split-group-text";
914
- var html = '<span class="split-group-glyph" aria-hidden="true">' + iconHtml("columns-2") + '</span><span class="split-group-vendors">';
915
- for (var i = 0; i < members.length; i++) {
916
- var vendor = members[i].vendor || "claude";
917
- html += '<img class="session-vendor-icon split-group-vendor" src="' +
918
- (VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude) + '" alt="" title="' +
919
- escapeHtml(VENDOR_NAMES[vendor] || vendor) + '">';
920
- }
921
- html += "</span>";
925
+ var html = '<span class="split-group-glyph" aria-hidden="true">' + iconHtml("columns-2") + '</span>';
922
926
  var splitProcessing = false;
923
927
  var splitBackgroundTaskCount = 0;
924
928
  for (var si = 0; si < members.length; si++) {
@@ -929,7 +933,7 @@ function renderSplitGroupItem(group, members) {
929
933
  if (splitBackgroundTaskCount) html += '<span class="session-background-task-count">' + splitBackgroundTaskCount + '</span>';
930
934
  html += '<span class="split-group-name">' + highlightMatch(group.name || "Split", getListSearchQuery()) + "</span>";
931
935
  text.innerHTML = html;
932
- el.appendChild(text);
936
+ appendSessionTitleLine(el, text);
933
937
 
934
938
  var unread = 0;
935
939
  for (var ui = 0; ui < members.length; ui++) unread += members[ui].unread || 0;
@@ -938,7 +942,6 @@ function renderSplitGroupItem(group, members) {
938
942
  badge.dataset.splitGroupId = group.id;
939
943
  if (unread > 0) badge.textContent = unread > 99 ? "99+" : String(unread);
940
944
  el.appendChild(badge);
941
-
942
945
  el.addEventListener("click", function () {
943
946
  if (openGroup(group)) {
944
947
  dismissOverlayPanels();
@@ -994,22 +997,23 @@ export function renderSessionList(sessions) {
994
997
 
995
998
  // Partition: loop sessions vs normal sessions
996
999
  // Group by loopId + date so all runs of the same task on the same day are merged
1000
+ var visibleSessions = ordinarySessionListSessions(cachedSessions);
997
1001
  var splitGroups = store.get('splitGroups') || [];
998
1002
  var groupedIds = groupedSessionIds(splitGroups);
999
- var hierarchy = prepareSidebarHierarchy(cachedSessions);
1003
+ var hierarchy = prepareSidebarHierarchy(visibleSessions);
1000
1004
  var hierarchyByDriver = hierarchy.byDriver;
1001
1005
  var hierarchySessionIds = hierarchy.sessionIds;
1002
1006
  hierarchySessionIds.forEach(function (id) { groupedIds.delete(id); });
1003
1007
  var sessionsById = new Map();
1004
- for (var si = 0; si < cachedSessions.length; si++) sessionsById.set(cachedSessions[si].id, cachedSessions[si]);
1008
+ for (var si = 0; si < visibleSessions.length; si++) sessionsById.set(visibleSessions[si].id, visibleSessions[si]);
1005
1009
  var loopGroups = {}; // groupKey -> [sessions]
1006
1010
  var normalSessions = [];
1007
- for (var i = 0; i < cachedSessions.length; i++) {
1008
- var s = cachedSessions[i];
1011
+ for (var i = 0; i < visibleSessions.length; i++) {
1012
+ var s = visibleSessions[i];
1009
1013
  if (s.sessionRole === "worker") continue;
1010
1014
  if (groupedIds.has(s.id)) continue;
1011
- if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
1012
- // Task crafting sessions live in the scheduler calendar, not the main list (except debate)
1015
+ if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph") {
1016
+ // Task crafting sessions live in the scheduler calendar, not the main list.
1013
1017
  continue;
1014
1018
  } else if (s.loop && s.loop.loopId) {
1015
1019
  var startedAt = s.loop.startedAt || 0;
@@ -1029,11 +1033,6 @@ export function renderSessionList(sessions) {
1029
1033
  if (driverRoot) items.push({ type: "driver-hierarchy", root: driverRoot, data: driverRoot.driver, lastActivity: driverRoot.lastActivity });
1030
1034
  else items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
1031
1035
  }
1032
- if (hierarchy.orphans.length) {
1033
- var orphanActivity = 0;
1034
- for (var oi = 0; oi < hierarchy.orphans.length; oi++) orphanActivity = Math.max(orphanActivity, hierarchy.orphans[oi].lastActivity || 0);
1035
- items.push({ type: "orphan-workers", workers: hierarchy.orphans, lastActivity: orphanActivity });
1036
- }
1037
1036
  for (var sg = 0; sg < splitGroups.length; sg++) {
1038
1037
  var splitGroup = splitGroups[sg];
1039
1038
  var splitMembers = [];
@@ -1083,7 +1082,7 @@ export function renderSessionList(sessions) {
1083
1082
  if (it.type === "loop") return renderLoopGroup(it.loopId, it.children, it.groupKey) || document.createElement("div");
1084
1083
  if (it.type === "split-group") return renderSplitGroupItem(it.group, it.members);
1085
1084
  if (it.type === "driver-hierarchy") return renderDesktopDriverHierarchy(it.root, renderSessionItem, rerender, matchIds);
1086
- if (it.type === "orphan-workers") return renderDesktopOrphanHierarchy(it.workers, renderSessionItem, rerender, matchIds);
1085
+ if (it.type === "orphan-workers") return document.createElement("div");
1087
1086
  return renderSessionItem(it.data);
1088
1087
  },
1089
1088
  };
@@ -62,6 +62,14 @@ export function closeSidebar() {
62
62
  ctx.sidebarOverlay.classList.remove("visible");
63
63
  }
64
64
 
65
+ export function toggleSidebarCollapse() {
66
+ if (!ctx) return;
67
+ var layout = ctx.$("layout");
68
+ var collapsed = layout.classList.toggle("sidebar-collapsed");
69
+ try { localStorage.setItem("sidebar-collapsed", collapsed ? "1" : ""); } catch (e) {}
70
+ setTimeout(function () { syncResizeHandles(); }, 210);
71
+ }
72
+
65
73
  export function initSidebar(_ctx) {
66
74
  ctx = _ctx;
67
75
 
@@ -87,13 +95,6 @@ export function initSidebar(_ctx) {
87
95
  ctx.sidebarOverlay.addEventListener("click", closeSidebar);
88
96
 
89
97
  // --- Desktop sidebar collapse/expand ---
90
- function toggleSidebarCollapse() {
91
- var layout = ctx.$("layout");
92
- var collapsed = layout.classList.toggle("sidebar-collapsed");
93
- try { localStorage.setItem("sidebar-collapsed", collapsed ? "1" : ""); } catch (e) {}
94
- setTimeout(function () { syncUserIslandWidth(); syncResizeHandle(); }, 210);
95
- }
96
-
97
98
  if (ctx.sidebarToggleBtn) ctx.sidebarToggleBtn.addEventListener("click", toggleSidebarCollapse);
98
99
  if (ctx.sidebarExpandBtn) ctx.sidebarExpandBtn.addEventListener("click", toggleSidebarCollapse);
99
100
  var mateSidebarToggle = document.getElementById("mate-sidebar-toggle-btn");
@@ -245,12 +246,9 @@ export function initSidebar(_ctx) {
245
246
 
246
247
  function syncUserIslandWidth() {
247
248
  if (!userIsland) return;
248
- var mateSidebarColumn = document.getElementById("mate-sidebar-column");
249
- var isMateDM = document.body.classList.contains("mate-dm-active");
250
- var col = (isMateDM && mateSidebarColumn && !mateSidebarColumn.classList.contains("hidden")) ? mateSidebarColumn : sidebarColumn;
251
- if (!col) return;
252
- var rect = col.getBoundingClientRect();
253
- userIsland.style.width = (rect.right - 8 - 8) + "px";
249
+ var iconStrip = document.getElementById("icon-strip");
250
+ var stripWidth = iconStrip ? iconStrip.getBoundingClientRect().width : 0;
251
+ userIsland.style.width = (stripWidth > 0 ? stripWidth : 72) + "px";
254
252
  }
255
253
 
256
254
  // --- Sidebar resize handle ---
@@ -3,6 +3,7 @@ import { iconHtml, refreshIcons } from './icons.js';
3
3
  import { store } from './store.js';
4
4
  import { addToMessages, keepActiveThinkingAtTail, scrollToBottom } from './chat-render-runtime.js';
5
5
  import { prepareThinkingView, bindThinkingView, updateThinkingView, beginThinkingView, setThinkingViewLive, finishThinkingView } from './thinking-view.js';
6
+ import { resolveProjectAssistantIdentity, applyProjectAssistantIdentity } from './project-chat-identity.js';
6
7
 
7
8
  function getThinkingState() {
8
9
  return store.get('thinkingState') || null;
@@ -19,8 +20,7 @@ function updateThinkingState(state, patch) {
19
20
  }
20
21
 
21
22
  function isMateThinking() {
22
- var target = store.get('dmTargetUser');
23
- return !!(store.get('dmMode') && target && target.isMate);
23
+ return resolveProjectAssistantIdentity(store.snap(), store.get('currentVendor')).kind === 'mate';
24
24
  }
25
25
 
26
26
  function maybeScrollToBottom() {
@@ -30,10 +30,10 @@ function maybeScrollToBottom() {
30
30
  function createThinkingElement() {
31
31
  var el = document.createElement("div");
32
32
  el.className = "thinking-item";
33
- if (isMateThinking()) {
34
- var target = store.get('dmTargetUser');
35
- var mateName = (target && target.displayName) || "Mate";
36
- var mateAvatar = document.body.dataset.mateAvatarUrl || "";
33
+ var identity = resolveProjectAssistantIdentity(store.snap(), store.get('currentVendor'));
34
+ if (identity.kind === 'mate') {
35
+ var mateName = identity.name;
36
+ var mateAvatar = identity.avatarUrl;
37
37
  el.classList.add("mate-thinking");
38
38
  el.innerHTML =
39
39
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
@@ -48,6 +48,7 @@ function createThinkingElement() {
48
48
  '</div>' +
49
49
  '<div class="thinking-content"></div>' +
50
50
  '</div>';
51
+ applyProjectAssistantIdentity(el, identity);
51
52
  } else {
52
53
  el.innerHTML =
53
54
  '<div class="thinking-header">' +
@@ -33,13 +33,14 @@ var MATE_TOOLS = [
33
33
  { id: "mate-memory-btn", icon: "brain", label: "Memory", countId: "mate-memory-count" },
34
34
  { id: "mate-knowledge-btn", icon: "book-open", label: "Knowledge", countId: "mate-knowledge-count" },
35
35
  { id: "mate-sticky-notes-btn", icon: "sticky-note", label: "Sticky Notes" },
36
- { id: "mate-debate-btn", icon: "mic", label: "Debate" },
36
+ { id: "mate-debates-btn", icon: "messages-square", label: "Debates" },
37
37
  { id: "mate-mcp-btn", icon: "cable", label: "MCP / Skills", countId: "mate-mcp-sidebar-count" },
38
38
  ];
39
39
 
40
40
  export var PALETTES = {
41
41
  session: {
42
42
  tools: SESSION_TOOLS,
43
+ fixedTools: [{ id: "you-button", icon: "user-round", label: "Me", countId: "me-unread-badge", fixed: true, scope: "clay" }],
43
44
  activeContainerId: "session-actions",
44
45
  hiddenSectionId: "session-actions-hidden",
45
46
  },
@@ -57,13 +58,13 @@ export var PALETTES = {
57
58
  //
58
59
  // Skills now shares the MCP / Skills entry; explicit MCP placement wins.
59
60
  var LEGACY_SESSION_TOOL_IDS = { "skills-btn": "mcp-btn" };
60
- var LEGACY_MATE_TOOL_IDS = { "mate-skills-btn": "mate-mcp-btn" };
61
+ var LEGACY_MATE_TOOL_IDS = { "mate-skills-btn": "mate-mcp-btn", "mate-debate-btn": "mate-debates-btn" };
61
62
 
62
63
  // Tools withdrawn from the session palette with no replacement. Git moved to
63
64
  // the always-visible repository placard below the tool strip, so it no longer
64
65
  // consumes an icon slot. A saved preference naming it is dropped rather than
65
66
  // left to linger in stored order forever.
66
- var RETIRED_SESSION_TOOL_IDS = ["git-sidebar-btn", "loop-tool-btn"];
67
+ var RETIRED_SESSION_TOOL_IDS = ["git-sidebar-btn", "loop-tool-btn", "debates-btn"];
67
68
 
68
69
  function indexOfId(list, id) {
69
70
  for (var i = 0; i < list.length; i++) {
@@ -37,6 +37,8 @@ function buildPalette(name) {
37
37
  var active = document.getElementById(palette.activeContainerId);
38
38
  if (!active) return;
39
39
 
40
+ (palette.fixedTools || []).forEach(function (tool) { active.appendChild(buildToolButton(tool, name)); });
41
+
40
42
  // Create buttons with stable IDs and append in registry order (default).
41
43
  for (var i = 0; i < palette.tools.length; i++) {
42
44
  active.appendChild(buildToolButton(palette.tools[i], name));
@@ -73,8 +75,11 @@ function buildToolButton(tool, paletteName) {
73
75
  btn.setAttribute('aria-label', tool.label);
74
76
  btn.dataset.toolId = tool.id;
75
77
  btn.dataset.palette = paletteName;
76
- // Draggable at all times so users can reorder in place.
78
+ // Ordinary tools can be reordered; fixed tools retain their first slots.
77
79
  btn.draggable = true;
80
+ if (tool.fixed) { btn.draggable = false; btn.dataset.fixed = 'true'; }
81
+ if (tool.scope) btn.dataset.scope = tool.scope;
82
+ if (tool.id === 'you-button') { btn.classList.add('you-button'); btn.setAttribute('aria-pressed', 'false'); }
78
83
 
79
84
  var icon = document.createElement('i');
80
85
  icon.setAttribute('data-lucide', tool.icon);
@@ -89,10 +94,12 @@ function buildToolButton(tool, paletteName) {
89
94
  var count = document.createElement('span');
90
95
  count.id = tool.countId;
91
96
  count.className = 'sidebar-badge hidden';
97
+ if (tool.id === 'you-button') { count.className = 'sidebar-badge me-unread-badge'; count.hidden = true; }
92
98
  btn.appendChild(count);
93
99
  }
94
100
 
95
101
  btn.addEventListener('dragstart', function (e) {
102
+ if (tool.fixed) { e.preventDefault(); return; }
96
103
  var container = btn.parentNode;
97
104
  if (!container || container.id !== PALETTES[paletteName].activeContainerId) {
98
105
  e.preventDefault();
@@ -122,6 +129,7 @@ function getDragAfterElement(container, x, y) {
122
129
  var closest = null;
123
130
  var closestDist = Number.POSITIVE_INFINITY;
124
131
  for (var i = 0; i < tiles.length; i++) {
132
+ if (tiles[i].dataset.fixed === 'true') continue;
125
133
  var rect = tiles[i].getBoundingClientRect();
126
134
  // Pointer is "before" the tile if it's left of the tile's horizontal
127
135
  // midpoint on the same row (or on a higher row).
@@ -164,7 +172,7 @@ function applyPreferences(name, prefs) {
164
172
  function place(id) {
165
173
  if (placed[id]) return;
166
174
  var btn = document.getElementById(id);
167
- if (!btn) return;
175
+ if (!btn || btn.dataset.fixed === 'true') return;
168
176
  active.appendChild(btn);
169
177
  placed[id] = true;
170
178
  }
@@ -201,7 +209,9 @@ function savePreferences(name) {
201
209
 
202
210
  var order = [];
203
211
  var activeTiles = active.querySelectorAll('[data-tool-id]');
204
- for (var i = 0; i < activeTiles.length; i++) order.push(activeTiles[i].dataset.toolId);
212
+ for (var i = 0; i < activeTiles.length; i++) {
213
+ if (activeTiles[i].dataset.fixed !== 'true') order.push(activeTiles[i].dataset.toolId);
214
+ }
205
215
 
206
216
  // Every tool is visible, so the explicit order is the whole preference.
207
217
  fetch('/api/user/tool-palettes', {
@@ -11,6 +11,7 @@ import { VENDOR_NAMES } from './app-rendering.js';
11
11
  import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
12
12
  import { buildElicitationCard, markElicitationCard, showElicitationError } from './elicitation-form.js';
13
13
  import { findAskUserCard, getAskUserRequestKey, hasAskUserRequest, rememberAskUserRequest, markAskUserRequestAnswered, saveAskUserState, restoreAskUserState, resetAskUserState } from './ask-user-state.js';
14
+ import { getProjectAssistantPlaceholder, resolveProjectAssistantIdentity, applyProjectAssistantIdentity } from './project-chat-identity.js';
14
15
 
15
16
  var ctx;
16
17
 
@@ -142,11 +143,11 @@ function updateToolGroupHeader(group) {
142
143
  refreshIcons();
143
144
  }
144
145
 
145
- // Show group header only when 2+ visible tools (or always in mate DM)
146
+ // Show group header only when 2+ visible tools (or always in a Mate conversation)
146
147
  var header = group.el.querySelector(".tool-group-header");
147
148
  var isMate = group.el.classList.contains("mate-tool-group");
148
149
  if (isMate) {
149
- // Mate DM: hide entire group when no tools, show collapsed when tools exist
150
+ // Mate conversation: hide entire group when no tools, show collapsed when tools exist
150
151
  if (group.toolCount === 0) {
151
152
  group.el.style.display = "none";
152
153
  } else {
@@ -248,12 +249,13 @@ export function renderAskUserQuestion(toolId, input) {
248
249
  container.dataset.askUserRequestKey = requestKey;
249
250
  rememberAskUserRequest(toolId);
250
251
 
251
- // Mate DM: wrap in avatar + content layout (same as msg-assistant)
252
+ // Mate conversation: wrap in avatar + content layout (same as msg-assistant)
252
253
  var mateContentWrap = null;
253
- if (ctx.isMateDm && ctx.isMateDm()) {
254
+ if (resolveProjectAssistantIdentity().kind === 'mate') {
254
255
  container.classList.add("mate-ask-user");
255
- var mateName = ctx.getMateName();
256
- var mateAvatar = ctx.getMateAvatarUrl();
256
+ var identity = resolveProjectAssistantIdentity();
257
+ var mateName = identity.name;
258
+ var mateAvatar = identity.avatarUrl;
257
259
 
258
260
  var avi = document.createElement("img");
259
261
  avi.className = "dm-bubble-avatar";
@@ -393,7 +395,7 @@ export function renderAskUserQuestion(toolId, input) {
393
395
  actions.appendChild(skipBtn);
394
396
  container.appendChild(actions);
395
397
 
396
- // Mate DM: move all content into the bubble content wrapper
398
+ // Mate conversation: move all content into the bubble content wrapper
397
399
  if (mateContentWrap) {
398
400
  var children = [];
399
401
  for (var ci = 0; ci < container.childNodes.length; ci++) {
@@ -420,12 +422,7 @@ export function disableMainInput() {
420
422
 
421
423
  export function enableMainInput() {
422
424
  ctx.inputEl.disabled = false;
423
- if (document.body.classList.contains("mate-dm-active") && document.body.dataset.mateName) {
424
- ctx.inputEl.placeholder = "Message " + document.body.dataset.mateName + "...";
425
- } else {
426
- var _v = store.get('currentVendor') || "claude";
427
- ctx.inputEl.placeholder = "Message " + (VENDOR_NAMES[_v] || VENDOR_NAMES.claude) + "...";
428
- }
425
+ ctx.inputEl.placeholder = getProjectAssistantPlaceholder(store.snap(), store.get('currentVendor'));
429
426
  }
430
427
 
431
428
  function submitAskUserAnswer(container, toolId, questions, answers, multiSelections) {
@@ -557,8 +554,8 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
557
554
  return;
558
555
  }
559
556
 
560
- // Channel layout or Mate DM: conversational "Can I ...?" style
561
- if ((ctx.isMateDm && ctx.isMateDm()) || getChatLayout() === "channel") {
557
+ // Channel layout or Mate conversation: conversational "Can I ...?" style
558
+ if (resolveProjectAssistantIdentity().kind === "mate" || getChatLayout() === "channel") {
562
559
  renderConversationalPermission(requestId, toolName, toolInput, mateId, vendor);
563
560
  applyPermissionHints(pendingPermissions[requestId], hints);
564
561
  return;
@@ -824,10 +821,11 @@ function matePermissionInfo(toolName, toolInput) {
824
821
  }
825
822
 
826
823
  function resolvePermissionIdentity(mateId, vendor) {
827
- // Mate DM: use DM target mate info
828
- if (ctx.isMateDm && ctx.isMateDm()) {
829
- var name = ctx.getMateName();
830
- var avatar = ctx.getMateAvatarUrl();
824
+ // Use the Mate bound to this session, including legacy DM targets.
825
+ if (resolveProjectAssistantIdentity().kind === 'mate') {
826
+ var identity = resolveProjectAssistantIdentity();
827
+ var name = identity.name;
828
+ var avatar = identity.avatarUrl;
831
829
  // Override if specific mateId provided (e.g. @mention)
832
830
  if (mateId && ctx.getMateById) {
833
831
  var mentionMate = ctx.getMateById(mateId);
@@ -1577,7 +1575,7 @@ function updateTodoSticky() {
1577
1575
  export function startThinking() {
1578
1576
  ctx.finalizeAssistantBlock();
1579
1577
  var el = startThinkingSegment();
1580
- if (!ctx.isMateDm()) {
1578
+ if (resolveProjectAssistantIdentity().kind !== 'mate') {
1581
1579
  ctx.setActivity("thinking");
1582
1580
  }
1583
1581
  return el;
@@ -1605,7 +1603,8 @@ export function createToolItem(id, name) {
1605
1603
  toolGroupCounter++;
1606
1604
  var groupEl = document.createElement("div");
1607
1605
  groupEl.className = "tool-group";
1608
- var isMateToolGroup = ctx.isMateDm();
1606
+ var identity = resolveProjectAssistantIdentity();
1607
+ var isMateToolGroup = identity.kind === 'mate';
1609
1608
  if (isMateToolGroup) groupEl.classList.add("mate-tool-group");
1610
1609
  groupEl.dataset.groupId = "g" + toolGroupCounter;
1611
1610
 
@@ -1619,7 +1618,7 @@ export function createToolItem(id, name) {
1619
1618
  '<div class="tool-group-items"></div>';
1620
1619
 
1621
1620
  if (isMateToolGroup) {
1622
- var mateAvatar = ctx.getMateAvatarUrl();
1621
+ var mateAvatar = identity.avatarUrl;
1623
1622
  groupEl.innerHTML =
1624
1623
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1625
1624
  '<div class="dm-bubble-content">' + toolGroupInner + '</div>';
@@ -1627,6 +1626,7 @@ export function createToolItem(id, name) {
1627
1626
  groupEl.innerHTML = toolGroupInner;
1628
1627
  }
1629
1628
 
1629
+ applyProjectAssistantIdentity(groupEl, identity);
1630
1630
  groupEl.querySelector(".tool-group-header").addEventListener("click", function () {
1631
1631
  groupEl.classList.toggle("collapsed");
1632
1632
  if (currentToolGroup) currentToolGroup.userToggled = true;