clay-server 2.31.0 → 2.32.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/lib/browser-mcp-server.js +32 -44
  2. package/lib/debate-mcp-server.js +14 -31
  3. package/lib/mcp-local.js +31 -1
  4. package/lib/project-connection.js +4 -2
  5. package/lib/project-filesystem.js +47 -1
  6. package/lib/project-http.js +75 -8
  7. package/lib/project-mcp.js +4 -0
  8. package/lib/project-sessions.js +88 -51
  9. package/lib/project-user-message.js +12 -7
  10. package/lib/project.js +204 -90
  11. package/lib/public/app.js +123 -448
  12. package/lib/public/codex-avatar.png +0 -0
  13. package/lib/public/css/debate.css +3 -2
  14. package/lib/public/css/filebrowser.css +91 -1
  15. package/lib/public/css/icon-strip.css +21 -5
  16. package/lib/public/css/input.css +181 -100
  17. package/lib/public/css/mates.css +43 -0
  18. package/lib/public/css/mention.css +48 -4
  19. package/lib/public/css/menus.css +1 -1
  20. package/lib/public/css/messages.css +2 -0
  21. package/lib/public/css/notifications-center.css +19 -0
  22. package/lib/public/index.html +46 -24
  23. package/lib/public/modules/app-connection.js +138 -37
  24. package/lib/public/modules/app-cursors.js +18 -17
  25. package/lib/public/modules/app-debate-ui.js +9 -9
  26. package/lib/public/modules/app-dm.js +170 -131
  27. package/lib/public/modules/app-favicon.js +28 -26
  28. package/lib/public/modules/app-header.js +79 -68
  29. package/lib/public/modules/app-home-hub.js +55 -47
  30. package/lib/public/modules/app-loop-ui.js +34 -18
  31. package/lib/public/modules/app-loop-wizard.js +6 -6
  32. package/lib/public/modules/app-messages.js +195 -152
  33. package/lib/public/modules/app-misc.js +23 -12
  34. package/lib/public/modules/app-notifications.js +91 -3
  35. package/lib/public/modules/app-panels.js +203 -49
  36. package/lib/public/modules/app-projects.js +159 -150
  37. package/lib/public/modules/app-rate-limit.js +5 -4
  38. package/lib/public/modules/app-rendering.js +149 -101
  39. package/lib/public/modules/app-skills-install.js +4 -4
  40. package/lib/public/modules/context-sources.js +12 -41
  41. package/lib/public/modules/dom-refs.js +21 -0
  42. package/lib/public/modules/filebrowser.js +173 -2
  43. package/lib/public/modules/input.js +86 -0
  44. package/lib/public/modules/mate-sidebar.js +38 -0
  45. package/lib/public/modules/mention.js +24 -6
  46. package/lib/public/modules/scheduler.js +1 -1
  47. package/lib/public/modules/sidebar-mates.js +66 -34
  48. package/lib/public/modules/sidebar-mobile.js +34 -30
  49. package/lib/public/modules/sidebar-projects.js +60 -57
  50. package/lib/public/modules/sidebar-sessions.js +75 -69
  51. package/lib/public/modules/sidebar.js +12 -20
  52. package/lib/public/modules/skills.js +8 -9
  53. package/lib/public/modules/sticky-notes.js +1 -2
  54. package/lib/public/modules/store.js +9 -2
  55. package/lib/public/modules/stt.js +4 -1
  56. package/lib/public/modules/tools.js +14 -9
  57. package/lib/sdk-bridge.js +511 -1113
  58. package/lib/sdk-message-processor.js +123 -134
  59. package/lib/sdk-worker.js +4 -0
  60. package/lib/server-dm.js +1 -0
  61. package/lib/server.js +86 -1
  62. package/lib/sessions.js +47 -36
  63. package/lib/ws-schema.js +2 -0
  64. package/lib/yoke/adapters/claude-worker.js +559 -0
  65. package/lib/yoke/adapters/claude.js +1418 -0
  66. package/lib/yoke/adapters/codex.js +968 -0
  67. package/lib/yoke/adapters/gemini.js +668 -0
  68. package/lib/yoke/codex-app-server.js +307 -0
  69. package/lib/yoke/index.js +199 -0
  70. package/lib/yoke/instructions.js +62 -0
  71. package/lib/yoke/interface.js +92 -0
  72. package/lib/yoke/mcp-bridge-server.js +294 -0
  73. package/lib/yoke/package.json +7 -0
  74. package/package.json +3 -1
@@ -2,8 +2,31 @@
2
2
  // Extracted from app.js (PR-28)
3
3
 
4
4
  import { store } from './store.js';
5
-
6
- var _ctx = null;
5
+ import { getWs } from './ws-ref.js';
6
+ import { getMessagesEl, getInputEl, getSendBtn } from './dom-refs.js';
7
+ import { escapeHtml, copyToClipboard } from './utils.js';
8
+ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
9
+ import { iconHtml, refreshIcons } from './icons.js';
10
+ import { openTerminal } from './terminal.js';
11
+ import { userAvatarUrl } from './avatar.js';
12
+ import { closeToolGroup } from './tools.js';
13
+ import { showImageModal, showPasteModal } from './app-misc.js';
14
+ import { sendMessage } from './input.js';
15
+ import { getChatLayout } from './theme.js';
16
+ import { getScheduledMsgEl } from './app-rate-limit.js';
17
+
18
+ export var VENDOR_AVATARS = {
19
+ claude: "/claude-code-avatar.png",
20
+ codex: "/codex-avatar.png",
21
+ gemini: "/claude-code-avatar.png",
22
+ };
23
+ export var VENDOR_NAMES = {
24
+ claude: "Claude Code",
25
+ codex: "Codex",
26
+ gemini: "Gemini",
27
+ };
28
+ var NEW_MSG_BTN_DEFAULT = "\u2193 Latest";
29
+ var NEW_MSG_BTN_ACTIVITY = "\u2193 New activity";
7
30
 
8
31
  // --- Module-owned state (not in store) ---
9
32
  var turnCounter = 0;
@@ -16,8 +39,16 @@ var streamDrainTimer = null;
16
39
  var isUserScrolledUp = false;
17
40
  var scrollThreshold = 150;
18
41
 
19
- export function initRendering(ctx) {
20
- _ctx = ctx;
42
+ export function initRendering() {
43
+ // Update input placeholder when vendor changes
44
+ store.subscribe(function (state, prev) {
45
+ if (state.currentVendor !== prev.currentVendor) {
46
+ var inputEl = document.getElementById("input");
47
+ if (inputEl) {
48
+ inputEl.placeholder = "Message " + (VENDOR_NAMES[state.currentVendor] || VENDOR_NAMES.claude) + "...";
49
+ }
50
+ }
51
+ });
21
52
  }
22
53
 
23
54
  // --- State accessors (module-local, not in store) ---
@@ -33,10 +64,10 @@ export function setIsUserScrolledUp(v) { isUserScrolledUp = v; }
33
64
  // --- Rendering functions ---
34
65
 
35
66
  export function addToMessages(el) {
36
- var messagesEl = _ctx.messagesEl;
67
+ var messagesEl = getMessagesEl();
37
68
  if (prependAnchor) messagesEl.insertBefore(el, prependAnchor);
38
69
  else messagesEl.appendChild(el);
39
- var _sme = _ctx.getScheduledMsgEl();
70
+ var _sme = getScheduledMsgEl();
40
71
  if (_sme && el !== _sme && _sme.parentNode === messagesEl) {
41
72
  messagesEl.appendChild(_sme);
42
73
  }
@@ -44,12 +75,13 @@ export function addToMessages(el) {
44
75
 
45
76
  export function scrollToBottom() {
46
77
  if (prependAnchor) return;
78
+ var newMsgBtn = document.getElementById("new-msg-btn");
47
79
  if (isUserScrolledUp) {
48
- _ctx.newMsgBtn.textContent = _ctx.newMsgBtnActivity;
49
- _ctx.newMsgBtn.classList.remove("hidden");
80
+ newMsgBtn.textContent = NEW_MSG_BTN_ACTIVITY;
81
+ newMsgBtn.classList.remove("hidden");
50
82
  return;
51
83
  }
52
- var messagesEl = _ctx.messagesEl;
84
+ var messagesEl = getMessagesEl();
53
85
  requestAnimationFrame(function () {
54
86
  messagesEl.scrollTop = messagesEl.scrollHeight;
55
87
  });
@@ -58,16 +90,17 @@ export function scrollToBottom() {
58
90
  export function forceScrollToBottom() {
59
91
  if (prependAnchor) return;
60
92
  isUserScrolledUp = false;
61
- _ctx.newMsgBtn.classList.add("hidden");
62
- _ctx.newMsgBtn.textContent = _ctx.newMsgBtnDefault;
63
- var messagesEl = _ctx.messagesEl;
93
+ var newMsgBtn = document.getElementById("new-msg-btn");
94
+ newMsgBtn.classList.add("hidden");
95
+ newMsgBtn.textContent = NEW_MSG_BTN_DEFAULT;
96
+ var messagesEl = getMessagesEl();
64
97
  requestAnimationFrame(function () {
65
98
  messagesEl.scrollTop = messagesEl.scrollHeight;
66
99
  });
67
100
  }
68
101
 
69
102
  export function getMsgTime() {
70
- var _ts = store.getState().currentMsgTs;
103
+ var _ts = store.get('currentMsgTs');
71
104
  var d = _ts ? new Date(_ts) : new Date();
72
105
  var time = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
73
106
  var now = new Date();
@@ -78,9 +111,9 @@ export function getMsgTime() {
78
111
  }
79
112
 
80
113
  export function shouldGroupMessage(senderClass) {
81
- var _s = store.getState();
114
+ var _s = store.snap();
82
115
  if (_s.replayingHistory && !_s.currentMsgTs) return false;
83
- var prev = _ctx.messagesEl.lastElementChild;
116
+ var prev = getMessagesEl().lastElementChild;
84
117
  if (!prev || !prev.classList.contains(senderClass)) return false;
85
118
  var prevTime = prev.querySelector(".dm-bubble-time");
86
119
  if (!prevTime) return false;
@@ -88,7 +121,7 @@ export function shouldGroupMessage(senderClass) {
88
121
  }
89
122
 
90
123
  export function ensureAssistantBlock() {
91
- var _el = store.getState().currentMsgEl;
124
+ var _el = store.get('currentMsgEl');
92
125
  if (!_el) {
93
126
  _el = document.createElement("div");
94
127
  _el.className = "msg-assistant";
@@ -98,9 +131,10 @@ export function ensureAssistantBlock() {
98
131
  if (grouped) _el.classList.add("grouped");
99
132
 
100
133
  var _isDm2 = document.body.classList.contains("mate-dm-active") && document.body.dataset.mateAvatarUrl;
134
+ var vendor = store.get('currentVendor') || "claude";
101
135
  var avi = document.createElement("img");
102
136
  avi.className = "dm-bubble-avatar dm-bubble-avatar-mate";
103
- avi.src = _isDm2 ? document.body.dataset.mateAvatarUrl : _ctx.CLAUDE_CODE_AVATAR;
137
+ avi.src = _isDm2 ? document.body.dataset.mateAvatarUrl : (VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude);
104
138
  _el.appendChild(avi);
105
139
 
106
140
  var contentWrap = document.createElement("div");
@@ -110,7 +144,8 @@ export function ensureAssistantBlock() {
110
144
  header.className = "dm-bubble-header";
111
145
  var nameSpan = document.createElement("span");
112
146
  nameSpan.className = "dm-bubble-name";
113
- nameSpan.textContent = _isDm2 ? ((_ctx.getDmTargetUser() && _ctx.getDmTargetUser().displayName) || "Mate") : "Claude Code";
147
+ var dmTarget = store.get('dmTargetUser');
148
+ nameSpan.textContent = _isDm2 ? ((dmTarget && dmTarget.displayName) || "Mate") : (VENDOR_NAMES[vendor] || VENDOR_NAMES.claude);
114
149
  header.appendChild(nameSpan);
115
150
  var timeSpan = document.createElement("span");
116
151
  timeSpan.className = "dm-bubble-time";
@@ -124,7 +159,7 @@ export function ensureAssistantBlock() {
124
159
  contentWrap.appendChild(mdDiv);
125
160
  _el.appendChild(contentWrap);
126
161
  addToMessages(_el);
127
- store.setState({ currentMsgEl: _el, currentFullText: "" });
162
+ store.set({ currentMsgEl: _el, currentFullText: "" });
128
163
  }
129
164
  return _el;
130
165
  }
@@ -159,7 +194,7 @@ export function addCopyHandler(msgEl, rawText) {
159
194
  resetTimer = setTimeout(reset, 3000);
160
195
  } else {
161
196
  clearTimeout(resetTimer);
162
- _ctx.copyToClipboard(rawText).then(function () {
197
+ copyToClipboard(rawText).then(function () {
163
198
  msgEl.classList.remove("copy-primed");
164
199
  msgEl.classList.add("copy-done");
165
200
  hint.textContent = "Grabbed!";
@@ -183,7 +218,7 @@ export function appendDelta(text) {
183
218
 
184
219
  function drainStreamTick() {
185
220
  streamDrainTimer = null;
186
- var _s = store.getState();
221
+ var _s = store.snap();
187
222
  if (!_s.currentMsgEl || streamBuffer.length === 0) return;
188
223
 
189
224
  var n;
@@ -197,14 +232,14 @@ function drainStreamTick() {
197
232
  var chunk = streamBuffer.slice(0, n);
198
233
  streamBuffer = streamBuffer.slice(n);
199
234
  var newText = _s.currentFullText + chunk;
200
- store.setState({ currentFullText: newText });
235
+ store.set({ currentFullText: newText });
201
236
 
202
237
  var contentEl = _s.currentMsgEl.querySelector(".md-content");
203
- contentEl.innerHTML = _ctx.renderMarkdown(newText);
238
+ contentEl.innerHTML = renderMarkdown(newText);
204
239
 
205
240
  if (highlightTimer) clearTimeout(highlightTimer);
206
241
  highlightTimer = setTimeout(function () {
207
- _ctx.highlightCodeBlocks(contentEl);
242
+ highlightCodeBlocks(contentEl);
208
243
  }, 150);
209
244
 
210
245
  scrollToBottom();
@@ -217,39 +252,40 @@ function drainStreamTick() {
217
252
  export function flushStreamBuffer() {
218
253
  if (streamDrainTimer) { cancelAnimationFrame(streamDrainTimer); streamDrainTimer = null; }
219
254
  if (streamBuffer.length > 0) {
220
- store.setState({ currentFullText: store.getState().currentFullText + streamBuffer });
255
+ store.set({ currentFullText: store.get('currentFullText') + streamBuffer });
221
256
  streamBuffer = "";
222
257
  }
223
- var _s = store.getState();
258
+ var _s = store.snap();
224
259
  if (_s.currentMsgEl) {
225
260
  var contentEl = _s.currentMsgEl.querySelector(".md-content");
226
261
  if (contentEl) {
227
- contentEl.innerHTML = _ctx.renderMarkdown(_s.currentFullText);
228
- _ctx.highlightCodeBlocks(contentEl);
262
+ contentEl.innerHTML = renderMarkdown(_s.currentFullText);
263
+ highlightCodeBlocks(contentEl);
229
264
  }
230
265
  }
231
266
  }
232
267
 
233
268
  export function finalizeAssistantBlock() {
234
269
  flushStreamBuffer();
235
- var _s = store.getState();
270
+ var _s = store.snap();
236
271
  if (_s.currentMsgEl) {
237
272
  var contentEl = _s.currentMsgEl.querySelector(".md-content");
238
273
  if (contentEl) {
239
- _ctx.highlightCodeBlocks(contentEl);
240
- _ctx.renderMermaidBlocks(contentEl);
274
+ highlightCodeBlocks(contentEl);
275
+ renderMermaidBlocks(contentEl);
241
276
  }
242
277
  if (_s.currentFullText) {
243
278
  addCopyHandler(_s.currentMsgEl, _s.currentFullText);
244
279
  }
245
- _ctx.closeToolGroup();
280
+ closeToolGroup();
246
281
  }
247
- store.setState({ currentMsgEl: null, currentFullText: "" });
282
+ store.set({ currentMsgEl: null, currentFullText: "" });
248
283
  }
249
284
 
250
285
  export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
251
286
  if (!text && (!images || images.length === 0) && (!pastes || pastes.length === 0)) return;
252
- var isOtherUser = fromUserId && fromUserId !== _ctx.myUserId;
287
+ var myUserId = store.get('myUserId');
288
+ var isOtherUser = fromUserId && fromUserId !== myUserId;
253
289
  var div = document.createElement("div");
254
290
  div.className = "msg-user" + (isOtherUser ? " msg-user-other" : "");
255
291
  div.dataset.turn = ++turnCounter;
@@ -270,7 +306,7 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
270
306
  }
271
307
  img.loading = "lazy";
272
308
  img.className = "bubble-img";
273
- img.addEventListener("click", function () { _ctx.showImageModal(this.src); });
309
+ img.addEventListener("click", function () { showImageModal(this.src); });
274
310
  img.addEventListener("error", function () {
275
311
  var placeholder = document.createElement("div");
276
312
  placeholder.className = "bubble-img-expired";
@@ -291,10 +327,10 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
291
327
  chip.className = "bubble-paste";
292
328
  var preview = pasteText.substring(0, 60).replace(/\n/g, " ");
293
329
  if (pasteText.length > 60) preview += "...";
294
- chip.innerHTML = '<span class="bubble-paste-preview">' + _ctx.escapeHtml(preview) + '</span><span class="bubble-paste-label">PASTED</span>';
330
+ chip.innerHTML = '<span class="bubble-paste-preview">' + escapeHtml(preview) + '</span><span class="bubble-paste-label">PASTED</span>';
295
331
  chip.addEventListener("click", function (e) {
296
332
  e.stopPropagation();
297
- _ctx.showPasteModal(pasteText);
333
+ showPasteModal(pasteText);
298
334
  });
299
335
  pasteRow.appendChild(chip);
300
336
  })(pastes[p]);
@@ -309,13 +345,14 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
309
345
  }
310
346
 
311
347
 
348
+ var cachedAllUsers = store.get('cachedAllUsers');
312
349
  var _targetUser;
313
350
  var _displayName;
314
351
  if (isOtherUser) {
315
- _targetUser = _ctx.cachedAllUsers.find(function (u) { return u.id === fromUserId; });
352
+ _targetUser = cachedAllUsers.find(function (u) { return u.id === fromUserId; });
316
353
  _displayName = fromUserName || (_targetUser && (_targetUser.displayName || _targetUser.username)) || "User";
317
354
  } else {
318
- _targetUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
355
+ _targetUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
319
356
  if (!_targetUser) {
320
357
  try { _targetUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {}
321
358
  }
@@ -328,8 +365,8 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
328
365
  var avi = document.createElement("img");
329
366
  avi.className = "dm-bubble-avatar" + (isOtherUser ? " dm-bubble-avatar-other" : " dm-bubble-avatar-me");
330
367
  avi.src = isOtherUser
331
- ? _ctx.userAvatarUrl(_targetUser || { id: fromUserId }, 36)
332
- : (document.body.dataset.myAvatarUrl || _ctx.userAvatarUrl(_targetUser || { id: _ctx.myUserId }, 36));
368
+ ? userAvatarUrl(_targetUser || { id: fromUserId }, 36)
369
+ : (document.body.dataset.myAvatarUrl || userAvatarUrl(_targetUser || { id: myUserId }, 36));
333
370
  div.appendChild(avi);
334
371
 
335
372
  var contentWrap = document.createElement("div");
@@ -353,22 +390,22 @@ export function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
353
390
  actions.className = "msg-actions";
354
391
  actions.innerHTML =
355
392
  '<span class="msg-action-time">' + getMsgTime() + '</span>' +
356
- '<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + _ctx.iconHtml("copy") + '</button>' +
357
- '<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + _ctx.iconHtml("git-branch") + '</button>' +
358
- '<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + _ctx.iconHtml("rotate-ccw") + '</button>' +
359
- '<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + _ctx.iconHtml("pencil") + '</button>';
393
+ '<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
394
+ '<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
395
+ (((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>' : '') +
396
+ '<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + iconHtml("pencil") + '</button>';
360
397
  div.appendChild(actions);
361
398
 
362
399
  actions.querySelector(".msg-action-copy").addEventListener("click", function () {
363
400
  var self = this;
364
- _ctx.copyToClipboard(text || "");
365
- self.innerHTML = _ctx.iconHtml("check");
366
- _ctx.refreshIcons();
367
- setTimeout(function () { self.innerHTML = _ctx.iconHtml("copy"); _ctx.refreshIcons(); }, 1200);
401
+ copyToClipboard(text || "");
402
+ self.innerHTML = iconHtml("check");
403
+ refreshIcons();
404
+ setTimeout(function () { self.innerHTML = iconHtml("copy"); refreshIcons(); }, 1200);
368
405
  });
369
406
 
370
407
  addToMessages(div);
371
- _ctx.refreshIcons();
408
+ refreshIcons();
372
409
  forceScrollToBottom();
373
410
  }
374
411
 
@@ -416,7 +453,7 @@ export function addConflictMessage(msg) {
416
453
  killBtn.setAttribute("data-pid", p.pid);
417
454
  killBtn.addEventListener("click", function() {
418
455
  var pid = parseInt(this.getAttribute("data-pid"), 10);
419
- _ctx.getWs().send(JSON.stringify({ type: "kill_process", pid: pid }));
456
+ getWs().send(JSON.stringify({ type: "kill_process", pid: pid }));
420
457
  this.disabled = true;
421
458
  this.textContent = "Killing...";
422
459
  });
@@ -446,7 +483,7 @@ export function addContextOverflowMessage(msg) {
446
483
  btn.className = "context-overflow-btn";
447
484
  btn.textContent = "New Conversation";
448
485
  btn.addEventListener("click", function() {
449
- _ctx.getWs().send(JSON.stringify({ type: "new_session" }));
486
+ getWs().send(JSON.stringify({ type: "new_session" }));
450
487
  });
451
488
  div.appendChild(btn);
452
489
 
@@ -458,9 +495,13 @@ export function addAuthRequiredMessage(msg) {
458
495
  var div = document.createElement("div");
459
496
  div.className = "auth-required-msg";
460
497
 
498
+ var vendor = msg.vendor || "claude";
499
+ var loginCmd = msg.loginCommand || (vendor === "codex" ? "codex --login" : "claude login");
500
+ var vendorName = msg.text || "Claude Code is not logged in.";
501
+
461
502
  var header = document.createElement("div");
462
503
  header.className = "auth-required-header";
463
- header.textContent = msg.text || "Claude Code is not logged in.";
504
+ header.textContent = vendorName;
464
505
  div.appendChild(header);
465
506
 
466
507
  var hint = document.createElement("div");
@@ -476,7 +517,11 @@ export function addAuthRequiredMessage(msg) {
476
517
 
477
518
  var guide = document.createElement("div");
478
519
  guide.className = "auth-required-guide";
479
- guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
520
+ if (vendor === "codex") {
521
+ guide.textContent = "Run the login command in the terminal and follow the instructions to authenticate.";
522
+ } else {
523
+ guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
524
+ }
480
525
  div.appendChild(guide);
481
526
 
482
527
  var sessionHint = document.createElement("div");
@@ -484,13 +529,14 @@ export function addAuthRequiredMessage(msg) {
484
529
  sessionHint.textContent = "After logging in, start a new session to continue.";
485
530
  div.appendChild(sessionHint);
486
531
 
532
+ var termCommand = loginCmd + "\n";
487
533
  var loginBtn = document.createElement("button");
488
534
  loginBtn.className = "auth-required-btn";
489
535
  loginBtn.textContent = "Open terminal & log in";
490
536
  loginBtn.addEventListener("click", function () {
491
- _ctx.setPendingTermCommand("claude\n");
492
- _ctx.getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
493
- _ctx.openTerminal();
537
+ store.set({ pendingTermCommand: termCommand });
538
+ getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
539
+ openTerminal();
494
540
  });
495
541
  div.appendChild(loginBtn);
496
542
 
@@ -500,34 +546,39 @@ export function addAuthRequiredMessage(msg) {
500
546
  var inputArea = document.getElementById("input-area");
501
547
  if (inputArea) inputArea.classList.add("hidden");
502
548
 
503
- if (!store.getState().replayingHistory) {
504
- _ctx.setPendingTermCommand("claude\n");
505
- _ctx.getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
506
- _ctx.openTerminal();
549
+ if (!store.get('replayingHistory')) {
550
+ store.set({ pendingTermCommand: termCommand });
551
+ getWs().send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
552
+ openTerminal();
507
553
  }
508
554
  } else {
509
- hint.textContent = "Please ask an administrator to log in to Claude Code.";
555
+ var adminVendorName = vendor.charAt(0).toUpperCase() + vendor.slice(1);
556
+ hint.textContent = "Please ask an administrator to log in to " + adminVendorName + ".";
510
557
  div.appendChild(hint);
511
558
  addToMessages(div);
512
559
  scrollToBottom();
513
560
 
514
- _ctx.inputEl.disabled = true;
515
- _ctx.inputEl.placeholder = "Login required. Start a new session after logging in.";
516
- _ctx.sendBtn.disabled = true;
561
+ var inputEl = getInputEl();
562
+ inputEl.disabled = true;
563
+ inputEl.placeholder = "Login required. Start a new session after logging in.";
564
+ getSendBtn().disabled = true;
517
565
  }
518
566
  }
519
567
 
520
568
  // --- Pre-thinking (instant dots before server responds) ---
521
569
 
522
570
  export function showClaudePreThinking() {
523
- if (_ctx.getChatLayout() !== "channel") return;
524
- var claudeAvatar = _ctx.CLAUDE_CODE_AVATAR;
525
- doShowMatePreThinking("Claude Code", claudeAvatar);
571
+ if (getChatLayout() !== "channel") return;
572
+ var vendor = store.get('currentVendor') || "claude";
573
+ var vendorAvatar = VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude;
574
+ var vendorName = VENDOR_NAMES[vendor] || VENDOR_NAMES.claude;
575
+ doShowMatePreThinking(vendorName, vendorAvatar);
526
576
  }
527
577
 
528
578
  export function showMatePreThinking() {
529
579
  removeMatePreThinking();
530
- var mateName = _ctx.getDmTargetUser() ? (_ctx.getDmTargetUser().displayName || "Mate") : "Mate";
580
+ var dmTarget = store.get('dmTargetUser');
581
+ var mateName = dmTarget ? (dmTarget.displayName || "Mate") : "Mate";
531
582
  var mateAvatar = document.body.dataset.mateAvatarUrl || "";
532
583
  doShowMatePreThinking(mateName, mateAvatar);
533
584
  }
@@ -536,18 +587,18 @@ function doShowMatePreThinking(mateName, mateAvatar) {
536
587
  var _el = document.createElement("div");
537
588
  _el.className = "thinking-item mate-thinking mate-pre-thinking";
538
589
  _el.innerHTML =
539
- '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + _ctx.escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
590
+ '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
540
591
  '<div class="dm-bubble-content">' +
541
- '<div class="dm-bubble-header"><span class="dm-bubble-name">' + _ctx.escapeHtml(mateName) + '</span></div>' +
592
+ '<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
542
593
  '<div class="mate-thinking-dots"><span></span><span></span><span></span></div>' +
543
594
  '</div>';
544
- store.setState({ matePreThinkingEl: _el });
595
+ store.set({ matePreThinkingEl: _el });
545
596
  if (activityEl && activityEl.parentNode) {
546
597
  activityEl.parentNode.insertBefore(_el, activityEl);
547
598
  } else {
548
599
  addToMessages(_el);
549
600
  }
550
- _ctx.refreshIcons();
601
+ refreshIcons();
551
602
  scrollToBottom();
552
603
  }
553
604
 
@@ -556,42 +607,39 @@ export function removeMatePreThinking() {
556
607
  clearTimeout(matePreThinkingTimer);
557
608
  matePreThinkingTimer = null;
558
609
  }
559
- var _el = store.getState().matePreThinkingEl;
610
+ var _el = store.get('matePreThinkingEl');
560
611
  if (_el) {
561
612
  _el.remove();
562
- store.setState({ matePreThinkingEl: null });
613
+ store.set({ matePreThinkingEl: null });
563
614
  }
564
615
  }
565
616
 
566
- // --- Prompt suggestion chips ---
617
+ // --- Ghost suggestion (prompt recommendation as ghost text) ---
618
+
619
+ var _ghostSuggestionText = "";
620
+
621
+ export function getGhostSuggestion() {
622
+ return _ghostSuggestionText;
623
+ }
567
624
 
568
625
  export function showSuggestionChips(suggestion) {
569
- if (!suggestion || _ctx.getProcessing()) return;
570
- _ctx.suggestionChipsEl.innerHTML = "";
571
- var chip = document.createElement("button");
572
- chip.className = "suggestion-chip";
573
- chip.innerHTML = _ctx.iconHtml("sparkles") +
574
- '<span class="suggestion-chip-text">' + _ctx.escapeHtml(suggestion) + '</span>';
575
- chip.addEventListener("click", function () {
576
- _ctx.inputEl.value = suggestion;
577
- hideSuggestionChips();
578
- _ctx.sendMessage();
579
- });
580
- _ctx.suggestionChipsEl.appendChild(chip);
581
- _ctx.suggestionChipsEl.classList.remove("hidden");
582
- _ctx.refreshIcons();
583
- var messagesEl = _ctx.messagesEl;
584
- requestAnimationFrame(function () {
585
- var chipHeight = _ctx.suggestionChipsEl.offsetHeight || 0;
586
- if (chipHeight > 0) {
587
- messagesEl.style.paddingBottom = chipHeight + "px";
588
- scrollToBottom();
589
- }
590
- });
626
+ if (!suggestion || store.get('processing')) return;
627
+ var inputEl = getInputEl();
628
+ // Only show ghost text if input is empty
629
+ if (inputEl && inputEl.value.trim()) return;
630
+ _ghostSuggestionText = suggestion;
631
+ var ghostEl = document.getElementById("ghost-suggestion");
632
+ if (!ghostEl) return;
633
+ ghostEl.innerHTML = escapeHtml(suggestion) +
634
+ ' <span class="ghost-hint"><kbd>Enter</kbd> to send</span>';
635
+ ghostEl.classList.remove("hidden");
591
636
  }
592
637
 
593
638
  export function hideSuggestionChips() {
594
- _ctx.suggestionChipsEl.innerHTML = "";
595
- _ctx.suggestionChipsEl.classList.add("hidden");
596
- _ctx.messagesEl.style.paddingBottom = "";
639
+ _ghostSuggestionText = "";
640
+ var ghostEl = document.getElementById("ghost-suggestion");
641
+ if (ghostEl) {
642
+ ghostEl.innerHTML = "";
643
+ ghostEl.classList.add("hidden");
644
+ }
597
645
  }
@@ -54,7 +54,7 @@ export function initSkillInstall() {
54
54
  for (var j = 0; j < pendingSkillInstalls.length; j++) {
55
55
  var s = pendingSkillInstalls[j];
56
56
  if (s.installed) continue;
57
- fetch(store.getState().basePath + "api/install-skill", {
57
+ fetch(store.get('basePath') + "api/install-skill", {
58
58
  method: "POST",
59
59
  headers: { "Content-Type": "application/json" },
60
60
  body: JSON.stringify({ url: s.url, skill: s.name, scope: s.scope || "global" }),
@@ -151,9 +151,9 @@ export function handleSkillInstallWs(msg) {
151
151
  if (pendingSkillInstalls[i].name === msg.skill) {
152
152
  if (msg.success) {
153
153
  pendingSkillInstalls[i].installed = true;
154
- var _kis = Object.assign({}, store.getState().knownInstalledSkills);
154
+ var _kis = Object.assign({}, store.get('knownInstalledSkills'));
155
155
  _kis[msg.skill] = true;
156
- store.setState({ knownInstalledSkills: _kis });
156
+ store.set({ knownInstalledSkills: _kis });
157
157
  } else {
158
158
  skillInstalling = false;
159
159
  skillInstallOk.disabled = false;
@@ -189,7 +189,7 @@ export function handleSkillInstallWs(msg) {
189
189
  }
190
190
 
191
191
  export function requireSkills(opts, cb) {
192
- fetch(store.getState().basePath + "api/check-skill-updates", {
192
+ fetch(store.get('basePath') + "api/check-skill-updates", {
193
193
  method: "POST",
194
194
  headers: { "Content-Type": "application/json" },
195
195
  body: JSON.stringify({ skills: opts.skills }),
@@ -181,51 +181,22 @@ function removeSource(sourceId) {
181
181
  }
182
182
 
183
183
  function renderChips() {
184
- var container = document.getElementById("context-sources-chips");
185
- container.innerHTML = "";
186
-
187
- 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);
215
- }
216
-
217
- // Update add button label
184
+ // Update add button — show badge count when sources are active
218
185
  var addBtn = document.getElementById("context-sources-add");
219
- var labelSpan = addBtn.querySelector("span");
186
+ var labelSpan = addBtn.querySelector(".ctx-label");
187
+ var existingBadge = addBtn.querySelector(".ctx-badge");
220
188
  if (activeSourceIds.size > 0) {
221
- labelSpan.textContent = "";
222
- labelSpan.style.display = "none";
189
+ if (labelSpan) labelSpan.style.display = "none";
190
+ if (!existingBadge) {
191
+ existingBadge = document.createElement("span");
192
+ existingBadge.className = "ctx-badge";
193
+ addBtn.appendChild(existingBadge);
194
+ }
195
+ existingBadge.textContent = activeSourceIds.size;
223
196
  } else {
224
- labelSpan.textContent = "Context Sources";
225
- labelSpan.style.display = "";
197
+ if (labelSpan) { labelSpan.style.display = ""; }
198
+ if (existingBadge) existingBadge.remove();
226
199
  }
227
-
228
- if (typeof lucide !== "undefined") lucide.createIcons();
229
200
  }
230
201
 
231
202
  function renderPicker() {
@@ -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
+ }