clay-server 5.2.0-beta.8 → 5.2.0-beta.9

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 (48) hide show
  1. package/lib/pane-pending-interactions.js +20 -0
  2. package/lib/pane-session-activation.js +114 -0
  3. package/lib/pane-session-transport.js +26 -0
  4. package/lib/project-sessions.js +15 -5
  5. package/lib/project.js +11 -3
  6. package/lib/public/app.js +14 -0
  7. package/lib/public/css/contextual-clay.css +1 -1
  8. package/lib/public/css/input.css +6 -0
  9. package/lib/public/css/sticky-notes.css +37 -11
  10. package/lib/public/modules/app-connection.js +3 -0
  11. package/lib/public/modules/app-message-router.js +13 -0
  12. package/lib/public/modules/app-messages.js +25 -5
  13. package/lib/public/modules/app-misc.js +3 -1
  14. package/lib/public/modules/app-projects.js +5 -0
  15. package/lib/public/modules/clay-owner-navigation.js +0 -3
  16. package/lib/public/modules/clay-studio-render.js +3 -1
  17. package/lib/public/modules/command-palette.js +52 -14
  18. package/lib/public/modules/contextual-clay-workspace.js +14 -2
  19. package/lib/public/modules/contextual-clay.js +16 -3
  20. package/lib/public/modules/contextual-mate-context.js +6 -4
  21. package/lib/public/modules/elicitation-form.js +5 -0
  22. package/lib/public/modules/history-replay-batch.js +8 -0
  23. package/lib/public/modules/input.js +55 -11
  24. package/lib/public/modules/message-delivery.js +10 -2
  25. package/lib/public/modules/model-picker.js +2 -0
  26. package/lib/public/modules/pane-activation.js +214 -0
  27. package/lib/public/modules/pane-binding.js +82 -0
  28. package/lib/public/modules/pane-bridge.js +5 -2
  29. package/lib/public/modules/pane-browser-bridge.js +5 -3
  30. package/lib/public/modules/pane-file-bridge.js +4 -0
  31. package/lib/public/modules/pane-issue-bridge.js +4 -1
  32. package/lib/public/modules/rewind.js +5 -0
  33. package/lib/public/modules/session-actions.js +5 -2
  34. package/lib/public/modules/session-context-menu.js +5 -0
  35. package/lib/public/modules/session-rich-drafts.js +21 -0
  36. package/lib/public/modules/session-switch-defer.js +67 -0
  37. package/lib/public/modules/split-group-restore.js +68 -0
  38. package/lib/public/modules/split-pair-ui.js +1 -0
  39. package/lib/public/modules/split-pane-reconciler.js +15 -0
  40. package/lib/public/modules/split-pane-renderer.js +87 -6
  41. package/lib/public/modules/split-session-boundary.js +1 -1
  42. package/lib/public/modules/split-view.js +51 -52
  43. package/lib/public/modules/sticky-notes-checklist.js +51 -6
  44. package/lib/public/modules/stt-controller.js +4 -1
  45. package/lib/public/modules/stt-recording.js +1 -1
  46. package/lib/public/modules/tools.js +18 -2
  47. package/lib/sessions.js +15 -8
  48. package/package.json +4 -3
@@ -9,6 +9,7 @@ import { openFile } from './filebrowser.js';
9
9
  import { mateAvatarUrl } from './avatar.js';
10
10
  import { getChatLayout } from './theme.js';
11
11
  import { store } from './store.js';
12
+ import { currentPaneReview } from './pane-binding.js';
12
13
  import { applyPermissionHints } from './permission-hints.js';
13
14
  import { VENDOR_NAMES } from './app-rendering.js';
14
15
  import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
@@ -394,12 +395,13 @@ export function renderAskUserQuestion(toolId, input) {
394
395
  skipBtn.className = "ask-user-skip";
395
396
  skipBtn.textContent = "Skip";
396
397
  skipBtn.addEventListener("click", function () {
398
+ if (!currentPaneReview(container)) return;
397
399
  if (container.classList.contains("answered")) return;
398
400
  container.classList.add("answered");
399
401
  markAskUserRequestAnswered(toolId);
400
402
  enableMainInput();
401
403
  if (ctx.ws && ctx.connected) {
402
- ctx.ws.send(JSON.stringify({ type: "stop" }));
404
+ ctx.ws.send(JSON.stringify({ type: "stop", paneBinding: container.paneBinding || undefined }));
403
405
  }
404
406
  });
405
407
  actions.appendChild(skipBtn);
@@ -419,6 +421,7 @@ export function renderAskUserQuestion(toolId, input) {
419
421
  container.appendChild(mateContentWrap);
420
422
  }
421
423
 
424
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
422
425
  ctx.addToMessages(container);
423
426
  disableMainInput();
424
427
  ctx.setActivity(null);
@@ -436,6 +439,7 @@ export function enableMainInput() {
436
439
  }
437
440
 
438
441
  function submitAskUserAnswer(container, toolId, questions, answers, multiSelections) {
442
+ if (!currentPaneReview(container)) return;
439
443
  if (container.classList.contains("answered") || container.dataset.askUserSubmitted === "true") return;
440
444
 
441
445
  var result = {};
@@ -461,6 +465,7 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
461
465
  if (ctx.ws && ctx.connected) {
462
466
  ctx.ws.send(JSON.stringify({
463
467
  type: "ask_user_response",
468
+ paneBinding: container.paneBinding || undefined,
464
469
  toolId: toolId,
465
470
  answers: result,
466
471
  }));
@@ -655,6 +660,7 @@ function renderFormalPermission(requestId, toolName, toolInput, decisionReason)
655
660
  container.appendChild(header);
656
661
  container.appendChild(body);
657
662
  container.appendChild(actions);
663
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
658
664
  ctx.addToMessages(container);
659
665
 
660
666
  pendingPermissions[requestId] = container;
@@ -762,6 +768,7 @@ function renderPlanPermission(requestId) {
762
768
  container.appendChild(body);
763
769
  container.appendChild(actions);
764
770
  container.appendChild(feedbackRow);
771
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
765
772
  ctx.addToMessages(container);
766
773
 
767
774
  pendingPermissions[requestId] = container;
@@ -769,10 +776,11 @@ function renderPlanPermission(requestId) {
769
776
  ctx.setActivity(null);
770
777
  maybeScrollToBottom();
771
778
  // Focus the feedback input after render
772
- setTimeout(function () { feedbackInput.focus(); }, 50);
779
+ setTimeout(function () { if (currentPaneReview(container)) feedbackInput.focus(); }, 50);
773
780
  }
774
781
 
775
782
  function sendPlanResponse(container, requestId, decision, feedback) {
783
+ if (!currentPaneReview(container)) return;
776
784
  if (container.classList.contains("resolved")) return;
777
785
  container.classList.add("resolved");
778
786
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
@@ -800,6 +808,7 @@ function sendPlanResponse(container, requestId, decision, feedback) {
800
808
  if (ctx.ws && ctx.connected) {
801
809
  var payload = {
802
810
  type: "permission_response",
811
+ paneBinding: container.paneBinding || undefined,
803
812
  requestId: requestId,
804
813
  decision: decision,
805
814
  };
@@ -954,6 +963,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
954
963
 
955
964
  container.appendChild(content);
956
965
 
966
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
957
967
  ctx.addToMessages(container);
958
968
  pendingPermissions[requestId] = container;
959
969
  refreshIcons();
@@ -962,6 +972,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
962
972
  }
963
973
 
964
974
  function sendPermissionResponse(container, requestId, decision) {
975
+ if (!currentPaneReview(container)) return;
965
976
  if (container.classList.contains("resolved")) return;
966
977
  container.classList.add("resolved");
967
978
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
@@ -979,6 +990,7 @@ function sendPermissionResponse(container, requestId, decision) {
979
990
  if (ctx.ws && ctx.connected) {
980
991
  ctx.ws.send(JSON.stringify({
981
992
  type: "permission_response",
993
+ paneBinding: container.paneBinding || undefined,
982
994
  requestId: requestId,
983
995
  decision: decision,
984
996
  }));
@@ -1053,6 +1065,7 @@ export function renderElicitationRequest(msg) {
1053
1065
  var container = buildElicitationCard(msg, function () {
1054
1066
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
1055
1067
  });
1068
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
1056
1069
  ctx.addToMessages(container);
1057
1070
  pendingElicitations[msg.requestId] = container;
1058
1071
  refreshIcons();
@@ -1079,6 +1092,7 @@ export function markElicitationError(requestId, error, expired) {
1079
1092
  var pendingUserDialogs = {};
1080
1093
 
1081
1094
  function sendUserDialogResponse(container, requestId, behavior, result) {
1095
+ if (!currentPaneReview(container)) return;
1082
1096
  if (container.classList.contains("resolved")) return;
1083
1097
  container.classList.add("resolved");
1084
1098
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
@@ -1094,6 +1108,7 @@ function sendUserDialogResponse(container, requestId, behavior, result) {
1094
1108
  if (ctx.ws && ctx.connected) {
1095
1109
  var msg = {
1096
1110
  type: "user_dialog_response",
1111
+ paneBinding: container.paneBinding || undefined,
1097
1112
  requestId: requestId,
1098
1113
  behavior: isCancel ? "cancelled" : "completed",
1099
1114
  };
@@ -1185,6 +1200,7 @@ export function renderUserDialogRequest(msg) {
1185
1200
  container.appendChild(header);
1186
1201
  container.appendChild(body);
1187
1202
  container.appendChild(actions);
1203
+ container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
1188
1204
  ctx.addToMessages(container);
1189
1205
 
1190
1206
  pendingUserDialogs[msg.requestId] = container;
package/lib/sessions.js CHANGED
@@ -789,8 +789,9 @@ function createSessionManager(opts) {
789
789
  return 0;
790
790
  }
791
791
 
792
- function replayHistory(session, fromIndex, targetWs, transform) {
793
- var _send = (targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send;
792
+ function replayHistory(session, fromIndex, targetWs, transform, snapshotSend) {
793
+ var _send = snapshotSend || ((targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send);
794
+ var sendHistory = snapshotSend && snapshotSend.history || _send;
794
795
  var total = session.history.length;
795
796
  if (typeof fromIndex !== "number") {
796
797
  if (total <= INITIAL_HISTORY_SIZE) {
@@ -800,7 +801,7 @@ function createSessionManager(opts) {
800
801
  }
801
802
  }
802
803
 
803
- _send({ type: "history_meta", total: total, from: fromIndex });
804
+ sendHistory({ type: "history_meta", total: total, from: fromIndex });
804
805
 
805
806
  for (var i = fromIndex; i < total; i++) {
806
807
  var _item = session.history[i];
@@ -812,7 +813,7 @@ function createSessionManager(opts) {
812
813
  var normalizedItem = require("./yoke/usage").clientEvent(_item, session.vendor || "claude");
813
814
  var replayItem = transform ? transform(normalizedItem) : normalizedItem;
814
815
  if (replayItem && typeof replayItem === "object") replayItem = Object.assign({}, replayItem, { _historyIndex: i });
815
- _send(replayItem);
816
+ sendHistory(replayItem);
816
817
  }
817
818
 
818
819
  // Find the last result message in the full history for accurate context data
@@ -833,16 +834,17 @@ function createSessionManager(opts) {
833
834
  }
834
835
  }
835
836
 
836
- _send({ type: "history_done", controlHistoryRevision: require("./clay-control-target-projection").digest(session.history || []), sessionId: session.localId, sessionOriginId: session.sessionOriginId || null, lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, context: lastContext, contextUsage: require("./yoke/usage").context(session.lastContextUsage, session.vendor || "claude") });
837
+ sendHistory({ type: "history_done", controlHistoryRevision: require("./clay-control-target-projection").digest(session.history || []), sessionId: session.localId, sessionOriginId: session.sessionOriginId || null, lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, context: lastContext, contextUsage: require("./yoke/usage").context(session.lastContextUsage, session.vendor || "claude") });
837
838
  for (var vi = 0; vi < sessionViewedListeners.length; vi++) {
838
839
  try { sessionViewedListeners[vi](session, targetWs, _send); } catch (e) {}
839
840
  }
840
841
  }
841
842
 
842
- function switchSession(localId, targetWs, transform) {
843
+ function switchSession(localId, targetWs, transform, snapshotSend, snapshotGuard) {
843
844
  var session = sessions.get(localId);
844
845
  if (!session) return;
845
846
  if (typeof onSessionHydrate === "function") onSessionHydrate(session);
847
+ if (snapshotGuard && !snapshotGuard()) return;
846
848
 
847
849
  // Pane sockets (split-view iframes) pin their session via switch_session
848
850
  // but must not move the project-global active pointer: reloads and
@@ -863,7 +865,7 @@ function createSessionManager(opts) {
863
865
  singleUserUnread[localId] = 0;
864
866
 
865
867
  // In multi-user mode with a specific client, only send to that client
866
- var _send = (targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send;
868
+ var _send = snapshotSend || ((targetWs && sendTo) ? function (obj) { sendTo(targetWs, obj); } : send);
867
869
 
868
870
  var _capsByVendor = capabilitiesByVendor || {};
869
871
  var _sessionVendor = session.vendor || defaultVendor || "claude";
@@ -876,7 +878,7 @@ function createSessionManager(opts) {
876
878
  || [];
877
879
  _send({ type: "slash_commands", commands: _vendorCmds, vendor: _sessionVendor });
878
880
  broadcastSessionList();
879
- replayHistory(session, undefined, targetWs, transform);
881
+ replayHistory(session, undefined, targetWs, transform, snapshotSend);
880
882
 
881
883
  if (session.isProcessing) {
882
884
  _send({ type: "status", status: "processing" });
@@ -894,10 +896,15 @@ function createSessionManager(opts) {
894
896
  toolInput: p.toolInput,
895
897
  toolUseId: p.toolUseId,
896
898
  decisionReason: p.decisionReason,
899
+ defaultToNo: p.defaultToNo,
900
+ suppressAlwaysAllowRule: p.suppressAlwaysAllowRule,
901
+ mateId: p.mateId || undefined,
902
+ vendor: p.vendor || session.vendor || undefined,
897
903
  });
898
904
  }
899
905
 
900
906
  // Re-send active mention indicator so returning clients restore the mate avatar state
907
+ if (snapshotSend) require('./pane-pending-interactions').send(session, _send);
901
908
  if (session._mentionInProgress && session._mentionActiveMateId) {
902
909
  _send({ type: "mention_processing", mateId: session._mentionActiveMateId, active: true });
903
910
  }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "5.2.0-beta.8",
3
+ "version": "5.2.0-beta.9",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js"
7
7
  },
8
8
  "scripts": {
9
9
  "dev": "node bin/cli.js --dev",
10
- "test": "node --test --test-force-exit test/*.test.js",
11
- "semantic-release": "semantic-release"
10
+ "test": "node --test --test-force-exit --test-skip-pattern=\"^real sidebar (normal|long-turn):\" test/*.test.js",
11
+ "semantic-release": "semantic-release",
12
+ "test:pane-performance": "node --test --test-force-exit test/pane-reuse-browser.test.js"
12
13
  },
13
14
  "files": [
14
15
  "bin/",