clay-server 2.29.4 → 2.29.5-beta.2

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.
package/lib/project.js CHANGED
@@ -872,6 +872,12 @@ function createProjectContext(opts) {
872
872
 
873
873
  // --- WS disconnection handler (delegated to project-connection.js) ---
874
874
  function handleDisconnection(ws) {
875
+ // Clean up extension WS reference if this was the extension client
876
+ if (browserState._extensionWs === ws) {
877
+ browserState._extensionWs = null;
878
+ browserState._extensionId = null;
879
+ if (_mcp) _mcp.handleExtensionDisconnect();
880
+ }
875
881
  _connection.handleDisconnection(ws);
876
882
  }
877
883
 
package/lib/public/app.js CHANGED
@@ -54,7 +54,7 @@ import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateR
54
54
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
55
55
  import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity, drawFaviconAnimFrame as _favDrawFaviconAnimFrame } from './modules/app-favicon.js';
56
56
  import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
57
- import { initMisc, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
57
+ import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
58
58
  import { initSkillInstall, requireSkills as _siRequireSkills, requireClayMateInterview as _siRequireClayMateInterview, handleSkillInstallWs as _siHandleSkillInstallWs } from './modules/app-skills-install.js';
59
59
  import { initDebateUi, showDebateConcludeConfirm as _debShowDebateConcludeConfirm, exitDebateConcludeMode as _debExitDebateConcludeMode, handleDebateConcludeSend as _debHandleDebateConcludeSend, showDebateEndedMode as _debShowDebateEndedMode, exitDebateEndedMode as _debExitDebateEndedMode, showDebateUserFloor as _debShowDebateUserFloor, exitDebateFloorMode as _debExitDebateFloorMode, handleDebateFloorSend as _debHandleDebateFloorSend, renderDebateUserFloorDone as _debRenderDebateUserFloorDone, showDebateSticky as _debShowDebateSticky, showDebateBottomBar as _debShowDebateBottomBar, removeDebateBottomBar as _debRemoveDebateBottomBar, sendDebateStickyComment as _debSendDebateStickyComment, updateDebateRound as _debUpdateDebateRound } from './modules/app-debate-ui.js';
60
60
  import { initLoopUi, updateLoopInputVisibility as _loopUpdateLoopInputVisibility, updateLoopButton as _loopUpdateLoopButton, showLoopBanner as _loopShowLoopBanner, updateLoopBanner as _loopUpdateLoopBanner, updateRalphBars as _loopUpdateRalphBars, showRalphCraftingBar as _loopShowRalphCraftingBar, showRalphApprovalBar as _loopShowRalphApprovalBar, updateRalphApprovalStatus as _loopUpdateRalphApprovalStatus, openRalphPreviewModal as _loopOpenRalphPreviewModal, showExecModal as _loopShowExecModal, closeExecModal as _loopCloseExecModal, updateExecModalStatus as _loopUpdateExecModalStatus } from './modules/app-loop-ui.js';
@@ -1337,6 +1337,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1337
1337
  setActivity: setActivity,
1338
1338
  processMessage: processMessage,
1339
1339
  onConnected: function () {
1340
+ // Flush any extension messages that arrived before WS was ready
1341
+ flushPendingExtMessages();
1342
+
1340
1343
  // Reset terminal xterm instances (server will send fresh term_list)
1341
1344
  resetTerminals();
1342
1345
 
@@ -11,6 +11,29 @@ import { setExtensionConnected } from './mcp-ui.js';
11
11
  var confirmCallback = null;
12
12
  var _extRequestCallbacks = {};
13
13
 
14
+ // Queue for extension messages that arrived before WS was ready
15
+ var _pendingExtMessages = [];
16
+
17
+ function sendOrQueue(msgObj) {
18
+ var ws = getWs();
19
+ if (ws && ws.readyState === 1) {
20
+ ws.send(JSON.stringify(msgObj));
21
+ } else {
22
+ _pendingExtMessages.push(msgObj);
23
+ }
24
+ }
25
+
26
+ export function flushPendingExtMessages() {
27
+ if (_pendingExtMessages.length === 0) return;
28
+ var ws = getWs();
29
+ 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]));
34
+ }
35
+ }
36
+
14
37
  export function initMisc() {
15
38
  // --- Confirm modal listeners ---
16
39
  var confirmModal = document.getElementById("confirm-modal");
@@ -98,14 +121,11 @@ export function initMisc() {
98
121
  if (msg.type === "clay_ext_tab_list") {
99
122
  setExtensionConnected(true);
100
123
  updateBrowserTabList(msg.tabs);
101
- // Also inform server about tab list
102
- var ws = getWs();
103
- if (ws && ws.readyState === 1) {
104
- ws.send(JSON.stringify({
105
- type: "browser_tab_list",
106
- tabs: msg.tabs
107
- }));
108
- }
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
+ });
109
129
  }
110
130
  if (msg.type === "clay_ext_result") {
111
131
  handleExtensionResult(msg.requestId, msg.result);
@@ -114,19 +134,17 @@ export function initMisc() {
114
134
  setExtensionConnected(false);
115
135
  }
116
136
 
117
- // MCP bridge: extension reports available MCP servers
137
+ // MCP bridge: extension reports available MCP servers (queue if WS not ready yet)
118
138
  if (msg.type === "mcp_servers_available") {
119
- var ws2 = getWs();
120
- if (ws2 && ws2.readyState === 1) {
121
- ws2.send(JSON.stringify({
122
- type: "mcp_servers_available",
123
- servers: msg.servers,
124
- hostConnected: msg.hostConnected
125
- }));
126
- }
139
+ sendOrQueue({
140
+ type: "mcp_servers_available",
141
+ servers: msg.servers,
142
+ hostConnected: msg.hostConnected
143
+ });
127
144
  }
128
145
 
129
- // MCP bridge: tool result from extension
146
+ // MCP bridge: tool result from extension (tool results should not be queued,
147
+ // if WS is down the call already timed out server-side)
130
148
  if (msg.type === "mcp_tool_result") {
131
149
  var ws3 = getWs();
132
150
  if (ws3 && ws3.readyState === 1) {
@@ -229,23 +229,12 @@ export function updateProjectList(msg) {
229
229
  }
230
230
  }
231
231
 
232
- // Update user strip (DM targets) - only if user data actually changed
232
+ // Update user strip (DM targets) - renderUserStrip has its own fingerprint guard
233
233
  if (msg.allUsers) {
234
- var allUsersJson = JSON.stringify(msg.allUsers);
235
- var dmFavJson = msg.dmFavorites ? JSON.stringify(msg.dmFavorites) : _lastDmFavJson;
236
- var dmConvJson = msg.dmConversations ? JSON.stringify(msg.dmConversations) : _lastDmConvJson;
237
- var userDataChanged = allUsersJson !== _lastAllUsersJson || dmFavJson !== _lastDmFavJson || dmConvJson !== _lastDmConvJson;
238
-
239
234
  _ctx.setCachedAllUsers(msg.allUsers);
240
235
  if (msg.dmFavorites) _ctx.setCachedDmFavorites(msg.dmFavorites);
241
236
  if (msg.dmConversations) _ctx.setCachedDmConversations(msg.dmConversations);
242
-
243
- if (userDataChanged) {
244
- _lastAllUsersJson = allUsersJson;
245
- _lastDmFavJson = dmFavJson;
246
- _lastDmConvJson = dmConvJson;
247
- _ctx.renderUserStrip(msg.allUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
248
- }
237
+ _ctx.renderUserStrip(msg.allUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
249
238
  if (document.body.classList.contains("mate-dm-active") || document.body.classList.contains("wide-view")) {
250
239
  var refreshedMyUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
251
240
  if (refreshedMyUser) {
@@ -271,9 +260,6 @@ export function updateProjectList(msg) {
271
260
  var _lastTopbarUserIds = [];
272
261
  var _lastProjectsJson = "";
273
262
  var _lastRenderedSlug = null;
274
- var _lastAllUsersJson = "";
275
- var _lastDmFavJson = "";
276
- var _lastDmConvJson = "";
277
263
  export function renderTopbarPresence(serverUsers) {
278
264
  var countEl = document.getElementById("client-count");
279
265
  if (!countEl) return;
@@ -19,6 +19,7 @@ var currentDmUserId = null;
19
19
  var dmPickerOpen = false;
20
20
  var cachedDmRemovedUsers = {};
21
21
  var cachedMates = [];
22
+ var _lastUserStripJson = "";
22
23
 
23
24
  // --- Icon strip tooltip ---
24
25
  var iconStripTooltip = null;
@@ -238,6 +239,11 @@ function presenceAvatarUrl(userOrStyle) {
238
239
  }
239
240
 
240
241
  export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites, dmConversations, dmUnread, dmRemovedUsers, matesList) {
242
+ // Skip full DOM rebuild if input data hasn't changed
243
+ var fingerprint = JSON.stringify([allUsers, onlineUserIds, dmFavorites, dmConversations, dmUnread, dmRemovedUsers, matesList]);
244
+ if (fingerprint === _lastUserStripJson) return;
245
+ _lastUserStripJson = fingerprint;
246
+
241
247
  cachedMates = matesList || cachedMates || [];
242
248
  cachedAllUsers = allUsers || [];
243
249
  cachedOnlineUserIds = onlineUserIds || [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.29.4",
3
+ "version": "2.29.5-beta.2",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",