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
@@ -17,6 +17,8 @@ export function initContextSources(_ctx) {
17
17
 
18
18
  var addBtn = document.getElementById("context-sources-add");
19
19
  var picker = document.getElementById("context-sources-picker");
20
+ // Suppress tooltip when the picker is open
21
+ if (addBtn) addBtn.setAttribute("data-tip-suppress-when-open", "#context-sources-picker");
20
22
 
21
23
  addBtn.addEventListener("click", function(e) {
22
24
  e.stopPropagation();
@@ -36,8 +38,22 @@ export function initContextSources(_ctx) {
36
38
 
37
39
  function closePicker() {
38
40
  var picker = document.getElementById("context-sources-picker");
39
- picker.classList.add("hidden");
41
+ if (picker) picker.classList.add("hidden");
40
42
  document.removeEventListener("click", closePicker, true);
43
+ // Also close mobile bottom sheet if open
44
+ var moreSheet = document.getElementById("input-more-sheet");
45
+ if (moreSheet && moreSheet.classList.contains("open")) {
46
+ moreSheet.classList.remove("open");
47
+ setTimeout(function () { moreSheet.classList.add("hidden"); }, 250);
48
+ }
49
+ }
50
+
51
+ // Re-render all open picker surfaces (desktop popover and mobile bottom sheet)
52
+ function renderAllOpen() {
53
+ var picker = document.getElementById("context-sources-picker");
54
+ if (picker && !picker.classList.contains("hidden")) renderPicker();
55
+ var moreSheet = document.getElementById("input-more-sheet");
56
+ if (moreSheet && moreSheet.classList.contains("open")) renderPicker("-mobile");
41
57
  }
42
58
 
43
59
  // Restore state from server
@@ -80,11 +96,7 @@ export function updateTerminalList(terminals) {
80
96
  if (changed) saveToServer();
81
97
  renderChips();
82
98
 
83
- // If picker is open, re-render it
84
- var picker = document.getElementById("context-sources-picker");
85
- if (!picker.classList.contains("hidden")) {
86
- renderPicker();
87
- }
99
+ renderAllOpen();
88
100
  }
89
101
 
90
102
  // Called when Chrome extension sends tab list via postMessage
@@ -110,11 +122,7 @@ export function updateBrowserTabList(tabs) {
110
122
  if (changed) saveToServer();
111
123
  renderChips();
112
124
 
113
- // If picker is open, re-render it
114
- var picker = document.getElementById("context-sources-picker");
115
- if (!picker.classList.contains("hidden")) {
116
- renderPicker();
117
- }
125
+ renderAllOpen();
118
126
  }
119
127
 
120
128
  // Called when email_accounts_list arrives from server
@@ -141,10 +149,7 @@ export function updateEmailAccountList(msg) {
141
149
  if (changed) saveToServer();
142
150
  renderChips();
143
151
 
144
- var picker = document.getElementById("context-sources-picker");
145
- if (!picker.classList.contains("hidden")) {
146
- renderPicker();
147
- }
152
+ renderAllOpen();
148
153
  }
149
154
 
150
155
  // Called when email_unread_update arrives from server
@@ -152,10 +157,7 @@ export function updateEmailUnreadCounts(msg) {
152
157
  emailUnreadCounts = msg.unread || {};
153
158
  renderChips();
154
159
 
155
- var picker = document.getElementById("context-sources-picker");
156
- if (!picker.classList.contains("hidden")) {
157
- renderPicker();
158
- }
160
+ renderAllOpen();
159
161
  }
160
162
 
161
163
  function toggleSource(sourceId) {
@@ -166,7 +168,7 @@ function toggleSource(sourceId) {
166
168
  }
167
169
  saveToServer();
168
170
  renderChips();
169
- renderPicker();
171
+ renderAllOpen();
170
172
  }
171
173
 
172
174
  function removeSource(sourceId) {
@@ -174,63 +176,95 @@ function removeSource(sourceId) {
174
176
  saveToServer();
175
177
  renderChips();
176
178
 
177
- var picker = document.getElementById("context-sources-picker");
178
- if (!picker.classList.contains("hidden")) {
179
- renderPicker();
180
- }
179
+ renderAllOpen();
181
180
  }
182
181
 
183
- function renderChips() {
184
- var container = document.getElementById("context-sources-chips");
185
- container.innerHTML = "";
182
+ function buildActiveSourceRow(iconHtml, text) {
183
+ return '<div class="ctx-tip-row">' + iconHtml + '<span>' + escapeHtml(text) + '</span></div>';
184
+ }
186
185
 
186
+ function getActiveSourceRowsHTML() {
187
+ var rows = [];
187
188
  for (var id of activeSourceIds) {
188
- var chip = document.createElement("div");
189
- chip.className = "context-chip";
190
-
191
- var label = getSourceLabel(id);
192
- var iconName = getSourceIcon(id);
193
-
194
- var labelEl = document.createElement("span");
195
- labelEl.className = "context-chip-label";
196
- labelEl.innerHTML =
197
- '<i data-lucide="' + iconName + '"></i>' +
198
- '<span>' + escapeHtml(label) + '</span>';
199
- chip.appendChild(labelEl);
200
-
201
- var removeBtn = document.createElement("button");
202
- removeBtn.type = "button";
203
- removeBtn.className = "context-chip-remove";
204
- removeBtn.title = "Remove";
205
- removeBtn.innerHTML = '<i data-lucide="minus"></i>';
206
- removeBtn.setAttribute("data-source-id", id);
207
- removeBtn.addEventListener("click", function(e) {
208
- e.stopPropagation();
209
- removeSource(this.getAttribute("data-source-id"));
210
- if (typeof lucide !== "undefined") lucide.createIcons();
211
- });
212
-
213
- chip.appendChild(removeBtn);
214
- container.appendChild(chip);
189
+ var parts = id.split(":");
190
+ var type = parts[0];
191
+ var key = parts.slice(1).join(":");
192
+ if (type === "term") {
193
+ for (var i = 0; i < terminalList.length; i++) {
194
+ if (String(terminalList[i].id) === key) {
195
+ rows.push(buildActiveSourceRow(
196
+ '<i data-lucide="square-terminal"></i>',
197
+ terminalList[i].title || ("Terminal " + key)
198
+ ));
199
+ break;
200
+ }
201
+ }
202
+ } else if (type === "tab") {
203
+ var tabId = parseInt(key, 10);
204
+ for (var j = 0; j < browserTabList.length; j++) {
205
+ if (browserTabList[j].id === tabId) {
206
+ var t = browserTabList[j];
207
+ var title = t.title || t.url || "Tab";
208
+ if (title.length > 50) title = title.slice(0, 47) + "...";
209
+ var faviconHtml = t.favIconUrl
210
+ ? '<img src="' + escapeHtml(t.favIconUrl) + '" class="ctx-tip-favicon" onerror="this.style.display=\'none\'">'
211
+ : '<i data-lucide="globe"></i>';
212
+ rows.push(buildActiveSourceRow(faviconHtml, title));
213
+ break;
214
+ }
215
+ }
216
+ } else if (type === "email") {
217
+ for (var k = 0; k < emailAccountList.length; k++) {
218
+ if (emailAccountList[k].id === key) {
219
+ rows.push(buildActiveSourceRow(
220
+ '<i data-lucide="mail"></i>',
221
+ emailAccountList[k].email
222
+ ));
223
+ break;
224
+ }
225
+ }
226
+ }
215
227
  }
228
+ return rows;
229
+ }
216
230
 
217
- // Update add button label
231
+ function renderChips() {
232
+ // Update add button — show badge count when sources are active
218
233
  var addBtn = document.getElementById("context-sources-add");
219
- var labelSpan = addBtn.querySelector("span");
234
+ var labelSpan = addBtn.querySelector(".ctx-label");
235
+ var existingBadge = addBtn.querySelector(".ctx-badge");
220
236
  if (activeSourceIds.size > 0) {
221
- labelSpan.textContent = "";
222
- labelSpan.style.display = "none";
237
+ if (labelSpan) labelSpan.style.display = "none";
238
+ if (!existingBadge) {
239
+ existingBadge = document.createElement("span");
240
+ existingBadge.className = "ctx-badge";
241
+ addBtn.appendChild(existingBadge);
242
+ }
243
+ existingBadge.textContent = activeSourceIds.size;
244
+ var rows = getActiveSourceRowsHTML();
245
+ if (rows.length > 0) {
246
+ var html = '<div class="ctx-tip-header">Active context sources</div>' + rows.join("");
247
+ addBtn.setAttribute("data-tip-html", html);
248
+ addBtn.removeAttribute("data-tip");
249
+ } else {
250
+ addBtn.setAttribute("data-tip", "Add context sources");
251
+ addBtn.removeAttribute("data-tip-html");
252
+ }
253
+ addBtn.removeAttribute("title");
223
254
  } else {
224
- labelSpan.textContent = "Context Sources";
225
- labelSpan.style.display = "";
255
+ if (labelSpan) { labelSpan.style.display = ""; }
256
+ if (existingBadge) existingBadge.remove();
257
+ addBtn.setAttribute("data-tip", "Add context sources");
258
+ addBtn.removeAttribute("data-tip-html");
259
+ addBtn.removeAttribute("title");
226
260
  }
227
-
228
- if (typeof lucide !== "undefined") lucide.createIcons();
229
261
  }
230
262
 
231
- function renderPicker() {
263
+ export function renderPicker(suffix) {
264
+ suffix = suffix || "";
232
265
  // --- Terminals section ---
233
- var termSection = document.getElementById("context-picker-terminals");
266
+ var termSection = document.getElementById("context-picker-terminals" + suffix);
267
+ if (!termSection) return;
234
268
  termSection.innerHTML = "";
235
269
 
236
270
  var termLabel = document.createElement("div");
@@ -268,7 +302,8 @@ function renderPicker() {
268
302
  }
269
303
 
270
304
  // --- Browser Tabs section ---
271
- var tabSection = document.getElementById("context-picker-tabs");
305
+ var tabSection = document.getElementById("context-picker-tabs" + suffix);
306
+ if (!tabSection) return;
272
307
  tabSection.innerHTML = "";
273
308
 
274
309
  var tabLabel = document.createElement("div");
@@ -328,7 +363,8 @@ function renderPicker() {
328
363
  }
329
364
 
330
365
  // --- Email Accounts section ---
331
- var emailSection = document.getElementById("context-picker-email");
366
+ var emailSection = document.getElementById("context-picker-email" + suffix);
367
+ if (!emailSection) return;
332
368
  emailSection.innerHTML = "";
333
369
 
334
370
  var emailLabel = document.createElement("div");
@@ -0,0 +1,21 @@
1
+ // dom-refs.js - Shared DOM element references
2
+ // Lazy-cached getElementById lookups for elements used across multiple modules.
3
+ // Same pattern as ws-ref.js: infrastructure singleton, not state.
4
+
5
+ var _cache = {};
6
+
7
+ function ref(id) {
8
+ if (!_cache[id]) _cache[id] = document.getElementById(id);
9
+ return _cache[id];
10
+ }
11
+
12
+ export function getMessagesEl() { return ref("messages"); }
13
+ export function getInputEl() { return ref("input"); }
14
+ export function getSendBtn() { return ref("send-btn"); }
15
+ export function getSessionListEl() { return ref("session-list"); }
16
+
17
+ export function getStatusDot() {
18
+ return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
19
+ document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
20
+ document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
21
+ }
@@ -179,6 +179,35 @@ export function initFileBrowser(_ctx) {
179
179
  closeFileViewer();
180
180
  }
181
181
  });
182
+
183
+ // --- File search ---
184
+ var fbSearchInput = document.getElementById("fb-search-input");
185
+ var searchDebounce = null;
186
+
187
+ if (fbSearchInput) {
188
+ fbSearchInput.addEventListener("input", function () {
189
+ var q = fbSearchInput.value.trim();
190
+ if (searchDebounce) clearTimeout(searchDebounce);
191
+ if (!q) {
192
+ renderTree();
193
+ restoreExpanded({});
194
+ return;
195
+ }
196
+ searchDebounce = setTimeout(function () {
197
+ if (ctx.ws && ctx.connected) {
198
+ ctx.ws.send(JSON.stringify({ type: "fs_search", query: q }));
199
+ }
200
+ }, 200);
201
+ });
202
+ fbSearchInput.addEventListener("keydown", function (e) {
203
+ if (e.key === "Escape" && fbSearchInput.value) {
204
+ e.stopPropagation();
205
+ fbSearchInput.value = "";
206
+ renderTree();
207
+ restoreExpanded({});
208
+ }
209
+ });
210
+ }
182
211
  }
183
212
 
184
213
  // --- File watch helpers ---
@@ -232,10 +261,8 @@ export function resetFileBrowser() {
232
261
  // Hide the file browser panel, show sessions panel
233
262
  var filesPanel = document.getElementById("sidebar-panel-files");
234
263
  var sessionsPanel = document.getElementById("sidebar-panel-sessions");
235
- var filesHeaderContent = document.getElementById("files-header-content");
236
264
  var sessionsHeaderContent = document.getElementById("sessions-header-content");
237
265
  if (filesPanel) filesPanel.classList.add("hidden");
238
- if (filesHeaderContent) filesHeaderContent.classList.add("hidden");
239
266
  if (sessionsPanel) sessionsPanel.classList.remove("hidden");
240
267
  if (sessionsHeaderContent) sessionsHeaderContent.classList.remove("hidden");
241
268
  }
@@ -341,6 +368,150 @@ export function refreshIfOpen(filePath) {
341
368
 
342
369
  // --- WS handlers ---
343
370
 
371
+ export function handleFsSearch(msg) {
372
+ var entries = msg.entries || [];
373
+ var query = (msg.query || "").trim().toLowerCase();
374
+ if (!query) return;
375
+
376
+ ctx.fileTreeEl.innerHTML = "";
377
+
378
+ if (entries.length === 0) {
379
+ ctx.fileTreeEl.innerHTML = '<div class="fb-search-empty">No files found</div>';
380
+ return;
381
+ }
382
+
383
+ // Build a tree structure from flat search results
384
+ var tree = {};
385
+ for (var i = 0; i < entries.length; i++) {
386
+ var entry = entries[i];
387
+ var parts = entry.path.split("/");
388
+ var node = tree;
389
+ for (var j = 0; j < parts.length; j++) {
390
+ if (!node[parts[j]]) node[parts[j]] = {};
391
+ if (j === parts.length - 1) {
392
+ node[parts[j]]._entry = entry;
393
+ } else {
394
+ node = node[parts[j]];
395
+ }
396
+ }
397
+ }
398
+
399
+ renderFilteredTree(ctx.fileTreeEl, tree, 0, query);
400
+ refreshIcons();
401
+ }
402
+
403
+ function renderFilteredTree(container, tree, depth, query) {
404
+ var keys = Object.keys(tree);
405
+ var dirs = [];
406
+ var files = [];
407
+ for (var i = 0; i < keys.length; i++) {
408
+ if (keys[i] === "_entry") continue;
409
+ var node = tree[keys[i]];
410
+ var entry = node._entry;
411
+ if (entry && entry.type === "file") {
412
+ files.push(keys[i]);
413
+ } else {
414
+ dirs.push(keys[i]);
415
+ }
416
+ }
417
+ dirs.sort(function (a, b) {
418
+ var aH = a.charAt(0) === ".";
419
+ var bH = b.charAt(0) === ".";
420
+ if (aH !== bH) return aH ? 1 : -1;
421
+ return a.localeCompare(b);
422
+ });
423
+ files.sort(function (a, b) {
424
+ var aH = a.charAt(0) === ".";
425
+ var bH = b.charAt(0) === ".";
426
+ if (aH !== bH) return aH ? 1 : -1;
427
+ return a.localeCompare(b);
428
+ });
429
+
430
+ var allKeys = dirs.concat(files);
431
+ for (var k = 0; k < allKeys.length; k++) {
432
+ var name = allKeys[k];
433
+ var node = tree[name];
434
+ var entry = node._entry;
435
+ var isDir = !entry || entry.type === "dir";
436
+
437
+ var row = document.createElement("div");
438
+ row.className = "file-tree-item" + (isDir ? " expanded" : "");
439
+ row.style.paddingLeft = (8 + depth * 16) + "px";
440
+ if (entry) {
441
+ row.draggable = true;
442
+ row.dataset.path = entry.path;
443
+ row.addEventListener("dragstart", function (e) {
444
+ var cwd = ctx.cwd || "";
445
+ var rel = this.dataset.path;
446
+ var abs = cwd ? cwd.replace(/\/$/, "") + "/" + rel : rel;
447
+ e.dataTransfer.setData("text/plain", abs);
448
+ e.dataTransfer.effectAllowed = "copy";
449
+ });
450
+ }
451
+
452
+ var nameHtml = highlightMatch(name, query);
453
+
454
+ if (isDir) {
455
+ row.innerHTML =
456
+ '<span class="file-tree-chevron">' + iconHtml("chevron-right") + '</span>' +
457
+ '<span class="file-tree-icon file-tree-folder-icon"></span>' +
458
+ '<span class="file-tree-name">' + nameHtml + '</span>';
459
+
460
+ (function (iconEl, n) {
461
+ getFolderIconSvg(n, true, function (svg) { iconEl.innerHTML = svg; });
462
+ })(row.querySelector(".file-tree-folder-icon"), name);
463
+
464
+ var childContainer = document.createElement("div");
465
+ childContainer.className = "file-tree-children";
466
+
467
+ // Toggle expand/collapse on click
468
+ (function (rowEl, childEl, folderName) {
469
+ rowEl.addEventListener("click", function (e) {
470
+ e.stopPropagation();
471
+ var isExpanded = rowEl.classList.contains("expanded");
472
+ rowEl.classList.toggle("expanded");
473
+ childEl.classList.toggle("hidden", isExpanded);
474
+ var folderIconEl = rowEl.querySelector(".file-tree-folder-icon");
475
+ if (folderIconEl) {
476
+ getFolderIconSvg(folderName, !isExpanded, function (svg) { folderIconEl.innerHTML = svg; });
477
+ }
478
+ });
479
+ })(row, childContainer, name);
480
+
481
+ container.appendChild(row);
482
+ container.appendChild(childContainer);
483
+ renderFilteredTree(childContainer, node, depth + 1, query);
484
+ } else {
485
+ row.innerHTML =
486
+ '<span class="file-tree-spacer"></span>' +
487
+ '<span class="file-tree-icon">' + getFileIconSvg(name) + '</span>' +
488
+ '<span class="file-tree-name">' + nameHtml + '</span>';
489
+
490
+ (function (filePath, rowEl) {
491
+ rowEl.addEventListener("click", function (e) {
492
+ e.stopPropagation();
493
+ var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
494
+ if (prev) prev.classList.remove("active");
495
+ rowEl.classList.add("active");
496
+ requestFileContent(filePath);
497
+ if (window.innerWidth <= 768) closeSidebar();
498
+ });
499
+ })(entry.path, row);
500
+
501
+ container.appendChild(row);
502
+ }
503
+ }
504
+ }
505
+
506
+ function highlightMatch(text, query) {
507
+ var lower = text.toLowerCase();
508
+ var idx = lower.indexOf(query);
509
+ if (idx === -1) return escapeHtml(text);
510
+ return escapeHtml(text.substring(0, idx)) +
511
+ '<mark>' + escapeHtml(text.substring(idx, idx + query.length)) + '</mark>' +
512
+ escapeHtml(text.substring(idx + query.length));
513
+ }
514
+
344
515
  export function handleFsList(msg) {
345
516
  var dirPath = msg.path || ".";
346
517
  treeData[dirPath] = { loaded: true, children: msg.entries || [] };
@@ -1,6 +1,9 @@
1
1
  import { iconHtml, refreshIcons } from './icons.js';
2
2
  import { setRewindMode, isRewindMode } from './rewind.js';
3
+ import { renderPicker as renderContextPicker } from './context-sources.js';
3
4
  import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, stickyReapplyMention, sendMention, renderMentionUser, removeMentionChip } from './mention.js';
5
+ import { store } from './store.js';
6
+ import { mateAvatarUrl } from './avatar.js';
4
7
 
5
8
  var ctx;
6
9
 
@@ -217,8 +220,15 @@ export function sendMessage() {
217
220
  if (pastes.length > 0) {
218
221
  payload.pastes = pastes;
219
222
  }
223
+ // Include selected vendor for session binding (server uses on first message)
224
+ var _selVendor = store.get("currentVendor") || null;
225
+ if (_selVendor) payload.vendor = _selVendor;
220
226
  ctx.ws.send(JSON.stringify(payload));
221
227
 
228
+ // Hide vendor toggle after first message (vendor is locked to this session)
229
+ var _vtw2 = document.getElementById("vendor-toggle-wrap");
230
+ if (_vtw2) { _vtw2.classList.remove("hidden"); _vtw2.classList.add("locked"); }
231
+
222
232
  // Show pre-thinking dots before server responds
223
233
  if (ctx.isMateDm && ctx.isMateDm()) {
224
234
  ctx.showMatePreThinking();
@@ -648,6 +658,41 @@ export function initInput(_ctx) {
648
658
  });
649
659
  }
650
660
 
661
+ // Mobile "+" button -> unified bottom sheet with attach/image + context sources
662
+ var moreBtn = document.getElementById("input-more-btn");
663
+ var moreSheet = document.getElementById("input-more-sheet");
664
+ function openMoreSheet() {
665
+ if (!moreSheet) return;
666
+ // Render context sources into mobile sheet containers
667
+ try { renderContextPicker("-mobile"); } catch (e) {}
668
+ moreSheet.classList.remove("hidden");
669
+ requestAnimationFrame(function () { moreSheet.classList.add("open"); });
670
+ }
671
+ function closeMoreSheet() {
672
+ if (!moreSheet) return;
673
+ moreSheet.classList.remove("open");
674
+ setTimeout(function () { moreSheet.classList.add("hidden"); }, 250);
675
+ }
676
+ if (moreBtn && moreSheet) {
677
+ moreBtn.addEventListener("click", function (e) {
678
+ e.stopPropagation();
679
+ openMoreSheet();
680
+ });
681
+ var backdrop = moreSheet.querySelector(".input-more-backdrop");
682
+ if (backdrop) backdrop.addEventListener("click", closeMoreSheet);
683
+
684
+ var moreAttach = document.getElementById("input-more-attach");
685
+ if (moreAttach) moreAttach.addEventListener("click", function () {
686
+ closeMoreSheet();
687
+ createFileInput(null, null, true);
688
+ });
689
+ var moreImage = document.getElementById("input-more-image");
690
+ if (moreImage) moreImage.addEventListener("click", function () {
691
+ closeMoreSheet();
692
+ createFileInput("image/*", null, true);
693
+ });
694
+ }
695
+
651
696
  // Schedule button — inline expand with minute input
652
697
  var scheduleBtn = document.getElementById("schedule-btn");
653
698
  var scheduleInlineInput = null;
@@ -770,6 +815,69 @@ export function initInput(_ctx) {
770
815
  // Trigger the mention detection
771
816
  inputEl.dispatchEvent(new Event("input", { bubbles: true }));
772
817
  });
818
+
819
+ // Mate avatar overlay on @ button — rotate random mate faces
820
+ var _lastOverlayIdx = -1;
821
+ var _overlayInterval = null;
822
+
823
+ function getAvailableMates() {
824
+ var mates = store.get('cachedMatesList') || [];
825
+ return mates.filter(function (m) { return m.status !== 'interviewing'; });
826
+ }
827
+
828
+ function pickNextMate(available) {
829
+ if (available.length === 0) return null;
830
+ if (available.length === 1) return available[0];
831
+ var idx = Math.floor(Math.random() * available.length);
832
+ if (idx === _lastOverlayIdx) idx = (idx + 1) % available.length;
833
+ _lastOverlayIdx = idx;
834
+ return available[idx];
835
+ }
836
+
837
+ function setOverlayAvatar(mate) {
838
+ var img = askMateBtn.querySelector('.ask-mate-avatar');
839
+ if (!mate) {
840
+ if (img) img.remove();
841
+ return;
842
+ }
843
+ var url = mateAvatarUrl(mate, 20);
844
+ if (!img) {
845
+ img = document.createElement('img');
846
+ img.className = 'ask-mate-avatar fade-in';
847
+ img.width = 20;
848
+ img.height = 20;
849
+ img.alt = '';
850
+ img.src = url;
851
+ askMateBtn.appendChild(img);
852
+ return;
853
+ }
854
+ // Fade out, swap, fade in
855
+ img.classList.remove('fade-in');
856
+ img.classList.add('fade-out');
857
+ setTimeout(function () {
858
+ img.src = url;
859
+ img.classList.remove('fade-out');
860
+ img.classList.add('fade-in');
861
+ }, 300);
862
+ }
863
+
864
+ function rotateMateOverlay() {
865
+ var available = getAvailableMates();
866
+ setOverlayAvatar(pickNextMate(available));
867
+ }
868
+
869
+ function startOverlayRotation() {
870
+ if (_overlayInterval) clearInterval(_overlayInterval);
871
+ rotateMateOverlay();
872
+ _overlayInterval = setInterval(rotateMateOverlay, 10000);
873
+ }
874
+
875
+ // Update overlay when mate list changes
876
+ store.subscribe(function (state, prev) {
877
+ if (state.cachedMatesList !== prev.cachedMatesList) {
878
+ startOverlayRotation();
879
+ }
880
+ });
773
881
  }
774
882
 
775
883
  // Paste handler
@@ -930,6 +1038,12 @@ export function initInput(_ctx) {
930
1038
  return;
931
1039
  }
932
1040
  e.preventDefault();
1041
+ // If input is empty but ghost suggestion is showing, adopt it
1042
+ var ghost = ctx.getGhostSuggestion ? ctx.getGhostSuggestion() : "";
1043
+ if (!ctx.inputEl.value.trim() && ghost) {
1044
+ ctx.inputEl.value = ghost;
1045
+ if (ctx.hideSuggestionChips) ctx.hideSuggestionChips();
1046
+ }
933
1047
  sendMessage();
934
1048
  }
935
1049
  });
@@ -941,6 +1055,14 @@ export function initInput(_ctx) {
941
1055
 
942
1056
  // Send/Stop button — if sendable content exists, always send; otherwise stop
943
1057
  ctx.sendBtn.addEventListener("click", function () {
1058
+ // Adopt ghost suggestion if input is empty
1059
+ var ghost = ctx.getGhostSuggestion ? ctx.getGhostSuggestion() : "";
1060
+ if (!hasSendableContent() && ghost) {
1061
+ ctx.inputEl.value = ghost;
1062
+ if (ctx.hideSuggestionChips) ctx.hideSuggestionChips();
1063
+ sendMessage();
1064
+ return;
1065
+ }
944
1066
  if (hasSendableContent()) {
945
1067
  sendMessage();
946
1068
  return;
@@ -23,7 +23,11 @@ export function updateMermaidTheme(vars) {
23
23
  var mermaidIdCounter = 0;
24
24
 
25
25
  export function renderMarkdown(text) {
26
- return DOMPurify.sanitize(marked.parse(text));
26
+ // Normalize smart quotes so bold/italic delimiters flanking them parse correctly
27
+ var normalized = text
28
+ .replace(/[\u201C\u201D]/g, '"')
29
+ .replace(/[\u2018\u2019]/g, "'");
30
+ return DOMPurify.sanitize(marked.parse(normalized));
27
31
  }
28
32
 
29
33
  /**