clay-server 2.26.0-beta.12 → 2.26.0-beta.13

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.
@@ -1542,9 +1542,15 @@ function attachDebate(ctx) {
1542
1542
  avatarSeed: moderatorProfile.avatarSeed,
1543
1543
  });
1544
1544
 
1545
+ // Build explicit panelist list so the moderator knows who to call on
1546
+ var panelistNames = debate.panelists.map(function (p) {
1547
+ var prof = ctx.getMateProfile(mateCtx, p.mateId);
1548
+ return "@" + prof.name;
1549
+ });
1550
+ var panelistList = panelistNames.join(", ");
1545
1551
  var resumePrompt = instruction
1546
- ? "[The audience has requested the debate continue with the following direction]\nUser: " + instruction + "\n\n[As moderator, acknowledge this input and call on a panelist with @TheirName to continue the discussion.]"
1547
- : "[The audience has requested the debate continue. Call on the next panelist with @TheirName to explore additional perspectives.]";
1552
+ ? "[SYSTEM: The audience has requested the debate continue with new direction. You MUST call on a panelist to continue. Available panelists: " + panelistList + "]\n\nUser direction: " + instruction + "\n\n[Acknowledge this input briefly, then call on a panelist by writing their @Name to continue the discussion on this new direction. You must @mention exactly one panelist.]"
1553
+ : "[SYSTEM: The audience has requested the debate continue. You MUST call on the next panelist. Available panelists: " + panelistList + "]\n\n[Call on a panelist by writing their @Name to explore additional perspectives. You must @mention exactly one panelist.]";
1548
1554
 
1549
1555
  // If resuming from ended state, moderator session may be dead. Create a new one.
1550
1556
  if (wasEnded || !debate.moderatorSession || !debate.moderatorSession.isAlive()) {
@@ -1554,7 +1560,8 @@ function attachDebate(ctx) {
1554
1560
  var moderatorContext = buildModeratorContext(debate) + digests;
1555
1561
 
1556
1562
  // Include debate history so moderator has context
1557
- moderatorContext += "\n\nDebate history so far:\n---\n";
1563
+ moderatorContext += "\n\nIMPORTANT: This debate was previously paused and is now being RESUMED. You must continue the debate by calling on a panelist with @TheirName. Do NOT conclude or summarize.\n";
1564
+ moderatorContext += "\nDebate history so far:\n---\n";
1558
1565
  for (var hi = 0; hi < debate.history.length; hi++) {
1559
1566
  var h = debate.history[hi];
1560
1567
  moderatorContext += (h.mateName || h.speaker || "Unknown") + " (" + (h.role || "") + "): " + (h.text || "").slice(0, 500) + "\n\n";
package/lib/public/app.js CHANGED
@@ -5872,8 +5872,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5872
5872
  handleDmSend: function () { handleDmSend(); },
5873
5873
  isDebateEndedMode: function () { return debateEndedMode; },
5874
5874
  handleDebateEndedSend: function () { handleDebateEndedSend(); },
5875
- isDebateConcludeMode: function () { return debateConcludeMode; },
5876
- handleDebateConcludeSend: function () { handleDebateConcludeSend(); },
5875
+ isDebateConcludeMode: function () { return false; },
5876
+ handleDebateConcludeSend: null,
5877
5877
  isDebateFloorMode: function () { return debateFloorMode; },
5878
5878
  handleDebateFloorSend: function () { handleDebateFloorSend(); },
5879
5879
  isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
@@ -7201,70 +7201,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7201
7201
  }
7202
7202
 
7203
7203
  var debateFloorMode = false;
7204
- var debateConcludeMode = false;
7205
-
7206
- function showDebateConcludeMode() {
7207
- debateConcludeMode = true;
7208
- removeDebateBottomBar();
7209
- var inputArea = document.getElementById("input-area");
7210
- if (inputArea) {
7211
- inputArea.classList.add("debate-floor-mode");
7212
- inputArea.style.display = "";
7213
- }
7214
- // Add conclude banner above input
7215
- var existingBanner = document.getElementById("debate-floor-banner");
7216
- if (existingBanner) existingBanner.remove();
7217
- var banner = document.createElement("div");
7218
- banner.id = "debate-floor-banner";
7219
- banner.className = "debate-floor-banner";
7220
- banner.innerHTML = iconHtml("check-circle") + " <span>The moderator is ready to conclude</span>" +
7221
- '<button class="debate-floor-done-btn debate-floor-end-btn" id="debate-floor-end-btn">End Debate</button>';
7222
- if (inputArea && inputArea.parentNode) {
7223
- inputArea.parentNode.insertBefore(banner, inputArea);
7224
- }
7225
- refreshIcons();
7226
- // End Debate button
7227
- var endBtn = document.getElementById("debate-floor-end-btn");
7228
- if (endBtn) {
7229
- endBtn.addEventListener("click", function () {
7230
- if (ws && ws.readyState === 1) {
7231
- ws.send(JSON.stringify({ type: "debate_conclude_response", action: "end" }));
7232
- }
7233
- exitDebateConcludeMode();
7234
- });
7235
- }
7236
- // Update placeholder
7237
- var inputEl = document.getElementById("input");
7238
- if (inputEl) {
7239
- inputEl._origPlaceholder = inputEl._origPlaceholder || inputEl.placeholder;
7240
- inputEl.placeholder = "Add a direction to continue the debate...";
7241
- inputEl.focus();
7242
- }
7243
- scrollToBottom();
7244
- }
7245
-
7246
- function exitDebateConcludeMode() {
7247
- debateConcludeMode = false;
7248
- var inputArea = document.getElementById("input-area");
7249
- if (inputArea) inputArea.classList.remove("debate-floor-mode");
7250
- var banner = document.getElementById("debate-floor-banner");
7251
- if (banner) banner.remove();
7252
- var inputEl = document.getElementById("input");
7253
- if (inputEl && inputEl._origPlaceholder) {
7254
- inputEl.placeholder = inputEl._origPlaceholder;
7255
- delete inputEl._origPlaceholder;
7256
- }
7257
- }
7258
-
7259
- function handleDebateConcludeSend() {
7260
- var text = inputEl.value.trim();
7261
- if (ws && ws.readyState === 1) {
7262
- ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
7263
- }
7264
- inputEl.value = "";
7265
- exitDebateConcludeMode();
7266
- showDebateBottomBar("live");
7267
- }
7268
7204
 
7269
7205
  var debateEndedMode = false;
7270
7206
 
@@ -7508,9 +7444,43 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7508
7444
  }
7509
7445
  });
7510
7446
  } else if (mode === "conclude") {
7511
- // Use native input area with conclude banner
7512
- showDebateConcludeMode();
7513
- return; // don't create a separate bar
7447
+ bar.innerHTML =
7448
+ '<div class="debate-bottom-inner debate-bottom-conclude">' +
7449
+ '<div class="debate-bottom-conclude-label">' + iconHtml("check-circle") + ' The moderator is ready to conclude. End the debate?</div>' +
7450
+ '<textarea class="debate-bottom-conclude-input" id="debate-bottom-conclude-input" rows="3" placeholder="Or add a direction to continue..."></textarea>' +
7451
+ '<div class="debate-bottom-conclude-actions">' +
7452
+ '<button class="debate-bottom-continue" id="debate-bottom-continue">Continue</button>' +
7453
+ '<button class="debate-bottom-end" id="debate-bottom-end">End Debate</button>' +
7454
+ '</div>' +
7455
+ '</div>';
7456
+
7457
+ inputArea.parentNode.insertBefore(bar, inputArea);
7458
+ inputArea.style.display = "none";
7459
+ refreshIcons();
7460
+
7461
+ var textArea = document.getElementById("debate-bottom-conclude-input");
7462
+ document.getElementById("debate-bottom-end").addEventListener("click", function () {
7463
+ if (ws && ws.readyState === 1) {
7464
+ ws.send(JSON.stringify({ type: "debate_conclude_response", action: "end" }));
7465
+ }
7466
+ removeDebateBottomBar();
7467
+ });
7468
+ document.getElementById("debate-bottom-continue").addEventListener("click", function () {
7469
+ var text = textArea ? textArea.value.trim() : "";
7470
+ if (ws && ws.readyState === 1) {
7471
+ ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
7472
+ }
7473
+ removeDebateBottomBar();
7474
+ showDebateBottomBar("live");
7475
+ });
7476
+ if (textArea) {
7477
+ textArea.addEventListener("keydown", function (e) {
7478
+ if (e.key === "Enter" && !e.shiftKey) {
7479
+ e.preventDefault();
7480
+ document.getElementById("debate-bottom-continue").click();
7481
+ }
7482
+ });
7483
+ }
7514
7484
  }
7515
7485
  }
7516
7486
 
@@ -7530,9 +7500,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7530
7500
  var handBar = document.getElementById("debate-hand-raise-bar");
7531
7501
  if (handBar) handBar.remove();
7532
7502
  debateHandRaiseOpen = false;
7533
- // Clean up floor/conclude modes
7503
+ // Clean up floor/ended modes
7534
7504
  if (debateFloorMode) exitDebateFloorMode();
7535
- if (debateConcludeMode) exitDebateConcludeMode();
7536
7505
  if (debateEndedMode) exitDebateEndedMode();
7537
7506
  // Restore input area
7538
7507
  var inputArea = document.getElementById("input-area");
package/lib/sdk-bridge.js CHANGED
@@ -437,9 +437,32 @@ function createSDKBridge(opts) {
437
437
  session.pendingAskUser = {};
438
438
  session.activeTaskToolIds = {};
439
439
  session.taskIdMap = {};
440
- session.isProcessing = false;
441
440
  session.rateLimitResetsAt = null; // clear on success
441
+
442
+ // Handle SDK execution errors: show the error to the user instead of
443
+ // silently swallowing it. These have subtype "error_during_execution".
444
+ if (parsed.subtype === "error_during_execution") {
445
+ var execErrors = parsed.errors || [];
446
+ var execError = execErrors.length > 0
447
+ ? execErrors.join("; ")
448
+ : "Unknown SDK error";
449
+ if (parsed.terminal_reason) execError += " (reason: " + parsed.terminal_reason + ")";
450
+ console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
451
+ session.isProcessing = false;
452
+ onProcessingChanged();
453
+ sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
454
+ sendAndRecord(session, { type: "done", code: 1 });
455
+ sm.broadcastSessionList();
456
+ return;
457
+ }
458
+
459
+ session.isProcessing = false;
442
460
  onProcessingChanged();
461
+ // Detect "Not logged in" scenario early for the check below
462
+ var previewTrimmed = (session.responsePreview || "").trim();
463
+ var isZeroCost = !parsed.total_cost_usd || parsed.total_cost_usd === 0;
464
+ var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
465
+ && /not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed);
443
466
  // Fetch rich context usage breakdown (fire-and-forget, non-blocking)
444
467
  if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
445
468
  session.queryInstance.getContextUsage().then(function(ctxUsage) {
@@ -465,10 +488,6 @@ function createSDKBridge(opts) {
465
488
  }
466
489
  // Detect "Not logged in · Please run /login" from SDK.
467
490
  // This is a short canned response with zero cost, not actual AI output.
468
- var previewTrimmed = (session.responsePreview || "").trim();
469
- var isZeroCost = !parsed.total_cost_usd || parsed.total_cost_usd === 0;
470
- var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
471
- && /not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed);
472
491
  if (isLoginPrompt) {
473
492
  var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
474
493
  var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
package/lib/sessions.js CHANGED
@@ -1,6 +1,5 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
- var crypto = require("crypto");
4
3
  var config = require("./config");
5
4
  var utils = require("./utils");
6
5
  var users = require("./users");
@@ -76,9 +75,7 @@ function createSessionManager(opts) {
76
75
  }
77
76
 
78
77
  function saveSessionFile(session) {
79
- if (!session.cliSessionId) {
80
- session.cliSessionId = crypto.randomUUID();
81
- }
78
+ if (!session.cliSessionId) return;
82
79
  try {
83
80
  var metaObj = {
84
81
  type: "meta",
@@ -113,10 +110,7 @@ function createSessionManager(opts) {
113
110
  }
114
111
 
115
112
  function appendToSessionFile(session, obj) {
116
- if (!session.cliSessionId) {
117
- session.cliSessionId = crypto.randomUUID();
118
- saveSessionFile(session);
119
- }
113
+ if (!session.cliSessionId) return;
120
114
  session.lastActivity = Date.now();
121
115
  try {
122
116
  var afPath = sessionFilePath(session.cliSessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.26.0-beta.12",
3
+ "version": "2.26.0-beta.13",
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",