clay-server 3.2.0 → 3.2.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.
package/lib/sdk-bridge.js CHANGED
@@ -972,7 +972,8 @@ function createSDKBridge(opts) {
972
972
  // Only clean up if the session still references OUR resources.
973
973
  // A rewind + new startQuery may have already replaced these with
974
974
  // a newer query — clobbering them would kill the new query.
975
- if (session.queryInstance === myQueryInstance) {
975
+ var ownsRuntime = session.queryInstance === myQueryInstance;
976
+ if (ownsRuntime) {
976
977
  try {
977
978
  if (typeof session.queryInstance.close === "function") {
978
979
  session.queryInstance.close();
@@ -982,55 +983,57 @@ function createSDKBridge(opts) {
982
983
  }
983
984
  if (session.messageQueue === myMessageQueue) session.messageQueue = null;
984
985
  if (session.abortController === myAbortController) session.abortController = null;
985
- session.taskStopRequested = false;
986
- session.pendingPermissions = {};
987
- // Preserve MCP-mode AskUserQuestion entries across turn boundaries.
988
- // The MCP path is intentionally stateless: the tool returns immediately
989
- // ("card posted, end your turn") and the user's answer is expected to
990
- // arrive as a brand-new user_message on the *next* turn. That means the
991
- // pending entry MUST survive this finally block. Without it, the
992
- // ask_user_response handler can't find the toolId and silently drops
993
- // the answer. canUseTool-mode entries (Claude's native path) still hold
994
- // an open SDK permission callback that dies with the query, so those
995
- // are correctly cleared here.
996
- var keepAskUser = {};
997
- for (var _tid in session.pendingAskUser) {
998
- var _pending = session.pendingAskUser[_tid];
999
- if (_pending && _pending.mode === "mcp") keepAskUser[_tid] = _pending;
1000
- }
1001
- session.pendingAskUser = keepAskUser;
1002
- session.pendingElicitations = {};
986
+ if (ownsRuntime) {
987
+ session.taskStopRequested = false;
988
+ session.pendingPermissions = {};
989
+ // Preserve MCP-mode AskUserQuestion entries across turn boundaries.
990
+ // The MCP path is intentionally stateless: the tool returns immediately
991
+ // ("card posted, end your turn") and the user's answer is expected to
992
+ // arrive as a brand-new user_message on the *next* turn. That means the
993
+ // pending entry MUST survive this finally block. Without it, the
994
+ // ask_user_response handler can't find the toolId and silently drops
995
+ // the answer. canUseTool-mode entries (Claude's native path) still hold
996
+ // an open SDK permission callback that dies with the query, so those
997
+ // are correctly cleared here.
998
+ var keepAskUser = {};
999
+ for (var _tid in session.pendingAskUser) {
1000
+ var _pending = session.pendingAskUser[_tid];
1001
+ if (_pending && _pending.mode === "mcp") keepAskUser[_tid] = _pending;
1002
+ }
1003
+ session.pendingAskUser = keepAskUser;
1004
+ session.pendingElicitations = {};
1003
1005
 
1004
- // Auto-continue on rate limit (scheduler sessions, or user setting)
1005
- // Mark session as done processing so the late rate_limit_event handler
1006
- // can detect the race condition and schedule auto-continue itself.
1007
- session.isProcessing = false;
1006
+ // Auto-continue on rate limit (scheduler sessions, or user setting)
1007
+ // Mark session as done processing so the late rate_limit_event handler
1008
+ // can detect the race condition and schedule auto-continue itself.
1009
+ session.isProcessing = false;
1008
1010
 
1009
- var didScheduleAutoContinue = false;
1010
- var acEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
1011
- if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
1012
- && acEnabled && !session.destroying) {
1013
- var acResetsAt = session.rateLimitResetsAt;
1014
- session.rateLimitResetsAt = null;
1015
- session.rateLimitAutoContinuePending = true;
1016
- didScheduleAutoContinue = true;
1017
- console.log("[sdk-bridge] Rate limited, scheduling auto-continue via scheduleMessage for session " + session.localId);
1018
- if (typeof opts.scheduleMessage === "function") {
1019
- opts.scheduleMessage(session, "continue", acResetsAt);
1011
+ var didScheduleAutoContinue = false;
1012
+ var acEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
1013
+ if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
1014
+ && acEnabled && !session.destroying) {
1015
+ var acResetsAt = session.rateLimitResetsAt;
1016
+ session.rateLimitResetsAt = null;
1017
+ session.rateLimitAutoContinuePending = true;
1018
+ didScheduleAutoContinue = true;
1019
+ console.log("[sdk-bridge] Rate limited, scheduling auto-continue via scheduleMessage for session " + session.localId);
1020
+ if (typeof opts.scheduleMessage === "function") {
1021
+ opts.scheduleMessage(session, "continue", acResetsAt);
1022
+ }
1023
+ } else if (acEnabled && !session.destroying) {
1024
+ // Log why auto-continue was not scheduled (for debugging)
1025
+ console.log("[sdk-bridge] Query done, auto-continue enabled but not scheduled: rateLimitResetsAt=" +
1026
+ session.rateLimitResetsAt + " (will rely on late rate_limit_event handler)");
1020
1027
  }
1021
- } else if (acEnabled && !session.destroying) {
1022
- // Log why auto-continue was not scheduled (for debugging)
1023
- console.log("[sdk-bridge] Query done, auto-continue enabled but not scheduled: rateLimitResetsAt=" +
1024
- session.rateLimitResetsAt + " (will rely on late rate_limit_event handler)");
1025
- }
1026
1028
 
1027
- // Ralph Loop: notify completion so loop orchestrator can proceed
1028
- if (session.onQueryComplete && !didScheduleAutoContinue) {
1029
- console.log("[sdk-bridge] Calling onQueryComplete for session " + session.localId + " (title: " + (session.title || "?") + ")");
1030
- try {
1031
- session.onQueryComplete(session);
1032
- } catch (err) {
1033
- console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
1029
+ // Ralph Loop: notify completion so loop orchestrator can proceed
1030
+ if (session.onQueryComplete && !didScheduleAutoContinue) {
1031
+ console.log("[sdk-bridge] Calling onQueryComplete for session " + session.localId + " (title: " + (session.title || "?") + ")");
1032
+ try {
1033
+ session.onQueryComplete(session);
1034
+ } catch (err) {
1035
+ console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
1036
+ }
1034
1037
  }
1035
1038
  }
1036
1039
  }
@@ -1620,8 +1623,30 @@ function createSDKBridge(opts) {
1620
1623
  }
1621
1624
  // Route through QueryHandle (works for both in-process and worker paths)
1622
1625
  if (session.queryInstance && typeof session.queryInstance.pushMessage === "function") {
1623
- session.queryInstance.pushMessage(text, images);
1624
- return true;
1626
+ var activeQuery = session.queryInstance;
1627
+ var activeAbortController = session.abortController;
1628
+ var activeMessageQueue = session.messageQueue;
1629
+ var delivered = false;
1630
+ try {
1631
+ // Older third-party adapters returned undefined. Preserve compatibility
1632
+ // while requiring built-in adapters to return false for a closed input.
1633
+ delivered = activeQuery.pushMessage(text, images) !== false;
1634
+ } catch (e) {
1635
+ console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
1636
+ }
1637
+ if (delivered) return true;
1638
+
1639
+ // The handle object can outlive its underlying input queue or worker.
1640
+ // Retire it before the caller starts a resumed replacement query; the
1641
+ // old stream's finally block compares object identity and cannot clobber
1642
+ // the replacement resources.
1643
+ try {
1644
+ if (typeof activeQuery.close === "function") activeQuery.close();
1645
+ } catch (e) {}
1646
+ if (session.queryInstance === activeQuery) session.queryInstance = null;
1647
+ if (session.abortController === activeAbortController) session.abortController = null;
1648
+ if (session.messageQueue === activeMessageQueue) session.messageQueue = null;
1649
+ return false;
1625
1650
  }
1626
1651
  if (session._queryStarting) {
1627
1652
  session.pendingPush = session.pendingPush || [];
@@ -53,7 +53,7 @@ function createMessageQueue() {
53
53
  var ended = false;
54
54
  return {
55
55
  push: function(msg) {
56
- if (ended) return;
56
+ if (ended) return false;
57
57
  if (waiting) {
58
58
  var resolve = waiting;
59
59
  waiting = null;
@@ -61,6 +61,7 @@ function createMessageQueue() {
61
61
  } else {
62
62
  queue.push(msg);
63
63
  }
64
+ return true;
64
65
  },
65
66
  end: function() {
66
67
  ended = true;
@@ -346,6 +347,7 @@ function flattenEvent(raw) {
346
347
  // Wraps a raw SDK query object with the YOKE QueryHandle interface.
347
348
  // Events are flattened via flattenEvent before yielding.
348
349
  function createQueryHandle(rawQuery, messageQueue, abortController) {
350
+ var closed = false;
349
351
  var handle = {
350
352
  // Opaque adapter state (null for in-process queries)
351
353
  _adapterState: null,
@@ -356,14 +358,19 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
356
358
  return {
357
359
  next: function() {
358
360
  return rawIter.next().then(function(result) {
361
+ if (result.done) closed = true;
359
362
  if (result.done) return result;
360
363
  return { value: flattenEvent(result.value), done: false };
364
+ }, function(err) {
365
+ closed = true;
366
+ throw err;
361
367
  });
362
368
  },
363
369
  };
364
370
  },
365
371
 
366
372
  pushMessage: function(text, images) {
373
+ if (closed) return false;
367
374
  var content = [];
368
375
  if (images && images.length > 0) {
369
376
  for (var i = 0; i < images.length; i++) {
@@ -374,7 +381,7 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
374
381
  }
375
382
  }
376
383
  if (text) content.push({ type: "text", text: text });
377
- messageQueue.push({ type: "user", message: { role: "user", content: content } });
384
+ return messageQueue.push({ type: "user", message: { role: "user", content: content } });
378
385
  },
379
386
 
380
387
  setModel: function(model) {
@@ -443,12 +450,14 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
443
450
  },
444
451
 
445
452
  abort: function() {
453
+ closed = true;
446
454
  if (abortController) {
447
455
  try { abortController.abort(); } catch (e) {}
448
456
  }
449
457
  },
450
458
 
451
459
  close: function() {
460
+ closed = true;
452
461
  try { messageQueue.end(); } catch (e) {}
453
462
  if (rawQuery && typeof rawQuery.close === "function") {
454
463
  try { rawQuery.close(); } catch (e) {}
@@ -457,6 +466,7 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
457
466
 
458
467
  // End the message queue without closing the raw query
459
468
  endInput: function() {
469
+ closed = true;
460
470
  try { messageQueue.end(); } catch (e) {}
461
471
  },
462
472
 
@@ -987,6 +997,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
987
997
  },
988
998
 
989
999
  pushMessage: function(text, images) {
1000
+ if (iterEnded || (worker.process && (worker.process.killed || worker.process.exitCode != null))) return false;
990
1001
  var content = [];
991
1002
  if (images && images.length > 0) {
992
1003
  for (var i = 0; i < images.length; i++) {
@@ -998,7 +1009,12 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
998
1009
  }
999
1010
  if (text) content.push({ type: "text", text: text });
1000
1011
  var userMsg = { type: "user", message: { role: "user", content: content } };
1001
- worker.send({ type: "push_message", content: userMsg });
1012
+ try {
1013
+ worker.send({ type: "push_message", content: userMsg });
1014
+ return true;
1015
+ } catch (e) {
1016
+ return false;
1017
+ }
1002
1018
  },
1003
1019
 
1004
1020
  setModel: function(model) {
@@ -738,7 +738,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
738
738
  var handlerEntry = null;
739
739
 
740
740
  function pushMessageToQueue(msg) {
741
- if (messageQueueEnded) return;
741
+ if (messageQueueEnded) return false;
742
742
  if (messageWaiting) {
743
743
  var resolve = messageWaiting;
744
744
  messageWaiting = null;
@@ -746,6 +746,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
746
746
  } else {
747
747
  messageQueue.push(msg);
748
748
  }
749
+ return true;
749
750
  }
750
751
 
751
752
  function waitForMessage() {
@@ -1119,6 +1120,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1119
1120
  },
1120
1121
 
1121
1122
  pushMessage: function(text, images) {
1123
+ if (iteratorDone || state.done || messageQueueEnded) return false;
1122
1124
  var input;
1123
1125
  if (images && images.length > 0) {
1124
1126
  input = [];
@@ -1134,8 +1136,9 @@ function createCodexQueryHandle(appServer, queryOpts) {
1134
1136
  if (!state.loopStarted) {
1135
1137
  state.loopStarted = true;
1136
1138
  runQueryLoop(input);
1139
+ return true;
1137
1140
  } else {
1138
- pushMessageToQueue(input);
1141
+ return pushMessageToQueue(input);
1139
1142
  }
1140
1143
  },
1141
1144
 
@@ -449,7 +449,7 @@ function createKiroQueryHandle(acp, queryOpts) {
449
449
  var messageQueueEnded = false;
450
450
 
451
451
  function pushMessageToQueue(msg) {
452
- if (messageQueueEnded) return;
452
+ if (messageQueueEnded) return false;
453
453
  if (messageWaiting) {
454
454
  var resolve = messageWaiting;
455
455
  messageWaiting = null;
@@ -457,6 +457,7 @@ function createKiroQueryHandle(acp, queryOpts) {
457
457
  } else {
458
458
  messageQueue.push(msg);
459
459
  }
460
+ return true;
460
461
  }
461
462
  function waitForMessage() {
462
463
  if (messageQueue.length > 0) return Promise.resolve(messageQueue.shift());
@@ -737,6 +738,7 @@ function createKiroQueryHandle(acp, queryOpts) {
737
738
  },
738
739
 
739
740
  pushMessage: function(text, images) {
741
+ if (iteratorDone || state.done || messageQueueEnded) return false;
740
742
  var input = [];
741
743
  if (images && images.length > 0) {
742
744
  for (var i = 0; i < images.length; i++) {
@@ -752,8 +754,9 @@ function createKiroQueryHandle(acp, queryOpts) {
752
754
  if (!state.loopStarted) {
753
755
  state.loopStarted = true;
754
756
  runQueryLoop(payload);
757
+ return true;
755
758
  } else {
756
- pushMessageToQueue(payload);
759
+ return pushMessageToQueue(payload);
757
760
  }
758
761
  },
759
762
 
@@ -35,7 +35,7 @@ var TOOL_POLICIES = ["ask", "allow-all"];
35
35
  *
36
36
  * QueryHandle shape:
37
37
  * [Symbol.asyncIterator]() - yields SDK events (raw in Phase 3, normalized later)
38
- * .pushMessage(text, images)
38
+ * .pushMessage(text, images) - boolean; false when the handle cannot accept input
39
39
  * .setModel(model)
40
40
  * .setEffort(effort)
41
41
  * .setToolPolicy(policy) - "ask" | "allow-all"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
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",