clay-server 3.3.0-beta.1 → 3.3.0

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.
@@ -701,12 +701,7 @@ function spawnWorker(linuxUser, workerScriptPath, cwd) {
701
701
  });
702
702
 
703
703
  worker.send = function(msg) {
704
- if (!worker.connection || worker.connection.destroyed) return;
705
- try {
706
- worker.connection.write(JSON.stringify(serializeWorkerValue(msg)) + "\n");
707
- } catch (e) {
708
- console.error("[yoke/claude] Failed to send to worker:", e.message);
709
- }
704
+ return sendWorkerMessage(worker, msg);
710
705
  };
711
706
 
712
707
  worker.onMessage = function(handler) {
@@ -732,6 +727,36 @@ function spawnWorker(linuxUser, workerScriptPath, cwd) {
732
727
  return worker;
733
728
  }
734
729
 
730
+ function hasWritableWorkerConnection(worker) {
731
+ var connection = worker && worker.connection;
732
+ return !!connection
733
+ && !connection.destroyed
734
+ && !connection.writableEnded
735
+ && connection.writable !== false;
736
+ }
737
+
738
+ function sendWorkerMessage(worker, msg) {
739
+ if (!hasWritableWorkerConnection(worker)) return false;
740
+ try {
741
+ // A false return from socket.write means backpressure, not rejection. The
742
+ // bytes are still queued, so delivery is accepted unless write throws.
743
+ worker.connection.write(JSON.stringify(serializeWorkerValue(msg)) + "\n");
744
+ return true;
745
+ } catch (e) {
746
+ console.error("[yoke/claude] Failed to send to worker:", e.message);
747
+ return false;
748
+ }
749
+ }
750
+
751
+ function canReuseWorker(worker) {
752
+ return !!worker
753
+ && worker.ready
754
+ && worker.process
755
+ && !worker.process.killed
756
+ && worker.process.exitCode == null
757
+ && hasWritableWorkerConnection(worker);
758
+ }
759
+
735
760
  function cleanupWorker(worker) {
736
761
  console.log("[yoke/claude] cleanupWorker() called, pid=" + (worker.process ? worker.process.pid : "?") + " stack=" + new Error().stack.split("\n").slice(1, 4).join(" | "));
737
762
  if (worker._abortTimeout) { clearTimeout(worker._abortTimeout); worker._abortTimeout = null; }
@@ -1009,12 +1034,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1009
1034
  }
1010
1035
  if (text) content.push({ type: "text", text: text });
1011
1036
  var userMsg = { type: "user", message: { role: "user", content: content } };
1012
- try {
1013
- worker.send({ type: "push_message", content: userMsg });
1014
- return true;
1015
- } catch (e) {
1016
- return false;
1017
- }
1037
+ return worker.send({ type: "push_message", content: userMsg }) === true;
1018
1038
  },
1019
1039
 
1020
1040
  setModel: function(model) {
@@ -1498,7 +1518,8 @@ function createClaudeAdapter(opts) {
1498
1518
  var reusingWorker = false;
1499
1519
 
1500
1520
  // Wait for previous worker exit if needed
1501
- if (workerState && workerState.exitPromise && !workerState.worker) {
1521
+ if (workerState && workerState.exitPromise
1522
+ && (!workerState.worker || !canReuseWorker(workerState.worker))) {
1502
1523
  await Promise.race([
1503
1524
  workerState.exitPromise,
1504
1525
  new Promise(function(resolve) { setTimeout(resolve, 3000); }),
@@ -1506,8 +1527,7 @@ function createClaudeAdapter(opts) {
1506
1527
  }
1507
1528
 
1508
1529
  // Reuse existing worker if alive
1509
- if (workerState && workerState.worker && workerState.worker.ready &&
1510
- workerState.worker.process && !workerState.worker.process.killed) {
1530
+ if (workerState && canReuseWorker(workerState.worker)) {
1511
1531
  worker = workerState.worker;
1512
1532
  reusingWorker = true;
1513
1533
  // Clear old message handlers so they don't fire for the new query
@@ -1564,7 +1584,7 @@ function createClaudeAdapter(opts) {
1564
1584
  // will push the initial message and the worker receives it via push_message.
1565
1585
  // Instead, we send query_start with no prompt; the worker starts a query with
1566
1586
  // the message queue, and the first push_message will arrive.
1567
- worker.send({
1587
+ if (!worker.send({
1568
1588
  type: "query_start",
1569
1589
  prompt: null,
1570
1590
  options: queryOptions,
@@ -1572,7 +1592,9 @@ function createClaudeAdapter(opts) {
1572
1592
  originalHome: claudeOpts.originalHome || null,
1573
1593
  projectPath: claudeOpts.projectPath || null,
1574
1594
  _perfT0: claudeOpts._perfT0 || Date.now(),
1575
- });
1595
+ })) {
1596
+ throw new Error("Claude worker IPC connection closed before query start");
1597
+ }
1576
1598
 
1577
1599
  return handle;
1578
1600
  }
@@ -1808,6 +1830,10 @@ module.exports = {
1808
1830
  contractTestKit: {
1809
1831
  createMessageQueue: createMessageQueue,
1810
1832
  createQueryHandle: createQueryHandle,
1833
+ createWorkerQueryHandle: createWorkerQueryHandle,
1834
+ hasWritableWorkerConnection: hasWritableWorkerConnection,
1835
+ sendWorkerMessage: sendWorkerMessage,
1836
+ canReuseWorker: canReuseWorker,
1811
1837
  normalizeEvent: flattenEvent,
1812
1838
  },
1813
1839
  };
package/lib/yoke/index.js CHANGED
@@ -8,6 +8,23 @@ var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
8
8
  var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
9
9
  var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
10
10
 
11
+ // Keep the first session in a new project predictable. This order is also
12
+ // used by the UI when a project has no remembered vendor yet.
13
+ var DEFAULT_VENDOR_ORDER = ["claude", "codex", "kiro"];
14
+
15
+ function resolveDefaultVendor(availableVendors) {
16
+ availableVendors = availableVendors || {};
17
+ for (var i = 0; i < DEFAULT_VENDOR_ORDER.length; i++) {
18
+ var vendor = DEFAULT_VENDOR_ORDER[i];
19
+ if (Array.isArray(availableVendors)) {
20
+ if (availableVendors.indexOf(vendor) !== -1) return vendor;
21
+ } else if (availableVendors[vendor]) {
22
+ return vendor;
23
+ }
24
+ }
25
+ return "claude";
26
+ }
27
+
11
28
  /**
12
29
  * Wrap adapter.createQuery to inject cross-vendor project instructions.
13
30
  *
@@ -371,6 +388,8 @@ async function lazyCreateAdapter(adapters, vendor, opts) {
371
388
  module.exports = {
372
389
  createAdapter: createAdapter,
373
390
  createAdapters: createAdapters,
391
+ resolveDefaultVendor: resolveDefaultVendor,
392
+ DEFAULT_VENDOR_ORDER: DEFAULT_VENDOR_ORDER,
374
393
  lazyCreateAdapter: lazyCreateAdapter,
375
394
  checkAuth: checkAuth,
376
395
  checkInstalled: checkInstalled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.3.0-beta.1",
3
+ "version": "3.3.0",
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",