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
@@ -1,6 +1,7 @@
1
1
  import { avatarUrl, mateAvatarUrl } from './avatar.js';
2
2
  import { escapeHtml } from './utils.js';
3
3
  import { iconHtml, refreshIcons } from './icons.js';
4
+ import { store } from './store.js';
4
5
  import { hideKnowledge } from './mate-knowledge.js';
5
6
  import { isSchedulerOpen, closeScheduler } from './scheduler.js';
6
7
  import { hideNotes } from './sticky-notes.js';
@@ -150,6 +151,43 @@ export function showMateSidebar(mateId, mateData) {
150
151
  if (avatarEl) avatarEl.src = mateAvUrl;
151
152
  if (nameEl) nameEl.textContent = displayName;
152
153
 
154
+ // Vendor toggle in header
155
+ var mateVendorWrap = document.getElementById("mate-vendor-toggle");
156
+ if (mateVendorWrap) {
157
+ var available = store.get('availableVendors') || [];
158
+ var mateVendor = mateData.vendor || "claude";
159
+ var vendorIcons = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" };
160
+ var vendorNames = { claude: "Claude Code", codex: "Codex" };
161
+ var vendorKeys = ["claude", "codex"];
162
+ mateVendorWrap.innerHTML = "";
163
+ for (var vi = 0; vi < vendorKeys.length; vi++) {
164
+ var vk = vendorKeys[vi];
165
+ var vBtn = document.createElement("button");
166
+ vBtn.className = "mate-vendor-btn" + (vk === mateVendor ? " active" : "");
167
+ if (available.indexOf(vk) === -1) vBtn.classList.add("disabled");
168
+ vBtn.dataset.vendor = vk;
169
+ vBtn.innerHTML = '<img src="' + vendorIcons[vk] + '" class="mate-vendor-icon" alt="' + vendorNames[vk] + '"><span class="mate-vendor-label">' + vendorNames[vk] + '</span>';
170
+ vBtn.addEventListener("click", function() {
171
+ var v = this.dataset.vendor;
172
+ var avail = store.get('availableVendors') || [];
173
+ if (avail.indexOf(v) === -1) return;
174
+ if (v === (currentMate && currentMate.vendor || "claude")) return;
175
+ if (!currentMateId) return;
176
+ var ws = getWs();
177
+ if (ws) ws.send(JSON.stringify({ type: "mate_update", mateId: currentMateId, updates: { vendor: v } }));
178
+ // Update UI immediately
179
+ mateVendorWrap.querySelectorAll(".mate-vendor-btn").forEach(function(b) {
180
+ b.classList.toggle("active", b.dataset.vendor === v);
181
+ });
182
+ if (currentMate) currentMate.vendor = v;
183
+ // If current session is new (no history), sync vendor to input toggle
184
+ var _hTotal = store.get('historyTotal') || 0;
185
+ if (_hTotal === 0) store.set({ currentVendor: v });
186
+ });
187
+ mateVendorWrap.appendChild(vBtn);
188
+ }
189
+ }
190
+
153
191
  // Also populate collapsed header info
154
192
  var collapsedAvatar = document.getElementById("mate-collapsed-avatar");
155
193
  var collapsedName = document.getElementById("mate-collapsed-name");
@@ -2,6 +2,7 @@ import { mateAvatarUrl, userAvatarUrl } from './avatar.js';
2
2
  import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
3
3
  import { escapeHtml, copyToClipboard } from './utils.js';
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
+ import { store } from './store.js';
5
6
 
6
7
  var ctx;
7
8
 
@@ -77,13 +78,17 @@ export function showMentionMenu(query) {
77
78
  var menuEl = document.getElementById("mention-menu");
78
79
  if (!menuEl) return;
79
80
 
80
- menuEl.innerHTML = mentionFiltered.map(function (m, i) {
81
+ menuEl.innerHTML = '<div class="mention-hint">Mention a Mate to get advice on your current session<button class="mention-close-btn" aria-label="Close">&times;</button></div>' +
82
+ mentionFiltered.map(function (m, i) {
81
83
  var name = (m.profile && m.profile.displayName) || m.name || "Mate";
82
84
  var color = (m.profile && m.profile.avatarColor) || "#6c5ce7";
83
85
  var bio = m.bio || (m.profile && m.profile.bio) || "";
84
86
  var avatarSrc = mateAvatarUrl(m, 24);
87
+ var mVendor = m.vendor || "claude";
88
+ var vendorIcons = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" };
89
+ var vendorBadge = vendorIcons[mVendor] ? '<img class="mention-item-vendor-badge" src="' + vendorIcons[mVendor] + '" alt="' + mVendor + '">' : '';
85
90
  return '<div class="mention-item' + (i === 0 ? ' active' : '') + '" data-idx="' + i + '">' +
86
- '<img class="mention-item-avatar" src="' + escapeHtml(avatarSrc) + '" width="24" height="24" />' +
91
+ '<div class="mention-item-avatar-wrap"><img class="mention-item-avatar" src="' + escapeHtml(avatarSrc) + '" width="24" height="24" />' + vendorBadge + '</div>' +
87
92
  '<div class="mention-item-info">' +
88
93
  '<span class="mention-item-name">' + escapeHtml(name) +
89
94
  (m.primary ? ' <span class="mention-item-badge">SYSTEM</span>' : '') +
@@ -95,6 +100,18 @@ export function showMentionMenu(query) {
95
100
  }).join("");
96
101
  menuEl.classList.add("visible");
97
102
 
103
+ var closeBtn = menuEl.querySelector(".mention-close-btn");
104
+ if (closeBtn) closeBtn.addEventListener("click", function (e) {
105
+ e.preventDefault();
106
+ e.stopPropagation();
107
+ hideMentionMenu();
108
+ clearMentionState();
109
+ if (ctx && ctx.inputEl) {
110
+ ctx.inputEl.value = ctx.inputEl.value.replace(/@\S*$/, "");
111
+ ctx.inputEl.focus();
112
+ }
113
+ });
114
+
98
115
  menuEl.querySelectorAll(".mention-item").forEach(function (el) {
99
116
  el.addEventListener("click", function (e) {
100
117
  e.preventDefault();
@@ -215,10 +232,11 @@ function showInputMentionChip(name, color, avatarSrc) {
215
232
  '<button class="input-mention-chip-remove" type="button" aria-label="Remove mention">&times;</button>';
216
233
  chip.style.setProperty("--chip-color", color);
217
234
 
218
- // Insert before the textarea inside input-row
235
+ // Insert before the textarea wrapper inside input-row
219
236
  var inputRow = document.getElementById("input-row");
220
- if (inputRow && ctx.inputEl) {
221
- inputRow.insertBefore(chip, ctx.inputEl);
237
+ var textareaWrap = document.getElementById("input-textarea-wrap");
238
+ if (inputRow && textareaWrap) {
239
+ inputRow.insertBefore(chip, textareaWrap);
222
240
  }
223
241
 
224
242
  chip.querySelector(".input-mention-chip-remove").addEventListener("click", function (e) {
@@ -713,7 +731,7 @@ export function renderMentionUser(entry) {
713
731
  '<span class="msg-action-time">' + ts2 + '</span>' +
714
732
  '<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
715
733
  '<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
716
- '<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' +
734
+ (((store.get('vendorCapabilities') || {}).rewind !== false) ? '<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' : '') +
717
735
  '<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + iconHtml("pencil") + '</button>';
718
736
  div.appendChild(actions);
719
737
 
@@ -790,7 +790,7 @@ function renderModelTab(bodyEl, rec) {
790
790
  }
791
791
 
792
792
  var opts = {
793
- models: store.getState().currentModels || [],
793
+ models: store.get('currentModels') || [],
794
794
  currentModel: settings.model || "",
795
795
  currentMode: settings.permissionMode || "default",
796
796
  currentEffort: settings.effort || "medium",
@@ -5,8 +5,18 @@ import { userAvatarUrl, mateAvatarUrl } from './avatar.js';
5
5
  import { escapeHtml } from './utils.js';
6
6
  import { iconHtml, refreshIcons } from './icons.js';
7
7
  import { showMateProfilePopover } from './profile.js';
8
-
9
- var _ctx = null;
8
+ import { store } from './store.js';
9
+ import { getWs } from './ws-ref.js';
10
+ import { closeProjectCtxMenu } from './sidebar-projects.js';
11
+ import { spawnDustParticles } from './sidebar.js';
12
+ import { openDm } from './app-dm.js';
13
+ import { openMateWizard } from './mate-wizard.js';
14
+ import { getCachedProjects } from './app-projects.js';
15
+
16
+ function sendWs(msg) {
17
+ var ws = getWs();
18
+ if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg));
19
+ }
10
20
 
11
21
  // --- User strip state ---
12
22
  var cachedAllUsers = [];
@@ -19,6 +29,16 @@ var currentDmUserId = null;
19
29
  var dmPickerOpen = false;
20
30
  var cachedDmRemovedUsers = {};
21
31
  var cachedMates = [];
32
+ var activeMentionMateIds = {};
33
+
34
+ export function setMentionActive(mateId, active) {
35
+ if (active) { activeMentionMateIds[mateId] = true; }
36
+ else { delete activeMentionMateIds[mateId]; }
37
+ }
38
+
39
+ export function clearAllMentionActive() {
40
+ activeMentionMateIds = {};
41
+ }
22
42
  var _lastUserStripJson = "";
23
43
 
24
44
  // --- Icon strip tooltip ---
@@ -27,8 +47,20 @@ var iconStripTooltip = null;
27
47
  // --- DM user context menu ---
28
48
  var userCtxMenu = null;
29
49
 
30
- export function initSidebarMates(ctx) {
31
- _ctx = ctx;
50
+ export function initSidebarMates() {
51
+ // --- Reactive UI sync for user strip ---
52
+ store.subscribe(function (state, prev) {
53
+ if (state.cachedAllUsers !== prev.cachedAllUsers ||
54
+ state.cachedOnlineIds !== prev.cachedOnlineIds ||
55
+ state.cachedDmFavorites !== prev.cachedDmFavorites ||
56
+ state.cachedDmConversations !== prev.cachedDmConversations ||
57
+ state.dmUnread !== prev.dmUnread ||
58
+ state.dmRemovedUsers !== prev.dmRemovedUsers ||
59
+ state.cachedMatesList !== prev.cachedMatesList ||
60
+ state.myUserId !== prev.myUserId) {
61
+ renderUserStrip();
62
+ }
63
+ });
32
64
  }
33
65
 
34
66
  export function showIconTooltip(el, text) {
@@ -80,7 +112,7 @@ export function closeUserCtxMenu() {
80
112
 
81
113
  function showUserCtxMenu(anchorEl, user) {
82
114
  closeUserCtxMenu();
83
- if (_ctx.closeProjectCtxMenu) _ctx.closeProjectCtxMenu();
115
+ if (closeProjectCtxMenu) closeProjectCtxMenu();
84
116
 
85
117
  var menu = document.createElement("div");
86
118
  menu.className = "project-ctx-menu";
@@ -92,16 +124,14 @@ function showUserCtxMenu(anchorEl, user) {
92
124
  e.stopPropagation();
93
125
  // Spawn dust particles at the user icon position
94
126
  var iconRect = anchorEl.getBoundingClientRect();
95
- if (_ctx.spawnDustParticles) _ctx.spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
127
+ if (spawnDustParticles) spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
96
128
  closeUserCtxMenu();
97
129
  // Immediately mark as removed so strip re-render hides the icon,
98
130
  // even if the user was only visible via cachedDmConversations (not favorites)
99
131
  cachedDmRemovedUsers[user.id] = true;
100
- if (_ctx.onDmRemoveUser) _ctx.onDmRemoveUser(user.id);
101
- renderUserStrip(cachedAllUsers, cachedOnlineUserIds, cachedMyUserId, cachedDmFavorites, cachedDmConversations, cachedDmUnread, cachedDmRemovedUsers, cachedMates);
102
- if (_ctx.sendWs) {
103
- _ctx.sendWs({ type: "dm_remove_favorite", targetUserId: user.id });
104
- }
132
+ var dr = Object.assign({}, store.get('dmRemovedUsers')); dr[user.id] = true; store.set({ dmRemovedUsers: dr });
133
+ // renderUserStrip is handled by the store subscriber
134
+ sendWs({ type: "dm_remove_favorite", targetUserId: user.id });
105
135
  });
106
136
  menu.appendChild(removeItem);
107
137
 
@@ -140,7 +170,7 @@ function showMateCtxMenu(anchorEl, mate) {
140
170
  if (mate.primary) return;
141
171
 
142
172
  closeUserCtxMenu();
143
- if (_ctx.closeProjectCtxMenu) _ctx.closeProjectCtxMenu();
173
+ if (closeProjectCtxMenu) closeProjectCtxMenu();
144
174
 
145
175
  var menu = document.createElement("div");
146
176
  menu.className = "project-ctx-menu";
@@ -153,9 +183,7 @@ function showMateCtxMenu(anchorEl, mate) {
153
183
  e.stopPropagation();
154
184
  closeUserCtxMenu();
155
185
  showMateProfilePopover(anchorEl, mate, function (updates) {
156
- if (_ctx.sendWs) {
157
- _ctx.sendWs({ type: "mate_update", mateId: mate.id, updates: updates });
158
- }
186
+ sendWs({ type: "mate_update", mateId: mate.id, updates: updates });
159
187
  });
160
188
  });
161
189
  menu.appendChild(editItem);
@@ -168,10 +196,8 @@ function showMateCtxMenu(anchorEl, mate) {
168
196
  closeUserCtxMenu();
169
197
  // Spawn dust particles at the mate icon position
170
198
  var iconRect = anchorEl.getBoundingClientRect();
171
- if (_ctx.spawnDustParticles) _ctx.spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
172
- if (_ctx.sendWs) {
173
- _ctx.sendWs({ type: "dm_remove_favorite", targetUserId: mate.id });
174
- }
199
+ if (spawnDustParticles) spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
200
+ sendWs({ type: "dm_remove_favorite", targetUserId: mate.id });
175
201
  });
176
202
  menu.appendChild(removeItem);
177
203
 
@@ -238,7 +264,20 @@ function presenceAvatarUrl(userOrStyle) {
238
264
  return userAvatarUrl({ avatarStyle: userOrStyle || "thumbs" }, 24);
239
265
  }
240
266
 
267
+ // renderUserStrip: call with no args to read from store (subscriber pattern),
268
+ // or with all 8 args for legacy compatibility.
241
269
  export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites, dmConversations, dmUnread, dmRemovedUsers, matesList) {
270
+ if (arguments.length === 0) {
271
+ var s = store.snap();
272
+ allUsers = s.cachedAllUsers;
273
+ onlineUserIds = s.cachedOnlineIds;
274
+ myUserId = s.myUserId;
275
+ dmFavorites = s.cachedDmFavorites;
276
+ dmConversations = s.cachedDmConversations;
277
+ dmUnread = s.dmUnread;
278
+ dmRemovedUsers = s.dmRemovedUsers;
279
+ matesList = s.cachedMatesList;
280
+ }
242
281
  // Skip full DOM rebuild if input data hasn't changed
243
282
  var fingerprint = JSON.stringify([allUsers, onlineUserIds, dmFavorites, dmConversations, dmUnread, dmRemovedUsers, matesList]);
244
283
  if (fingerprint === _lastUserStripJson) return;
@@ -311,7 +350,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
311
350
 
312
351
  // Click: open DM
313
352
  el.addEventListener("click", function () {
314
- if (_ctx.openDm) _ctx.openDm(u.id);
353
+ if (openDm) openDm(u.id);
315
354
  });
316
355
 
317
356
  // Right-click: show context menu
@@ -327,8 +366,9 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
327
366
 
328
367
  // Build mate project status lookup from project list
329
368
  var mateProjectStatus = {};
330
- if (_ctx && _ctx.projectList) {
331
- var allProjects = _ctx.projectList;
369
+ var _projList = getCachedProjects() || [];
370
+ if (_projList.length) {
371
+ var allProjects = _projList;
332
372
  for (var pi = 0; pi < allProjects.length; pi++) {
333
373
  if (allProjects[pi].isMate) {
334
374
  mateProjectStatus[allProjects[pi].slug] = allProjects[pi];
@@ -380,10 +420,12 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
380
420
  avatar.style.background = mateColor + "30";
381
421
  el.appendChild(avatar);
382
422
 
383
- // Processing status dot (IO blink)
423
+ // Processing status dot (IO blink) - top-left
384
424
  var statusDot = document.createElement("span");
385
425
  statusDot.className = "icon-strip-status";
386
- if (mateProj.isProcessing) statusDot.classList.add("processing");
426
+ var isMentionActive = !!activeMentionMateIds[mate.id];
427
+ if (mateProj.isProcessing || isMentionActive) statusDot.classList.add("processing");
428
+ if (isMentionActive) el.classList.add("mention-active");
387
429
  el.appendChild(statusDot);
388
430
 
389
431
  // Mate badge (bot icon)
@@ -406,6 +448,8 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
406
448
 
407
449
  // Tooltip
408
450
  var displayName = mp.displayName || mate.name || "New Mate";
451
+ var mateVendor = mate.vendor || "claude";
452
+ var vendorLabels = { claude: "Claude Code", codex: "OpenAI Codex" };
409
453
  el.addEventListener("mouseenter", function () {
410
454
  var html = '<div style="font-weight:600">' + escapeHtml(displayName);
411
455
  if (mate.primary) {
@@ -415,13 +459,15 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
415
459
  if (mate.bio) {
416
460
  html += '<div style="font-weight:400;font-size:12px;color:var(--text-secondary);margin-top:2px">' + escapeHtml(mate.bio) + '</div>';
417
461
  }
462
+ var vendorLabel = vendorLabels[mateVendor] || mateVendor;
463
+ html += '<div style="font-size:11px;color:var(--text-dimmer);margin-top:3px">Powered by ' + escapeHtml(vendorLabel) + '</div>';
418
464
  showIconTooltipHtml(el, html);
419
465
  });
420
466
  el.addEventListener("mouseleave", hideIconTooltip);
421
467
 
422
468
  // Click: open DM with mate
423
469
  el.addEventListener("click", function () {
424
- if (_ctx.openDm) _ctx.openDm(mate.id);
470
+ if (openDm) openDm(mate.id);
425
471
  });
426
472
 
427
473
  // Right-click: context menu for mate
@@ -520,9 +566,7 @@ function toggleDmUserPicker(anchorEl) {
520
566
  item.appendChild(name);
521
567
 
522
568
  item.addEventListener("click", function () {
523
- if (_ctx.sendWs) {
524
- _ctx.sendWs({ type: "dm_add_favorite", targetUserId: u.id });
525
- }
569
+ sendWs({ type: "dm_add_favorite", targetUserId: u.id });
526
570
  closeDmUserPicker();
527
571
  });
528
572
 
@@ -570,7 +614,7 @@ function toggleDmUserPicker(anchorEl) {
570
614
  });
571
615
  }
572
616
  // Build unified list: installed builtins, deleted builtins, user-created
573
- var availBuiltins = (_ctx.availableBuiltins && _ctx.availableBuiltins()) || [];
617
+ var availBuiltins = store.get('cachedAvailableBuiltins') || [];
574
618
  var entries = [];
575
619
  // 1. Installed builtin mates
576
620
  for (var si = 0; si < allMates.length; si++) {
@@ -619,12 +663,12 @@ function toggleDmUserPicker(anchorEl) {
619
663
  bAddBtn.title = "Re-add " + b.displayName;
620
664
  bAddBtn.addEventListener("click", function (e) {
621
665
  e.stopPropagation();
622
- if (_ctx.sendWs) _ctx.sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
666
+ sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
623
667
  closeDmUserPicker();
624
668
  });
625
669
  bItem.appendChild(bAddBtn);
626
670
  bItem.addEventListener("click", function () {
627
- if (_ctx.sendWs) _ctx.sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
671
+ sendWs({ type: "mate_readd_builtin", builtinKey: b.key });
628
672
  closeDmUserPicker();
629
673
  });
630
674
  matesListEl.appendChild(bItem);
@@ -675,7 +719,7 @@ function toggleDmUserPicker(anchorEl) {
675
719
  yesBtn.textContent = m.builtinKey ? "Remove" : "Delete";
676
720
  yesBtn.addEventListener("click", function (e2) {
677
721
  e2.stopPropagation();
678
- if (_ctx.sendWs) _ctx.sendWs({ type: "mate_delete", mateId: m.id });
722
+ sendWs({ type: "mate_delete", mateId: m.id });
679
723
  closeDmUserPicker();
680
724
  });
681
725
  item.appendChild(yesBtn);
@@ -693,8 +737,8 @@ function toggleDmUserPicker(anchorEl) {
693
737
  });
694
738
  item.appendChild(delBtn);
695
739
  item.addEventListener("click", function () {
696
- if (_ctx.openDm) _ctx.openDm(m.id);
697
- if (!isFav && _ctx.sendWs) _ctx.sendWs({ type: "dm_add_favorite", targetUserId: m.id });
740
+ if (openDm) openDm(m.id);
741
+ if (!isFav) sendWs({ type: "dm_add_favorite", targetUserId: m.id });
698
742
  closeDmUserPicker();
699
743
  });
700
744
  matesListEl.appendChild(item);
@@ -720,7 +764,7 @@ function toggleDmUserPicker(anchorEl) {
720
764
  createMateEl.innerHTML = iconHtml("bot") + " <span>" + createMateLabel + "</span>";
721
765
  createMateEl.addEventListener("click", function () {
722
766
  closeDmUserPicker();
723
- if (_ctx.openMateWizard) _ctx.openMateWizard();
767
+ if (openMateWizard) openMateWizard();
724
768
  });
725
769
  picker.appendChild(createMateEl);
726
770
 
@@ -26,7 +26,15 @@ import {
26
26
  getCachedDmRemovedUsers
27
27
  } from './sidebar-mates.js';
28
28
 
29
- var _ctx = null;
29
+ import { store } from './store.js';
30
+ import { getWs } from './ws-ref.js';
31
+ import { dismissOverlayPanels, closeSidebar } from './sidebar.js';
32
+ import { switchProject, getCachedProjects } from './app-projects.js';
33
+ import { openDm } from './app-dm.js';
34
+ import { showHomeHub } from './app-home-hub.js';
35
+ import { openTerminal } from './terminal.js';
36
+ import { requestKnowledgeList } from './mate-knowledge.js';
37
+ import { loadRootDirectory } from './filebrowser.js';
30
38
 
31
39
  // --- Mobile state ---
32
40
  var mobileChatSheetOpen = false;
@@ -76,7 +84,7 @@ export function openMobileSheet(type) {
76
84
  listEl.appendChild(fileTree);
77
85
  fileTree.classList.remove("hidden");
78
86
  }
79
- if (_ctx.onFilesTabOpen) _ctx.onFilesTabOpen();
87
+ loadRootDirectory();
80
88
  } else if (type === "mate-knowledge") {
81
89
  titleEl.textContent = "Knowledge";
82
90
  sheet.classList.add("sheet-knowledge");
@@ -86,7 +94,7 @@ export function openMobileSheet(type) {
86
94
  knowledgeFiles.classList.remove("hidden");
87
95
  }
88
96
  // Request knowledge list if not loaded
89
- if (_ctx.requestKnowledgeList) _ctx.requestKnowledgeList();
97
+ requestKnowledgeList();
90
98
  } else if (type === "mate-profile") {
91
99
  titleEl.textContent = "";
92
100
  renderSheetMateProfile(listEl);
@@ -170,7 +178,7 @@ function renderSheetProjects(listEl) {
170
178
  }
171
179
 
172
180
  el.addEventListener("click", function () {
173
- if (_ctx.switchProject) _ctx.switchProject(p.slug);
181
+ if (switchProject) switchProject(p.slug);
174
182
  closeMobileSheet();
175
183
  });
176
184
 
@@ -270,7 +278,7 @@ function renderSheetSessions(listEl) {
270
278
  // Processing dot: same class as icon strip, same data source
271
279
  var mateSlug = "mate-" + mate.id;
272
280
  var mateProj = null;
273
- var allProjects = (_ctx && _ctx.projectList) || [];
281
+ var allProjects = getCachedProjects() || [];
274
282
  for (var pi = 0; pi < allProjects.length; pi++) {
275
283
  if (allProjects[pi].slug === mateSlug) { mateProj = allProjects[pi]; break; }
276
284
  }
@@ -309,7 +317,7 @@ function renderSheetSessions(listEl) {
309
317
  renderMobileSessionsInto(sessionListEl);
310
318
  } else if (type === "mate") {
311
319
  // Mate DM: open the DM and show mate actions
312
- if (_ctx.openDm) _ctx.openDm(mateId);
320
+ openDm(mateId);
313
321
  renderMateMobileActions(sessionListEl);
314
322
  }
315
323
 
@@ -339,7 +347,7 @@ function renderSheetSessions(listEl) {
339
347
  loading.textContent = "Loading sessions...";
340
348
  sessionListEl.appendChild(loading);
341
349
  }
342
- if (_ctx.switchProject) _ctx.switchProject(slug);
350
+ if (switchProject) switchProject(slug);
343
351
  if (!isDmNow || slug !== getCachedCurrentSlug()) {
344
352
  // renderSessionList will be called by WS, which calls refreshMobileChatSheet
345
353
  } else {
@@ -394,10 +402,10 @@ function createMobileSessionItem(s) {
394
402
 
395
403
  (function (id) {
396
404
  el.addEventListener("click", function () {
397
- if (_ctx.ws && _ctx.connected) {
398
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
405
+ if (getWs() && store.get('connected')) {
406
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
399
407
  }
400
- if (_ctx.dismissOverlayPanels) _ctx.dismissOverlayPanels();
408
+ if (dismissOverlayPanels) dismissOverlayPanels();
401
409
  closeMobileSheet();
402
410
  });
403
411
  })(s.id);
@@ -432,10 +440,10 @@ function createMobileLoopChild(s) {
432
440
 
433
441
  (function (id) {
434
442
  el.addEventListener("click", function () {
435
- if (_ctx.ws && _ctx.connected) {
436
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
443
+ if (getWs() && store.get('connected')) {
444
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
437
445
  }
438
- if (_ctx.dismissOverlayPanels) _ctx.dismissOverlayPanels();
446
+ if (dismissOverlayPanels) dismissOverlayPanels();
439
447
  closeMobileSheet();
440
448
  });
441
449
  })(s.id);
@@ -645,8 +653,8 @@ function renderMateMobileActions(container) {
645
653
  newSessionBtn.className = "mobile-session-new";
646
654
  newSessionBtn.innerHTML = '<i data-lucide="plus" style="width:16px;height:16px"></i> New session';
647
655
  newSessionBtn.addEventListener("click", function () {
648
- if (_ctx.ws && _ctx.connected) {
649
- _ctx.ws.send(JSON.stringify({ type: "new_session" }));
656
+ if (getWs() && store.get('connected')) {
657
+ getWs().send(JSON.stringify({ type: "new_session" }));
650
658
  }
651
659
  closeMobileSheet();
652
660
  });
@@ -694,8 +702,8 @@ function renderMobileSessionsInto(container) {
694
702
  newBtn.className = "mobile-session-new";
695
703
  newBtn.innerHTML = '<i data-lucide="plus" style="width:16px;height:16px"></i> New session';
696
704
  newBtn.addEventListener("click", function () {
697
- if (_ctx.ws && _ctx.connected) {
698
- _ctx.ws.send(JSON.stringify({ type: "new_session" }));
705
+ if (getWs() && store.get('connected')) {
706
+ getWs().send(JSON.stringify({ type: "new_session" }));
699
707
  }
700
708
  closeMobileSheet();
701
709
  });
@@ -801,7 +809,7 @@ export function refreshMobileChatSheet() {
801
809
  var statusDot = chip.querySelector(".icon-strip-status");
802
810
  if (statusDot) {
803
811
  var isProcessing = false;
804
- var allProjects = (_ctx && _ctx.projectList) || [];
812
+ var allProjects = getCachedProjects() || [];
805
813
  var lookupSlug = chip.dataset.type === "mate" ? ("mate-" + chip.dataset.mateId) : chip.dataset.slug;
806
814
  for (var pi = 0; pi < allProjects.length; pi++) {
807
815
  if (allProjects[pi].slug === lookupSlug && allProjects[pi].isProcessing) {
@@ -948,10 +956,10 @@ function renderSearchResults(container, query) {
948
956
 
949
957
  (function (id) {
950
958
  el.addEventListener("click", function () {
951
- if (_ctx.ws && _ctx.connected) {
952
- _ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
959
+ if (getWs() && store.get('connected')) {
960
+ getWs().send(JSON.stringify({ type: "switch_session", id: id }));
953
961
  }
954
- if (_ctx.dismissOverlayPanels) _ctx.dismissOverlayPanels();
962
+ if (dismissOverlayPanels) dismissOverlayPanels();
955
963
  closeMobileSheet();
956
964
  });
957
965
  })(s.id);
@@ -993,7 +1001,7 @@ function renderSheetTools(listEl) {
993
1001
  if (item.action === "files") {
994
1002
  setTimeout(function () { openMobileSheet("files"); }, 250);
995
1003
  } else if (item.action === "terminal") {
996
- if (_ctx.openTerminal) _ctx.openTerminal();
1004
+ openTerminal();
997
1005
  } else if (item.action === "scheduler") {
998
1006
  targetId = "scheduler-btn";
999
1007
  } else if (item.action === "mate-knowledge") {
@@ -1055,7 +1063,7 @@ function renderSheetSettings(listEl) {
1055
1063
  }
1056
1064
  }
1057
1065
  }
1058
- if (proj && _ctx.ownerLocked) proj = Object.assign({}, proj, { ownerLocked: true });
1066
+ if (proj && store.get('ownerLocked')) proj = Object.assign({}, proj, { ownerLocked: true });
1059
1067
  openProjectSettings(getCachedCurrentSlug(), proj);
1060
1068
  }, 250);
1061
1069
  } else if (item.action === "server-settings") {
@@ -1124,11 +1132,7 @@ function renderSheetSettings(listEl) {
1124
1132
  }
1125
1133
  }
1126
1134
 
1127
- export function initSidebarMobile(ctx) {
1128
- _ctx = ctx;
1129
-
1130
- // Put refreshMobileChatSheet on ctx for external callers
1131
- ctx.refreshMobileChatSheet = refreshMobileChatSheet;
1135
+ export function initSidebarMobile() {
1132
1136
 
1133
1137
  // --- Mobile sheet close handlers ---
1134
1138
  var mobileSheet = document.getElementById("mobile-sheet");
@@ -1262,9 +1266,9 @@ export function initSidebarMobile(ctx) {
1262
1266
 
1263
1267
  if (mobileHomeBtn) {
1264
1268
  mobileHomeBtn.addEventListener("click", function () {
1265
- if (_ctx.closeSidebar) _ctx.closeSidebar();
1269
+ closeSidebar();
1266
1270
  setMobileTabActive("home");
1267
- if (_ctx.showHomeHub) _ctx.showHomeHub();
1271
+ showHomeHub();
1268
1272
  });
1269
1273
  }
1270
1274
  }