clay-server 2.31.0 → 2.32.0-beta.10

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 (82) hide show
  1. package/lib/browser-mcp-server.js +32 -44
  2. package/lib/codex-defaults.js +18 -0
  3. package/lib/debate-mcp-server.js +14 -31
  4. package/lib/mcp-local.js +31 -1
  5. package/lib/project-connection.js +9 -6
  6. package/lib/project-debate.js +8 -0
  7. package/lib/project-filesystem.js +47 -1
  8. package/lib/project-http.js +75 -8
  9. package/lib/project-mate-interaction.js +102 -16
  10. package/lib/project-mcp.js +4 -0
  11. package/lib/project-notifications.js +9 -0
  12. package/lib/project-sessions.js +94 -51
  13. package/lib/project-user-message.js +12 -7
  14. package/lib/project.js +234 -99
  15. package/lib/public/app.js +135 -454
  16. package/lib/public/codex-avatar.png +0 -0
  17. package/lib/public/css/debate.css +3 -2
  18. package/lib/public/css/filebrowser.css +91 -1
  19. package/lib/public/css/icon-strip.css +21 -5
  20. package/lib/public/css/input.css +338 -104
  21. package/lib/public/css/mates.css +43 -0
  22. package/lib/public/css/mention.css +48 -4
  23. package/lib/public/css/menus.css +1 -1
  24. package/lib/public/css/messages.css +2 -0
  25. package/lib/public/css/notifications-center.css +26 -0
  26. package/lib/public/css/tooltip.css +47 -0
  27. package/lib/public/index.html +78 -26
  28. package/lib/public/modules/app-connection.js +138 -37
  29. package/lib/public/modules/app-cursors.js +18 -17
  30. package/lib/public/modules/app-debate-ui.js +9 -9
  31. package/lib/public/modules/app-dm.js +175 -131
  32. package/lib/public/modules/app-favicon.js +28 -26
  33. package/lib/public/modules/app-header.js +79 -68
  34. package/lib/public/modules/app-home-hub.js +55 -47
  35. package/lib/public/modules/app-loop-ui.js +34 -18
  36. package/lib/public/modules/app-loop-wizard.js +6 -6
  37. package/lib/public/modules/app-messages.js +199 -153
  38. package/lib/public/modules/app-misc.js +23 -12
  39. package/lib/public/modules/app-notifications.js +119 -9
  40. package/lib/public/modules/app-panels.js +203 -49
  41. package/lib/public/modules/app-projects.js +161 -150
  42. package/lib/public/modules/app-rate-limit.js +5 -4
  43. package/lib/public/modules/app-rendering.js +149 -101
  44. package/lib/public/modules/app-skills-install.js +4 -4
  45. package/lib/public/modules/context-sources.js +102 -66
  46. package/lib/public/modules/dom-refs.js +21 -0
  47. package/lib/public/modules/filebrowser.js +173 -2
  48. package/lib/public/modules/input.js +122 -0
  49. package/lib/public/modules/markdown.js +5 -1
  50. package/lib/public/modules/mate-sidebar.js +38 -0
  51. package/lib/public/modules/mention.js +24 -6
  52. package/lib/public/modules/scheduler.js +1 -1
  53. package/lib/public/modules/sidebar-mates.js +79 -35
  54. package/lib/public/modules/sidebar-mobile.js +34 -30
  55. package/lib/public/modules/sidebar-projects.js +60 -57
  56. package/lib/public/modules/sidebar-sessions.js +75 -69
  57. package/lib/public/modules/sidebar.js +12 -20
  58. package/lib/public/modules/skills.js +8 -9
  59. package/lib/public/modules/sticky-notes.js +1 -2
  60. package/lib/public/modules/store.js +9 -2
  61. package/lib/public/modules/stt.js +4 -1
  62. package/lib/public/modules/terminal.js +12 -0
  63. package/lib/public/modules/tools.js +18 -13
  64. package/lib/public/modules/tooltip.js +32 -5
  65. package/lib/sdk-bridge.js +562 -1114
  66. package/lib/sdk-message-processor.js +150 -135
  67. package/lib/sdk-worker.js +4 -0
  68. package/lib/server-dm.js +1 -0
  69. package/lib/server.js +86 -1
  70. package/lib/sessions.js +81 -37
  71. package/lib/ws-schema.js +2 -0
  72. package/lib/yoke/adapters/claude-worker.js +559 -0
  73. package/lib/yoke/adapters/claude.js +1483 -0
  74. package/lib/yoke/adapters/codex.js +1121 -0
  75. package/lib/yoke/adapters/gemini.js +709 -0
  76. package/lib/yoke/codex-app-server.js +307 -0
  77. package/lib/yoke/index.js +199 -0
  78. package/lib/yoke/instructions.js +62 -0
  79. package/lib/yoke/interface.js +98 -0
  80. package/lib/yoke/mcp-bridge-server.js +294 -0
  81. package/lib/yoke/package.json +7 -0
  82. package/package.json +3 -1
@@ -5,8 +5,14 @@ import { avatarUrl, userAvatarUrl } from './avatar.js';
5
5
  import { escapeHtml } from './utils.js';
6
6
  import { iconHtml, refreshIcons } from './icons.js';
7
7
  import { openSearch as openSessionSearch } from './session-search.js';
8
+ import { store } from './store.js';
9
+ import { getWs } from './ws-ref.js';
10
+ import { getSessionListEl } from './dom-refs.js';
11
+ import { dismissOverlayPanels, closeSidebar, updatePageTitle } from './sidebar.js';
12
+ import { showConfirm } from './app-misc.js';
13
+ import { getUpcomingSchedules } from './scheduler.js';
14
+ import { refreshMobileChatSheet } from './sidebar-mobile.js';
8
15
 
9
- var _ctx = null;
10
16
 
11
17
  // --- Session state ---
12
18
  var cachedSessions = [];
@@ -27,16 +33,15 @@ var countdownContainer = null;
27
33
  var sessionCtxMenu = null;
28
34
  var sessionCtxSessionId = null;
29
35
 
30
- export function initSidebarSessions(ctx) {
31
- _ctx = ctx;
36
+ export function initSidebarSessions() {
32
37
 
33
38
  document.addEventListener("click", function () { closeSessionCtxMenu(); });
34
39
 
35
40
  // --- Session search ---
36
- var searchBtn = _ctx.$("search-session-btn");
37
- var searchBox = _ctx.$("session-search");
38
- var searchInput = _ctx.$("session-search-input");
39
- var searchClear = _ctx.$("session-search-clear");
41
+ var searchBtn = document.getElementById("search-session-btn");
42
+ var searchBox = document.getElementById("session-search");
43
+ var searchInput = document.getElementById("session-search-input");
44
+ var searchClear = document.getElementById("session-search-clear");
40
45
 
41
46
  function openSearch() {
42
47
  searchBox.classList.remove("hidden");
@@ -79,8 +84,8 @@ export function initSidebarSessions(ctx) {
79
84
  return;
80
85
  }
81
86
  searchDebounce = setTimeout(function () {
82
- if (_ctx.ws && _ctx.connected) {
83
- _ctx.ws.send(JSON.stringify({ type: "search_sessions", query: searchQuery }));
87
+ if (getWs() && store.get('connected')) {
88
+ getWs().send(JSON.stringify({ type: "search_sessions", query: searchQuery }));
84
89
  }
85
90
  }, 200);
86
91
  });
@@ -93,11 +98,11 @@ export function initSidebarSessions(ctx) {
93
98
  });
94
99
 
95
100
  // --- Resume session picker ---
96
- var resumeModal = _ctx.$("resume-modal");
97
- var resumeCancel = _ctx.$("resume-cancel");
98
- var pickerLoading = _ctx.$("resume-picker-loading");
99
- var pickerEmpty = _ctx.$("resume-picker-empty");
100
- var pickerList = _ctx.$("resume-picker-list");
101
+ var resumeModal = document.getElementById("resume-modal");
102
+ var resumeCancel = document.getElementById("resume-cancel");
103
+ var pickerLoading = document.getElementById("resume-picker-loading");
104
+ var pickerEmpty = document.getElementById("resume-picker-empty");
105
+ var pickerList = document.getElementById("resume-picker-list");
101
106
 
102
107
  function openResumeModal() {
103
108
  resumeModal.classList.remove("hidden");
@@ -105,8 +110,8 @@ export function initSidebarSessions(ctx) {
105
110
  pickerEmpty.classList.add("hidden");
106
111
  pickerList.classList.add("hidden");
107
112
  pickerList.innerHTML = "";
108
- if (_ctx.ws && _ctx.connected) {
109
- _ctx.ws.send(JSON.stringify({ type: "list_cli_sessions" }));
113
+ if (getWs() && store.get('connected')) {
114
+ getWs().send(JSON.stringify({ type: "list_cli_sessions" }));
110
115
  }
111
116
  }
112
117
 
@@ -114,7 +119,8 @@ export function initSidebarSessions(ctx) {
114
119
  resumeModal.classList.add("hidden");
115
120
  }
116
121
 
117
- _ctx.resumeSessionBtn.addEventListener("click", openResumeModal);
122
+ var resumeBtn = document.getElementById("resume-session-btn");
123
+ if (resumeBtn) resumeBtn.addEventListener("click", openResumeModal);
118
124
  resumeCancel.addEventListener("click", closeResumeModal);
119
125
  resumeModal.querySelector(".confirm-backdrop").addEventListener("click", closeResumeModal);
120
126
 
@@ -172,7 +178,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
172
178
  menu.appendChild(renameItem);
173
179
 
174
180
  // Session visibility toggle (only the session owner can change)
175
- if (_ctx.multiUser && sessionData && sessionData.ownerId && sessionData.ownerId === _ctx.myUserId) {
181
+ if (store.get('isMultiUserMode') && sessionData && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
176
182
  var currentVis = (sessionData && sessionData.sessionVisibility) || "shared";
177
183
  var isPrivate = currentVis === "private";
178
184
  var visItem = document.createElement("button");
@@ -182,23 +188,23 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
182
188
  e.stopPropagation();
183
189
  closeSessionCtxMenu();
184
190
  var newVis = isPrivate ? "shared" : "private";
185
- if (_ctx.ws && _ctx.connected) {
186
- _ctx.ws.send(JSON.stringify({ type: "set_session_visibility", sessionId: sessionId, visibility: newVis }));
191
+ if (getWs() && store.get('connected')) {
192
+ getWs().send(JSON.stringify({ type: "set_session_visibility", sessionId: sessionId, visibility: newVis }));
187
193
  }
188
194
  });
189
195
  menu.appendChild(visItem);
190
196
  }
191
197
 
192
- if (!_ctx.permissions || _ctx.permissions.sessionDelete !== false) {
198
+ if (!store.get('permissions') || store.get('permissions').sessionDelete !== false) {
193
199
  var deleteItem = document.createElement("button");
194
200
  deleteItem.className = "session-ctx-item session-ctx-delete";
195
201
  deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
196
202
  deleteItem.addEventListener("click", function (e) {
197
203
  e.stopPropagation();
198
204
  closeSessionCtxMenu();
199
- _ctx.showConfirm('Delete "' + (title || "New Session") + '"? This session and its history will be permanently removed.', function () {
200
- var ws = _ctx.ws;
201
- if (ws && _ctx.connected) {
205
+ showConfirm('Delete "' + (title || "New Session") + '"? This session and its history will be permanently removed.', function () {
206
+ var ws = getWs();
207
+ if (ws && store.get('connected')) {
202
208
  ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
203
209
  }
204
210
  });
@@ -241,7 +247,7 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
241
247
  });
242
248
  menu.appendChild(renameItem);
243
249
 
244
- if (!_ctx.permissions || _ctx.permissions.sessionDelete !== false) {
250
+ if (!store.get('permissions') || store.get('permissions').sessionDelete !== false) {
245
251
  var deleteItem = document.createElement("button");
246
252
  deleteItem.className = "session-ctx-item session-ctx-delete";
247
253
  deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
@@ -251,9 +257,9 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
251
257
  var msg = 'Delete "' + (loopName || "Loop") + '"';
252
258
  if (childCount > 1) msg += " and its " + childCount + " sessions";
253
259
  msg += "? This cannot be undone.";
254
- _ctx.showConfirm(msg, function () {
255
- if (_ctx.ws && _ctx.connected) {
256
- _ctx.ws.send(JSON.stringify({ type: "delete_loop_group", loopId: loopId }));
260
+ showConfirm(msg, function () {
261
+ if (getWs() && store.get('connected')) {
262
+ getWs().send(JSON.stringify({ type: "delete_loop_group", loopId: loopId }));
257
263
  }
258
264
  });
259
265
  });
@@ -280,7 +286,7 @@ function showLoopCtxMenu(anchorBtn, loopId, loopName, childCount) {
280
286
  // --- Inline rename ---
281
287
 
282
288
  function startInlineRename(sessionId, currentTitle) {
283
- var el = _ctx.sessionListEl.querySelector('.session-item[data-session-id="' + sessionId + '"]');
289
+ var el = getSessionListEl().querySelector('.session-item[data-session-id="' + sessionId + '"]');
284
290
  if (!el) return;
285
291
  var textSpan = el.querySelector(".session-item-text");
286
292
  if (!textSpan) return;
@@ -298,8 +304,8 @@ function startInlineRename(sessionId, currentTitle) {
298
304
 
299
305
  function commitRename() {
300
306
  var newTitle = input.value.trim();
301
- if (newTitle && newTitle !== currentTitle && _ctx.ws && _ctx.connected) {
302
- _ctx.ws.send(JSON.stringify({ type: "rename_session", id: sessionId, title: newTitle }));
307
+ if (newTitle && newTitle !== currentTitle && getWs() && store.get('connected')) {
308
+ getWs().send(JSON.stringify({ type: "rename_session", id: sessionId, title: newTitle }));
303
309
  }
304
310
  // Restore text (server will send updated session_list)
305
311
  textSpan.innerHTML = originalHtml;
@@ -317,7 +323,7 @@ function startInlineRename(sessionId, currentTitle) {
317
323
  }
318
324
 
319
325
  function startLoopInlineRename(loopId, currentName) {
320
- var el = _ctx.sessionListEl.querySelector('.session-loop-group[data-loop-id="' + loopId + '"]');
326
+ var el = getSessionListEl().querySelector('.session-loop-group[data-loop-id="' + loopId + '"]');
321
327
  if (!el) return;
322
328
  var textSpan = el.querySelector(".session-item-text");
323
329
  if (!textSpan) return;
@@ -335,8 +341,8 @@ function startLoopInlineRename(loopId, currentName) {
335
341
 
336
342
  function commitRename() {
337
343
  var newName = input.value.trim();
338
- if (newName && newName !== currentName && _ctx.ws && _ctx.connected) {
339
- _ctx.ws.send(JSON.stringify({ type: "loop_registry_rename", id: loopId, name: newName }));
344
+ if (newName && newName !== currentName && getWs() && store.get('connected')) {
345
+ getWs().send(JSON.stringify({ type: "loop_registry_rename", id: loopId, name: newName }));
340
346
  }
341
347
  textSpan.innerHTML = originalHtml;
342
348
  if (newName && newName !== currentName) {
@@ -407,10 +413,10 @@ function renderLoopChild(s) {
407
413
 
408
414
  el.addEventListener("click", (function (id) {
409
415
  return function () {
410
- if (_ctx.ws && _ctx.connected) {
411
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
412
- _ctx.dismissOverlayPanels();
413
- _ctx.closeSidebar();
416
+ if (getWs() && store.get('connected')) {
417
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
418
+ dismissOverlayPanels();
419
+ closeSidebar();
414
420
  }
415
421
  };
416
422
  })(s.id));
@@ -530,10 +536,10 @@ function renderLoopGroup(loopId, children, groupKey) {
530
536
  // Click row (not chevron/more) -> switch to latest session
531
537
  el.addEventListener("click", (function (id) {
532
538
  return function () {
533
- if (_ctx.ws && _ctx.connected) {
534
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
535
- _ctx.dismissOverlayPanels();
536
- _ctx.closeSidebar();
539
+ if (getWs() && store.get('connected')) {
540
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
541
+ dismissOverlayPanels();
542
+ closeSidebar();
537
543
  }
538
544
  };
539
545
  })(latestSession.id));
@@ -617,10 +623,10 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
617
623
  // Click row -> switch to latest session of this run
618
624
  el.addEventListener("click", (function (id) {
619
625
  return function () {
620
- if (_ctx.ws && _ctx.connected) {
621
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
622
- _ctx.dismissOverlayPanels();
623
- _ctx.closeSidebar();
626
+ if (getWs() && store.get('connected')) {
627
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
628
+ dismissOverlayPanels();
629
+ closeSidebar();
624
630
  }
625
631
  };
626
632
  })(latestSession.id));
@@ -657,7 +663,7 @@ function renderSessionItem(s) {
657
663
  if (s.loop && s.loop.source === "debate") {
658
664
  textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
659
665
  }
660
- if (_ctx.multiUser && s.sessionVisibility === "private") {
666
+ if (store.get('isMultiUserMode') && s.sessionVisibility === "private") {
661
667
  textHtml += '<span class="session-private-icon" title="Private session">' + iconHtml("lock") + '</span>';
662
668
  }
663
669
  textHtml += highlightMatch(s.title || "New Session", searchQuery);
@@ -685,11 +691,11 @@ function renderSessionItem(s) {
685
691
 
686
692
  el.addEventListener("click", (function (id) {
687
693
  return function () {
688
- if (_ctx.ws && _ctx.connected) {
694
+ if (getWs() && store.get('connected')) {
689
695
  var pendingQuery = searchQuery || "";
690
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
691
- _ctx.dismissOverlayPanels();
692
- _ctx.closeSidebar();
696
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
697
+ dismissOverlayPanels();
698
+ closeSidebar();
693
699
  if (pendingQuery) {
694
700
  setTimeout(function () { openSessionSearch(pendingQuery); }, 400);
695
701
  }
@@ -709,9 +715,9 @@ export function renderSessionList(sessions) {
709
715
  if (sessions) cachedSessions = sessions;
710
716
 
711
717
  // If mobile chat sheet is open, refresh it
712
- if (_ctx.refreshMobileChatSheet) _ctx.refreshMobileChatSheet();
718
+ if (refreshMobileChatSheet) refreshMobileChatSheet();
713
719
 
714
- _ctx.sessionListEl.innerHTML = "";
720
+ getSessionListEl().innerHTML = "";
715
721
 
716
722
  // Partition: loop sessions vs normal sessions
717
723
  // Group by loopId + date so all runs of the same task on the same day are merged
@@ -765,16 +771,16 @@ export function renderSessionList(sessions) {
765
771
  var header = document.createElement("div");
766
772
  header.className = "session-group-header";
767
773
  header.textContent = group;
768
- _ctx.sessionListEl.appendChild(header);
774
+ getSessionListEl().appendChild(header);
769
775
  }
770
776
  if (item.type === "loop") {
771
- _ctx.sessionListEl.appendChild(renderLoopGroup(item.loopId, item.children, item.groupKey));
777
+ getSessionListEl().appendChild(renderLoopGroup(item.loopId, item.children, item.groupKey));
772
778
  } else {
773
- _ctx.sessionListEl.appendChild(renderSessionItem(item.data));
779
+ getSessionListEl().appendChild(renderSessionItem(item.data));
774
780
  }
775
781
  }
776
782
  refreshIcons();
777
- if (_ctx.updatePageTitle) _ctx.updatePageTitle();
783
+ if (updatePageTitle) updatePageTitle();
778
784
  }
779
785
 
780
786
  // --- Search results ---
@@ -794,7 +800,7 @@ export function handleSearchResults(msg) {
794
800
  export function updateSessionPresence(presence) {
795
801
  sessionPresence = presence;
796
802
  // Update presence avatars on existing session items without full re-render
797
- var items = _ctx.sessionListEl.querySelectorAll("[data-session-id]");
803
+ var items = getSessionListEl().querySelectorAll("[data-session-id]");
798
804
  for (var i = 0; i < items.length; i++) {
799
805
  renderPresenceAvatars(items[i], items[i].dataset.sessionId);
800
806
  }
@@ -866,11 +872,11 @@ function startCountdownTimer() {
866
872
  }
867
873
 
868
874
  function updateCountdowns() {
869
- if (!_ctx || !_ctx.getUpcomingSchedules || !_ctx.sessionListEl) return;
870
- var upcoming = _ctx.getUpcomingSchedules(3 * 60 * 1000); // 3 minutes
875
+ if (!getSessionListEl()) return;
876
+ var upcoming = getUpcomingSchedules(3 * 60 * 1000); // 3 minutes
871
877
 
872
878
  // Remove stale container
873
- if (countdownContainer && !_ctx.sessionListEl.contains(countdownContainer)) {
879
+ if (countdownContainer && !getSessionListEl().contains(countdownContainer)) {
874
880
  countdownContainer = null;
875
881
  }
876
882
 
@@ -885,7 +891,7 @@ function updateCountdowns() {
885
891
  if (!countdownContainer) {
886
892
  countdownContainer = document.createElement("div");
887
893
  countdownContainer.className = "session-countdown-group";
888
- _ctx.sessionListEl.insertBefore(countdownContainer, _ctx.sessionListEl.firstChild);
894
+ getSessionListEl().insertBefore(countdownContainer, getSessionListEl().firstChild);
889
895
  }
890
896
 
891
897
  var html = "";
@@ -922,9 +928,9 @@ function relativeTime(isoString) {
922
928
  }
923
929
 
924
930
  export function populateCliSessionList(sessions) {
925
- var pickerLoading = _ctx.$("resume-picker-loading");
926
- var pickerEmpty = _ctx.$("resume-picker-empty");
927
- var pickerList = _ctx.$("resume-picker-list");
931
+ var pickerLoading = document.getElementById("resume-picker-loading");
932
+ var pickerEmpty = document.getElementById("resume-picker-empty");
933
+ var pickerList = document.getElementById("resume-picker-list");
928
934
  if (!pickerLoading || !pickerList) return;
929
935
 
930
936
  pickerLoading.classList.add("hidden");
@@ -972,12 +978,12 @@ export function populateCliSessionList(sessions) {
972
978
 
973
979
  (function (sessionId) {
974
980
  item.addEventListener("click", function () {
975
- if (_ctx.ws && _ctx.connected) {
976
- _ctx.ws.send(JSON.stringify({ type: "resume_session", cliSessionId: sessionId }));
981
+ if (getWs() && store.get('connected')) {
982
+ getWs().send(JSON.stringify({ type: "resume_session", cliSessionId: sessionId }));
977
983
  }
978
- var modal = _ctx.$("resume-modal");
984
+ var modal = document.getElementById("resume-modal");
979
985
  if (modal) modal.classList.add("hidden");
980
- _ctx.closeSidebar();
986
+ closeSidebar();
981
987
  });
982
988
  })(s.sessionId);
983
989
 
@@ -13,8 +13,13 @@ import {
13
13
  import { initSidebarMobile } from './sidebar-mobile.js';
14
14
 
15
15
  var ctx;
16
+ var _syncResizeHandles = null;
16
17
 
17
- function dismissOverlayPanels() {
18
+ export function syncResizeHandles() {
19
+ if (_syncResizeHandles) _syncResizeHandles();
20
+ }
21
+
22
+ export function dismissOverlayPanels() {
18
23
  closeArchive();
19
24
  closeScheduler();
20
25
  }
@@ -56,21 +61,11 @@ export function closeSidebar() {
56
61
  export function initSidebar(_ctx) {
57
62
  ctx = _ctx;
58
63
 
59
- // Initialize session sub-module with sidebar callbacks
60
- ctx.closeSidebar = closeSidebar;
61
- ctx.dismissOverlayPanels = dismissOverlayPanels;
62
- ctx.updatePageTitle = updatePageTitle;
63
- ctx.showIconTooltip = showIconTooltip;
64
- ctx.showIconTooltipHtml = showIconTooltipHtml;
65
- ctx.hideIconTooltip = hideIconTooltip;
66
- ctx.closeUserCtxMenu = closeUserCtxMenu;
67
- ctx.closeProjectCtxMenu = closeProjectCtxMenu;
68
- ctx.getCurrentDmUserId = getCurrentDmUserId;
69
- ctx.spawnDustParticles = spawnDustParticles;
70
- initSidebarSessions(ctx);
71
- initSidebarProjects(ctx);
72
- initSidebarMates(ctx);
73
- initSidebarMobile(ctx);
64
+ // Initialize sidebar sub-modules (they import their own deps directly)
65
+ initSidebarSessions();
66
+ initSidebarProjects();
67
+ initSidebarMates();
68
+ initSidebarMobile();
74
69
 
75
70
  ctx.hamburgerBtn.addEventListener("click", function () {
76
71
  ctx.sidebar.classList.contains("open") ? closeSidebar() : openSidebar();
@@ -128,7 +123,6 @@ export function initSidebar(_ctx) {
128
123
  var sessionsPanel = ctx.$("sidebar-panel-sessions");
129
124
  var filesPanel = ctx.$("sidebar-panel-files");
130
125
  var sessionsHeaderContent = ctx.$("sessions-header-content");
131
- var filesHeaderContent = ctx.$("files-header-content");
132
126
  var filePanelClose = ctx.$("file-panel-close");
133
127
 
134
128
  function hideAllPanels() {
@@ -136,7 +130,6 @@ export function initSidebar(_ctx) {
136
130
  if (sessionsPanel) sessionsPanel.classList.add("hidden");
137
131
  if (filesPanel) filesPanel.classList.add("hidden");
138
132
  if (sessionsHeaderContent) sessionsHeaderContent.classList.add("hidden");
139
- if (filesHeaderContent) filesHeaderContent.classList.add("hidden");
140
133
  }
141
134
 
142
135
  function showProjectsPanel() {
@@ -157,7 +150,6 @@ export function initSidebar(_ctx) {
157
150
  filesPanel.classList.remove("fb-exit");
158
151
  filesPanel.classList.add("fb-enter");
159
152
  }
160
- if (filesHeaderContent) filesHeaderContent.classList.remove("hidden");
161
153
  if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
162
154
  }
163
155
 
@@ -364,7 +356,7 @@ export function initSidebar(_ctx) {
364
356
  }
365
357
 
366
358
  // Expose for external callers (e.g. after DM exit)
367
- ctx.syncResizeHandles = function () {
359
+ _syncResizeHandles = ctx.syncResizeHandles = function () {
368
360
  // Restore project sidebar width from localStorage (may have changed during DM)
369
361
  try {
370
362
  var sw = localStorage.getItem("sidebar-width");
@@ -1,21 +1,20 @@
1
1
  import { iconHtml, refreshIcons } from './icons.js';
2
2
  import { escapeHtml, copyToClipboard, showToast } from './utils.js';
3
+ import { store } from './store.js';
3
4
 
4
- var ctx;
5
5
  var modal;
6
6
  var contentEl;
7
7
  var activeTab = "all";
8
8
  var currentView = "list"; // "list" or "detail"
9
9
  var skillsData = {}; // cache per tab
10
- var basePath = "";
11
10
  var installedSkills = {}; // { skillName: { scope: "global"|"project"|"both" } }
12
11
  var searchQuery = "";
13
12
  var searchTimer = null;
14
13
  var searchCache = {}; // cache per query
15
14
 
16
- export function initSkills(_ctx) {
17
- ctx = _ctx;
18
- basePath = _ctx.basePath || "";
15
+ function basePath() { return store.get('basePath') || ""; }
16
+
17
+ export function initSkills() {
19
18
 
20
19
  modal = document.getElementById("skills-modal");
21
20
  contentEl = document.getElementById("skills-content");
@@ -134,7 +133,7 @@ export function initSkills(_ctx) {
134
133
  }
135
134
 
136
135
  function fetchInstalledSkills() {
137
- return fetch(basePath + "api/installed-skills")
136
+ return fetch(basePath() + "api/installed-skills")
138
137
  .then(function (res) { return res.json(); })
139
138
  .then(function (data) {
140
139
  installedSkills = data.installed || {};
@@ -225,7 +224,7 @@ function loadSkills(tab) {
225
224
  function loadInstalledSkills() {
226
225
  contentEl.innerHTML = '<div class="skills-loading"><div class="skills-spinner"></div> Loading installed skills...</div>';
227
226
 
228
- fetch(basePath + "api/installed-skills")
227
+ fetch(basePath() + "api/installed-skills")
229
228
  .then(function (res) { return res.json(); })
230
229
  .then(function (data) {
231
230
  installedSkills = data.installed || {};
@@ -563,7 +562,7 @@ function installSkill(source, skill, scope) {
563
562
  btns[i].innerHTML = '<div class="skills-btn-spinner"></div> Installing...';
564
563
  }
565
564
 
566
- fetch(basePath + "api/install-skill", {
565
+ fetch(basePath() + "api/install-skill", {
567
566
  method: "POST",
568
567
  headers: { "Content-Type": "application/json" },
569
568
  body: JSON.stringify({ url: url, skill: skill, scope: scope }),
@@ -631,7 +630,7 @@ function uninstallSkill(skill, scope) {
631
630
  unBtn.innerHTML = '<div class="skills-btn-spinner small"></div>';
632
631
  }
633
632
 
634
- fetch(basePath + "api/uninstall-skill", {
633
+ fetch(basePath() + "api/uninstall-skill", {
635
634
  method: "POST",
636
635
  headers: { "Content-Type": "application/json" },
637
636
  body: JSON.stringify({ skill: skill, scope: scope }),
@@ -885,8 +885,7 @@ export function handleNotesList(msg) {
885
885
  updateBadge();
886
886
  updateSidebarBadge();
887
887
 
888
- // If user already has notes, they know the feature dismiss onboarding
889
- if (list.length > 0) dismissOnboarding();
888
+ // If user already has notes, they know the feature (no onboarding needed)
890
889
 
891
890
  // Auto-show if there are notes
892
891
  if (list.length > 0 && !notesVisible) {
@@ -10,12 +10,19 @@ function createStore(initial) {
10
10
  }
11
11
 
12
12
  var store = {
13
- getState: function () { return _state; },
14
- setState: function (partial) {
13
+ // Read a single field: store.get('dmMode')
14
+ get: function (key) { return _state[key]; },
15
+ // Grab full snapshot for multi-field reads: var s = store.snap();
16
+ snap: function () { return _state; },
17
+ // Shallow-merge update: store.set({ dmMode: true, dmKey: 'abc' })
18
+ set: function (partial) {
15
19
  var prev = _state;
16
20
  _state = Object.assign({}, _state, partial);
17
21
  for (var i = 0; i < _listeners.length; i++) _listeners[i](_state, prev);
18
22
  },
23
+ // Keep legacy aliases for gradual migration
24
+ getState: function () { return _state; },
25
+ setState: function (partial) { store.set(partial); },
19
26
  subscribe: function (listener) {
20
27
  _listeners.push(listener);
21
28
  return function () {
@@ -3,6 +3,8 @@
3
3
 
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
5
  import { autoResize } from './input.js';
6
+ import { store } from './store.js';
7
+ import { VENDOR_NAMES } from './app-rendering.js';
6
8
 
7
9
  var ctx;
8
10
 
@@ -253,7 +255,8 @@ function stopRecording() {
253
255
  if (document.body.classList.contains("mate-dm-active") && document.body.dataset.mateName) {
254
256
  ctx.inputEl.setAttribute('placeholder', 'Message ' + document.body.dataset.mateName + '...');
255
257
  } else {
256
- ctx.inputEl.setAttribute('placeholder', 'Message Claude Code...');
258
+ var _v = store.get('currentVendor') || "claude";
259
+ ctx.inputEl.setAttribute('placeholder', 'Message ' + (VENDOR_NAMES[_v] || VENDOR_NAMES.claude) + '...');
257
260
  }
258
261
  }
259
262
 
@@ -152,6 +152,18 @@ export function initTerminal(_ctx) {
152
152
  fitTerminal();
153
153
  });
154
154
 
155
+ // Header toggle button
156
+ var toggleBtn = document.getElementById("terminal-toggle-btn");
157
+ if (toggleBtn) {
158
+ toggleBtn.addEventListener("click", function () {
159
+ if (isOpen && !ctx.terminalContainerEl.classList.contains("hidden")) {
160
+ closeTerminal();
161
+ } else {
162
+ openTerminal();
163
+ }
164
+ });
165
+ }
166
+
155
167
  // Sidebar terminal button
156
168
  var sidebarTermBtn = document.getElementById("terminal-sidebar-btn");
157
169
  if (sidebarTermBtn) {
@@ -5,6 +5,8 @@ import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
5
5
  import { openFile } from './filebrowser.js';
6
6
  import { mateAvatarUrl } from './avatar.js';
7
7
  import { getChatLayout } from './theme.js';
8
+ import { store } from './store.js';
9
+ import { VENDOR_NAMES } from './app-rendering.js';
8
10
 
9
11
  var ctx;
10
12
 
@@ -367,7 +369,8 @@ export function enableMainInput() {
367
369
  if (document.body.classList.contains("mate-dm-active") && document.body.dataset.mateName) {
368
370
  ctx.inputEl.placeholder = "Message " + document.body.dataset.mateName + "...";
369
371
  } else {
370
- ctx.inputEl.placeholder = "Message Claude Code...";
372
+ var _v = store.get('currentVendor') || "claude";
373
+ ctx.inputEl.placeholder = "Message " + (VENDOR_NAMES[_v] || VENDOR_NAMES.claude) + "...";
371
374
  }
372
375
  }
373
376
 
@@ -485,7 +488,7 @@ function permissionInputSummary(toolName, input) {
485
488
  }
486
489
  }
487
490
 
488
- export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason, mateId) {
491
+ export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason, mateId, vendor) {
489
492
  if (pendingPermissions[requestId]) return;
490
493
  ctx.finalizeAssistantBlock();
491
494
  stopThinking();
@@ -499,7 +502,7 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
499
502
 
500
503
  // Channel layout or Mate DM: conversational "Can I ...?" style
501
504
  if ((ctx.isMateDm && ctx.isMateDm()) || getChatLayout() === "channel") {
502
- renderConversationalPermission(requestId, toolName, toolInput, mateId);
505
+ renderConversationalPermission(requestId, toolName, toolInput, mateId, vendor);
503
506
  return;
504
507
  }
505
508
 
@@ -566,7 +569,7 @@ function renderFormalPermission(requestId, toolName, toolInput, decisionReason)
566
569
 
567
570
  var allowAlwaysBtn = document.createElement("button");
568
571
  allowAlwaysBtn.className = "permission-btn permission-allow-session";
569
- allowAlwaysBtn.textContent = "Always Allow";
572
+ allowAlwaysBtn.textContent = "Allow for Session";
570
573
  allowAlwaysBtn.addEventListener("click", function () {
571
574
  sendPermissionResponse(container, requestId, "allow_always");
572
575
  });
@@ -761,7 +764,7 @@ function matePermissionInfo(toolName, toolInput) {
761
764
  return { verb: verb, target: target };
762
765
  }
763
766
 
764
- function resolvePermissionIdentity(mateId) {
767
+ function resolvePermissionIdentity(mateId, vendor) {
765
768
  // Mate DM: use DM target mate info
766
769
  if (ctx.isMateDm && ctx.isMateDm()) {
767
770
  var name = ctx.getMateName();
@@ -786,15 +789,17 @@ function resolvePermissionIdentity(mateId) {
786
789
  };
787
790
  }
788
791
  }
789
- // Project chat (Claude Code)
792
+ // Project chat: use vendor name and avatar
793
+ var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png", gemini: "/gemini-avatar.png" };
794
+ var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
790
795
  return {
791
- name: "Claude Code",
792
- avatar: ctx.getClaudeAvatar ? ctx.getClaudeAvatar() : ""
796
+ name: vendorName,
797
+ avatar: (vendor && vendorAvatars[vendor]) || vendorAvatars.claude,
793
798
  };
794
799
  }
795
800
 
796
- function renderConversationalPermission(requestId, toolName, toolInput, mateId) {
797
- var identity = resolvePermissionIdentity(mateId);
801
+ function renderConversationalPermission(requestId, toolName, toolInput, mateId, vendor) {
802
+ var identity = resolvePermissionIdentity(mateId, vendor);
798
803
  var info = matePermissionInfo(toolName, toolInput);
799
804
  var askMsg = "Can I " + info.verb + (info.target ? " " + info.target : "") + "?";
800
805
 
@@ -851,7 +856,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId)
851
856
 
852
857
  var alwaysBtn = document.createElement("button");
853
858
  alwaysBtn.className = "mate-permission-reply mate-permission-always";
854
- alwaysBtn.textContent = "Always allow";
859
+ alwaysBtn.textContent = "Allow for session";
855
860
  alwaysBtn.addEventListener("click", function () {
856
861
  sendPermissionResponse(container, requestId, "allow_always");
857
862
  });
@@ -882,7 +887,7 @@ function sendPermissionResponse(container, requestId, decision) {
882
887
  container.classList.add("resolved");
883
888
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
884
889
 
885
- var label = decision === "deny" ? "Denied" : "Allowed";
890
+ var label = decision === "deny" ? "Denied" : decision === "allow_always" ? "Allowed for session" : "Allowed";
886
891
  var resolvedClass = decision === "deny" ? "resolved-denied" : "resolved-allowed";
887
892
  container.classList.add(resolvedClass);
888
893
 
@@ -923,7 +928,7 @@ export function markPermissionResolved(requestId, decision) {
923
928
  var resolvedClass = isDeny ? "resolved-denied" : "resolved-allowed";
924
929
  container.classList.add(resolvedClass);
925
930
 
926
- var label = planLabelMap[decision] || (decision === "deny" ? "Denied" : "Allowed");
931
+ var label = planLabelMap[decision] || (decision === "deny" ? "Denied" : decision === "allow_always" ? "Allowed for session" : "Allowed");
927
932
  var actions = container.querySelector(".permission-actions") || container.querySelector(".plan-permission-actions");
928
933
  if (actions) {
929
934
  actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';