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
@@ -13,6 +13,9 @@ var _extRequestCallbacks = {};
13
13
 
14
14
  // Queue for extension messages that arrived before WS was ready
15
15
  var _pendingExtMessages = [];
16
+ // Cache last extension state so we can resend on WS reconnect (server restart)
17
+ var _lastTabListMsg = null;
18
+ var _lastMcpServersMsg = null;
16
19
 
17
20
  function sendOrQueue(msgObj) {
18
21
  var ws = getWs();
@@ -24,14 +27,20 @@ function sendOrQueue(msgObj) {
24
27
  }
25
28
 
26
29
  export function flushPendingExtMessages() {
27
- if (_pendingExtMessages.length === 0) return;
28
30
  var ws = getWs();
29
31
  if (!ws || ws.readyState !== 1) return;
30
- var queued = _pendingExtMessages.slice();
31
- _pendingExtMessages = [];
32
- for (var i = 0; i < queued.length; i++) {
33
- ws.send(JSON.stringify(queued[i]));
32
+ // Flush queued messages from before WS was ready
33
+ if (_pendingExtMessages.length > 0) {
34
+ var queued = _pendingExtMessages.slice();
35
+ _pendingExtMessages = [];
36
+ for (var i = 0; i < queued.length; i++) {
37
+ ws.send(JSON.stringify(queued[i]));
38
+ }
34
39
  }
40
+ // Resend cached extension state on every WS reconnect so the server
41
+ // re-registers _extensionWs and rebuilds MCP proxy servers
42
+ if (_lastTabListMsg) ws.send(JSON.stringify(_lastTabListMsg));
43
+ if (_lastMcpServersMsg) ws.send(JSON.stringify(_lastMcpServersMsg));
35
44
  }
36
45
 
37
46
  export function initMisc() {
@@ -121,11 +130,9 @@ export function initMisc() {
121
130
  if (msg.type === "clay_ext_tab_list") {
122
131
  setExtensionConnected(true);
123
132
  updateBrowserTabList(msg.tabs);
124
- // Also inform server about tab list (queue if WS not ready yet)
125
- sendOrQueue({
126
- type: "browser_tab_list",
127
- tabs: msg.tabs
128
- });
133
+ // Cache and send (or queue) - resent on WS reconnect via flushPendingExtMessages
134
+ _lastTabListMsg = { type: "browser_tab_list", tabs: msg.tabs };
135
+ sendOrQueue(_lastTabListMsg);
129
136
  }
130
137
  if (msg.type === "clay_ext_result") {
131
138
  handleExtensionResult(msg.requestId, msg.result);
@@ -135,12 +142,14 @@ export function initMisc() {
135
142
  }
136
143
 
137
144
  // MCP bridge: extension reports available MCP servers (queue if WS not ready yet)
145
+ // Cache for resend on WS reconnect (server restart loses _availableServers)
138
146
  if (msg.type === "mcp_servers_available") {
139
- sendOrQueue({
147
+ _lastMcpServersMsg = {
140
148
  type: "mcp_servers_available",
141
149
  servers: msg.servers,
142
150
  hostConnected: msg.hostConnected
143
- });
151
+ };
152
+ sendOrQueue(_lastMcpServersMsg);
144
153
  }
145
154
 
146
155
  // MCP bridge: tool result from extension (tool results should not be queued,
@@ -161,6 +170,7 @@ export function initMisc() {
161
170
 
162
171
  // Forward an MCP tool call from the server to the Chrome extension
163
172
  export function forwardMcpToolCall(msg) {
173
+ console.log("[mcp] forwarding to extension:", msg.callId, msg.server, msg.method);
164
174
  window.postMessage({
165
175
  source: "clay-page",
166
176
  payload: {
@@ -185,6 +195,7 @@ export function setHttpMcpServers(servers) {
185
195
  }
186
196
 
187
197
  export function handleMcpToolCallMessage(msg) {
198
+ console.log("[mcp] tool call received from server:", msg.callId, msg.server, msg.params && msg.params.name);
188
199
  var httpUrl = _httpMcpServers[msg.server];
189
200
  if (httpUrl) {
190
201
  // HTTP transport: call directly via fetch
@@ -9,12 +9,19 @@ import { getWs } from './ws-ref.js';
9
9
  import { openDm } from './app-dm.js';
10
10
  import { getCachedProjects } from './app-projects.js';
11
11
  import { switchProject } from './app-projects.js';
12
+ import { mateAvatarUrl } from './avatar.js';
12
13
  var notifications = [];
13
14
  var unreadCount = 0;
14
15
  var bannerContainer = null;
15
16
  var bellBtn = null;
16
17
  var badgeEl = null;
17
18
 
19
+ // --- Update available banner state ---
20
+ // Server pushes update_available on an hourly boundary; dismissal is
21
+ // per-banner-instance and doesn't need to persist. The next server push
22
+ // (next hour) acts as a fresh ping.
23
+ var pendingUpdateMsg = null;
24
+
18
25
  // ========================================================
19
26
  // Init
20
27
  // ========================================================
@@ -44,7 +51,12 @@ function showAllBanners() {
44
51
  // Clear existing banners first
45
52
  if (bannerContainer) bannerContainer.innerHTML = "";
46
53
 
47
- if (notifications.length === 0) {
54
+ // Re-add update banner if present (may be suppressed by recent dismiss)
55
+ if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
56
+
57
+ // Check if any banner actually got rendered (update banner can be suppressed)
58
+ var hasVisibleBanner = bannerContainer.children.length > 0;
59
+ if (notifications.length === 0 && !hasVisibleBanner) {
48
60
  showBanner({
49
61
  id: "_empty",
50
62
  type: "info",
@@ -71,14 +83,17 @@ function showBanner(notif, autoDismissMs) {
71
83
  var projectIcon = isEmpty ? null : getProjectIcon(notif.slug);
72
84
  var projectName = isEmpty ? "" : getProjectName(notif.slug);
73
85
  var isPermission = notif.type === "permission_request" && notif.meta && notif.meta.requestId;
86
+ var mate = isEmpty ? null : getMateForNotification(notif);
74
87
 
75
88
  var banner = document.createElement("div");
76
89
  banner.className = "notif-banner" + (isPermission ? " notif-banner-permission" : "");
77
90
  if (!isEmpty) banner.setAttribute("data-notif-id", notif.id);
78
91
 
79
- var iconHtmlStr = projectIcon
80
- ? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
81
- : iconHtml(isEmpty ? "check-circle" : "folder");
92
+ var iconHtmlStr = mate
93
+ ? '<img class="notif-banner-avatar" src="' + escapeHtml(mateAvatarUrl(mate, 32)) + '" alt="' + escapeHtml(mate.displayName || mate.name || "Mate") + '">'
94
+ : projectIcon
95
+ ? '<span class="notif-banner-emoji">' + projectIcon + '</span>'
96
+ : iconHtml(isEmpty ? "check-circle" : "folder");
82
97
 
83
98
  // Format permission title as "Can I ..." style
84
99
  if (isPermission && notif.meta) {
@@ -93,7 +108,7 @@ function showBanner(notif, autoDismissMs) {
93
108
  actionsHtml =
94
109
  '<div class="notif-banner-actions">' +
95
110
  '<button class="notif-banner-allow">Sure</button>' +
96
- '<button class="notif-banner-always">Always allow</button>' +
111
+ '<button class="notif-banner-always">Allow for session</button>' +
97
112
  '<button class="notif-banner-deny">No</button>' +
98
113
  '<button class="notif-banner-goto" title="Go to session">' + iconHtml("external-link") + '</button>' +
99
114
  '</div>';
@@ -237,7 +252,7 @@ export function handleNotificationCreated(msg) {
237
252
  var notif = msg.notification;
238
253
 
239
254
  // Auto-dismiss if it's for the session the user is currently viewing
240
- var activeSession = store.getState().activeSessionId || null;
255
+ var activeSession = store.get('activeSessionId') || null;
241
256
  console.log("[notif] created:", notif.type, "sessionId=" + notif.sessionId + "(" + typeof notif.sessionId + ")", "active=" + activeSession + "(" + typeof activeSession + ")", "match=" + (notif.sessionId == activeSession));
242
257
  if (notif.sessionId && String(notif.sessionId) === String(activeSession)) {
243
258
  dismissNotif(notif.id);
@@ -292,12 +307,16 @@ function updateBadge() {
292
307
  // ========================================================
293
308
 
294
309
  function navigateToNotification(notif) {
295
- if (notif.mateId) {
296
- openDm(notif.mateId);
310
+ var mateId = notif.mateId || deriveMateIdFromNotification(notif);
311
+ if (mateId) {
312
+ if (notif.sessionId) {
313
+ try { sessionStorage.setItem("pending-notif-session", notif.sessionId); } catch (e) {}
314
+ }
315
+ openDm(mateId);
297
316
  return;
298
317
  }
299
318
 
300
- var currentSlug = store.getState().currentSlug || "";
319
+ var currentSlug = store.get('currentSlug') || "";
301
320
  var needsProjectSwitch = notif.slug && notif.slug !== currentSlug;
302
321
 
303
322
  if (needsProjectSwitch) {
@@ -314,6 +333,97 @@ function navigateToNotification(notif) {
314
333
  }
315
334
  }
316
335
 
336
+ function deriveMateIdFromNotification(notif) {
337
+ if (!notif) return null;
338
+ if (typeof notif.slug === "string" && notif.slug.indexOf("mate-") === 0) {
339
+ return notif.slug.substring(5) || null;
340
+ }
341
+ return null;
342
+ }
343
+
344
+ function getMateForNotification(notif) {
345
+ var mateId = notif && notif.meta ? notif.meta.avatarMateId : null;
346
+ if (!mateId) mateId = deriveMateIdFromNotification(notif);
347
+ if (!mateId) return null;
348
+ var mates = store.get('cachedMatesList') || [];
349
+ for (var i = 0; i < mates.length; i++) {
350
+ if (mates[i] && mates[i].id === mateId) return mates[i];
351
+ }
352
+ return { id: mateId };
353
+ }
354
+
355
+ // ========================================================
356
+ // Update available banner
357
+ // ========================================================
358
+
359
+ export function showUpdateBanner(msg) {
360
+ if (!msg || !msg.version) return;
361
+ pendingUpdateMsg = msg;
362
+ if (!bannerContainer) return;
363
+
364
+ // Remove any existing update banner
365
+ var existing = bannerContainer.querySelector('[data-notif-id="_update"]');
366
+ if (existing) removeBanner(existing);
367
+
368
+ var isHeadless = store.get('isHeadlessMode');
369
+ var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
370
+
371
+ var banner = document.createElement("div");
372
+ banner.className = "notif-banner notif-banner-update";
373
+ banner.setAttribute("data-notif-id", "_update");
374
+
375
+ var actionsHtml = '';
376
+ if (!isHeadless) {
377
+ actionsHtml =
378
+ '<div class="notif-banner-actions">' +
379
+ '<button class="notif-banner-update-now">Update now</button>' +
380
+ '</div>';
381
+ }
382
+
383
+ banner.innerHTML =
384
+ '<div class="notif-banner-icon"><img src="/icon-banded-76.png" width="32" height="32" alt="Clay" style="border-radius:8px"></div>' +
385
+ '<div class="notif-banner-body">' +
386
+ '<div class="notif-banner-project">CLAY</div>' +
387
+ '<div class="notif-banner-title">v' + escapeHtml(msg.version) + ' is available</div>' +
388
+ (isHeadless
389
+ ? '<div class="notif-banner-text">Run: npx clay-server@' + escapeHtml(updTag) + '</div>'
390
+ : '') +
391
+ actionsHtml +
392
+ '</div>' +
393
+ '<button class="notif-banner-close">' + iconHtml("x") + '</button>';
394
+
395
+ bannerContainer.appendChild(banner);
396
+ refreshIcons();
397
+
398
+ requestAnimationFrame(function () {
399
+ banner.classList.add("show");
400
+ });
401
+
402
+ // "Update now" button
403
+ var updateBtn = banner.querySelector(".notif-banner-update-now");
404
+ if (updateBtn) {
405
+ updateBtn.addEventListener("click", function (e) {
406
+ e.stopPropagation();
407
+ var ws = getWs();
408
+ if (ws && ws.readyState === 1) {
409
+ ws.send(JSON.stringify({ type: "update_now" }));
410
+ updateBtn.textContent = "Updating...";
411
+ updateBtn.disabled = true;
412
+ }
413
+ });
414
+ }
415
+
416
+ // Close button -> dismiss. No local throttle; the server pushes a new
417
+ // update_available on the next hour boundary, which re-shows naturally.
418
+ var closeBtn = banner.querySelector(".notif-banner-close");
419
+ if (closeBtn) {
420
+ closeBtn.addEventListener("click", function (e) {
421
+ e.stopPropagation();
422
+ removeBanner(banner);
423
+ });
424
+ }
425
+ }
426
+
317
427
  // ========================================================
318
428
  // Helpers
319
429
  // ========================================================