clay-server 2.34.0-beta.1 → 2.34.0-beta.10

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 (40) hide show
  1. package/lib/ask-user-mcp-server.js +120 -0
  2. package/lib/daemon.js +97 -38
  3. package/lib/mate-datastore.js +27 -5
  4. package/lib/mates.js +2 -2
  5. package/lib/os-users.js +13 -0
  6. package/lib/project-connection.js +16 -9
  7. package/lib/project-mate-datastore.js +16 -2
  8. package/lib/project-sessions.js +110 -7
  9. package/lib/project-user-message.js +4 -3
  10. package/lib/project.js +97 -10
  11. package/lib/public/css/mates.css +94 -52
  12. package/lib/public/css/mobile-nav.css +0 -14
  13. package/lib/public/css/notifications-center.css +23 -19
  14. package/lib/public/css/sidebar.css +326 -101
  15. package/lib/public/index.html +24 -57
  16. package/lib/public/modules/app-dm.js +0 -2
  17. package/lib/public/modules/app-messages.js +3 -5
  18. package/lib/public/modules/app-rendering.js +0 -2
  19. package/lib/public/modules/diff.js +21 -7
  20. package/lib/public/modules/mate-datastore-ui.js +108 -98
  21. package/lib/public/modules/mate-sidebar.js +0 -9
  22. package/lib/public/modules/mate-wizard.js +15 -15
  23. package/lib/public/modules/sidebar-mobile.js +10 -20
  24. package/lib/public/modules/sidebar-sessions.js +490 -113
  25. package/lib/public/modules/sidebar.js +8 -6
  26. package/lib/public/modules/tools.js +58 -13
  27. package/lib/public/sw.js +1 -1
  28. package/lib/sdk-bridge.js +36 -28
  29. package/lib/sdk-message-processor.js +14 -3
  30. package/lib/server.js +28 -72
  31. package/lib/sessions.js +157 -20
  32. package/lib/ws-schema.js +2 -0
  33. package/lib/yoke/adapters/claude-worker.js +114 -2
  34. package/lib/yoke/adapters/claude.js +56 -5
  35. package/lib/yoke/adapters/codex.js +349 -58
  36. package/lib/yoke/index.js +73 -35
  37. package/lib/yoke/instructions.js +0 -1
  38. package/lib/yoke/mcp-bridge-server.js +14 -6
  39. package/package.json +1 -2
  40. package/lib/yoke/adapters/gemini.js +0 -709
@@ -89,6 +89,56 @@ function generateUuid() {
89
89
  return "codex-" + ts + "-" + cnt + "-" + rnd;
90
90
  }
91
91
 
92
+ function waitMs(ms) {
93
+ return new Promise(function(resolve) {
94
+ setTimeout(resolve, ms);
95
+ });
96
+ }
97
+
98
+ function waitForProcessExit(proc, timeoutMs) {
99
+ return new Promise(function(resolve) {
100
+ if (!proc) {
101
+ resolve(true);
102
+ return;
103
+ }
104
+
105
+ if (proc.exitCode !== null || proc.signalCode !== null) {
106
+ resolve(true);
107
+ return;
108
+ }
109
+
110
+ var done = false;
111
+ var timer = null;
112
+
113
+ function cleanup() {
114
+ if (done) return;
115
+ done = true;
116
+ if (timer) clearTimeout(timer);
117
+ proc.removeListener("exit", onDone);
118
+ proc.removeListener("close", onDone);
119
+ }
120
+
121
+ function onDone() {
122
+ cleanup();
123
+ resolve(true);
124
+ }
125
+
126
+ proc.once("exit", onDone);
127
+ proc.once("close", onDone);
128
+
129
+ timer = setTimeout(function() {
130
+ cleanup();
131
+ resolve(false);
132
+ }, timeoutMs || 5000);
133
+ });
134
+ }
135
+
136
+ function createShutdownError() {
137
+ var err = new Error("Codex adapter is shutting down, retry shortly");
138
+ err.code = "CODEX_ADAPTER_SHUTTING_DOWN";
139
+ return err;
140
+ }
141
+
92
142
  function normalizePlanStatus(status) {
93
143
  if (status === "inProgress") return "in_progress";
94
144
  if (status === "completed") return "completed";
@@ -310,16 +360,33 @@ function flattenEvent(notification, state) {
310
360
  state.thinkingBlocks[item.id] = "blk_" + state.blockCounter;
311
361
  events.push({ yokeType: "thinking_start", blockId: "blk_" + state.blockCounter });
312
362
  }
313
- if (item.text) {
363
+ // Codex reasoning items may expose plain text via `text`, a short
364
+ // `summary`, or nested `content` parts. Prefer whichever is present;
365
+ // many turns arrive with only encrypted reasoning and no readable
366
+ // text at all, in which case the UI will hide the expand affordance.
367
+ var reasoningText = "";
368
+ if (typeof item.text === "string" && item.text.length > 0) {
369
+ reasoningText = item.text;
370
+ } else if (typeof item.summary === "string" && item.summary.length > 0) {
371
+ reasoningText = item.summary;
372
+ } else if (Array.isArray(item.content)) {
373
+ var parts = [];
374
+ for (var rpi = 0; rpi < item.content.length; rpi++) {
375
+ var rp = item.content[rpi];
376
+ if (rp && typeof rp.text === "string") parts.push(rp.text);
377
+ }
378
+ reasoningText = parts.join("\n");
379
+ }
380
+ if (reasoningText) {
314
381
  var thinkBlockId = state.thinkingBlocks[item.id];
315
382
  var prevThinkLen = state.thinkingLengths[item.id] || 0;
316
- if (item.text.length > prevThinkLen) {
383
+ if (reasoningText.length > prevThinkLen) {
317
384
  events.push({
318
385
  yokeType: "thinking_delta",
319
386
  blockId: thinkBlockId,
320
- text: item.text.substring(prevThinkLen),
387
+ text: reasoningText.substring(prevThinkLen),
321
388
  });
322
- state.thinkingLengths[item.id] = item.text.length;
389
+ state.thinkingLengths[item.id] = reasoningText.length;
323
390
  }
324
391
  }
325
392
  if (evtPhase === "completed") {
@@ -503,6 +570,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
503
570
  var systemPrompt = queryOpts.systemPrompt || "";
504
571
  var canUseTool = queryOpts.canUseTool || null;
505
572
  var onElicitation = queryOpts.onElicitation || null;
573
+ var onFinished = queryOpts.onFinished || null;
506
574
 
507
575
  // Check if the query was cancelled (either via handle.abort() or direct signal abort)
508
576
  function isCancelled() {
@@ -533,6 +601,19 @@ function createCodexQueryHandle(appServer, queryOpts) {
533
601
  var eventBuffer = [];
534
602
  var eventWaiting = null;
535
603
  var iteratorDone = false;
604
+ var finishedNotified = false;
605
+
606
+ function notifyFinished() {
607
+ if (finishedNotified) return;
608
+ finishedNotified = true;
609
+ if (typeof onFinished === "function") {
610
+ try {
611
+ onFinished();
612
+ } catch (e) {
613
+ console.error("[yoke/codex] onFinished error:", e.message || e);
614
+ }
615
+ }
616
+ }
536
617
 
537
618
  function pushEvent(evt) {
538
619
  if (iteratorDone) return;
@@ -552,6 +633,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
552
633
  eventWaiting = null;
553
634
  resolve({ value: undefined, done: true });
554
635
  }
636
+ notifyFinished();
555
637
  }
556
638
 
557
639
  // Message queue for multi-turn
@@ -711,10 +793,37 @@ function createCodexQueryHandle(appServer, queryOpts) {
711
793
 
712
794
  // --- Main query loop ---
713
795
  async function runQueryLoop(initialMessage) {
714
- // Prepend system prompt (project instructions from YOKE layer) to first message
715
- var currentMessage = systemPrompt
716
- ? systemPrompt + "\n\n" + initialMessage
717
- : initialMessage;
796
+ // Prepend system prompt (project instructions from YOKE layer) to first message.
797
+ // initialMessage may be a string (text-only) or an array of content items
798
+ // (e.g. [{ type: "text", text: "..." }, ...] when images/attachments are present).
799
+ // Naive string concatenation on an array coerces it via toString(), producing
800
+ // "[object Object]" inside the prompt, so we must branch on the shape.
801
+ var currentMessage;
802
+ if (!systemPrompt) {
803
+ currentMessage = initialMessage;
804
+ } else if (typeof initialMessage === "string") {
805
+ currentMessage = systemPrompt + "\n\n" + initialMessage;
806
+ } else if (Array.isArray(initialMessage)) {
807
+ // Prepend systemPrompt to the first text item; if none exists, insert one.
808
+ var cloned = initialMessage.slice();
809
+ var injected = false;
810
+ for (var i = 0; i < cloned.length; i++) {
811
+ if (cloned[i] && cloned[i].type === "text") {
812
+ cloned[i] = {
813
+ type: "text",
814
+ text: systemPrompt + "\n\n" + (cloned[i].text || ""),
815
+ };
816
+ injected = true;
817
+ break;
818
+ }
819
+ }
820
+ if (!injected) {
821
+ cloned.unshift({ type: "text", text: systemPrompt });
822
+ }
823
+ currentMessage = cloned;
824
+ } else {
825
+ currentMessage = initialMessage;
826
+ }
718
827
 
719
828
  try {
720
829
  // Set event handler on app-server
@@ -936,48 +1045,182 @@ function createCodexQueryHandle(appServer, queryOpts) {
936
1045
 
937
1046
  function createCodexAdapter(opts) {
938
1047
  var _cwd = (opts && opts.cwd) || process.cwd();
1048
+ var _slug = (opts && opts.slug) || "";
1049
+ var _defaultInitOpts = Object.assign({}, opts || {});
939
1050
  var _cachedModels = [];
940
1051
  var _appServer = null;
941
1052
  var _initPromise = null;
942
- var _initOpts = null; // stored for query-time access
1053
+ var _shutdownPromise = null;
1054
+ var _refCount = 0;
1055
+ var _lastActiveAt = Date.now();
1056
+ var _shuttingDown = false;
1057
+ var _activeQueries = [];
1058
+
1059
+ function updateLastActiveAt() {
1060
+ _lastActiveAt = Date.now();
1061
+ }
1062
+
1063
+ function registerActiveQuery(entry) {
1064
+ _activeQueries.push(entry);
1065
+ }
1066
+
1067
+ function removeActiveQuery(entry) {
1068
+ var next = [];
1069
+ for (var i = 0; i < _activeQueries.length; i++) {
1070
+ if (_activeQueries[i] !== entry) next.push(_activeQueries[i]);
1071
+ }
1072
+ _activeQueries = next;
1073
+ }
1074
+
1075
+ function decrementRefCount() {
1076
+ if (_refCount > 0) {
1077
+ _refCount--;
1078
+ } else {
1079
+ console.error("[yoke/codex] refCount negative, bug!");
1080
+ _refCount = 0;
1081
+ }
1082
+ updateLastActiveAt();
1083
+ }
1084
+
1085
+ function buildReadyResponse(skillNames) {
1086
+ return {
1087
+ models: _cachedModels,
1088
+ defaultModel: "gpt-5.4",
1089
+ skills: skillNames || [],
1090
+ slashCommands: skillNames || [],
1091
+ fastModeState: null,
1092
+ capabilities: {
1093
+ thinking: true,
1094
+ betas: false,
1095
+ rewind: false,
1096
+ sessionResume: true,
1097
+ promptSuggestions: true,
1098
+ elicitation: true,
1099
+ fileCheckpointing: false,
1100
+ contextCompacting: false,
1101
+ toolPolicy: ["ask", "allow-all"],
1102
+ },
1103
+ };
1104
+ }
1105
+
1106
+ function clearRuntimeState() {
1107
+ _appServer = null;
1108
+ _initPromise = null;
1109
+ _cachedModels = [];
1110
+ _refCount = 0;
1111
+ _activeQueries = [];
1112
+ updateLastActiveAt();
1113
+ }
1114
+
1115
+ function waitForRefCount(targetCount, timeoutMs) {
1116
+ var deadline = Date.now() + (timeoutMs || 5000);
1117
+ return new Promise(function(resolve) {
1118
+ function tick() {
1119
+ if (_refCount <= targetCount) {
1120
+ resolve(true);
1121
+ return;
1122
+ }
1123
+ if (Date.now() >= deadline) {
1124
+ resolve(false);
1125
+ return;
1126
+ }
1127
+ setTimeout(tick, 50);
1128
+ }
1129
+ tick();
1130
+ });
1131
+ }
1132
+
1133
+ function stopAppServer(deadlineMs) {
1134
+ var proc = _appServer && _appServer.proc ? _appServer.proc : null;
1135
+ if (!_appServer) return Promise.resolve(true);
1136
+ try {
1137
+ _appServer.stop();
1138
+ } catch (e) {
1139
+ console.error("[yoke/codex] App-server stop error:", e.message || e);
1140
+ }
1141
+ if (!proc) return Promise.resolve(true);
1142
+ var remaining = (typeof deadlineMs === "number") ? Math.max(0, deadlineMs - Date.now()) : 5000;
1143
+ return waitForProcessExit(proc, remaining).then(function(exited) {
1144
+ if (!exited) {
1145
+ try {
1146
+ proc.kill("SIGKILL");
1147
+ } catch (e) {}
1148
+ }
1149
+ return exited;
1150
+ });
1151
+ }
1152
+
1153
+ function beginShutdown(force, idleMs) {
1154
+ if (_shutdownPromise) return _shutdownPromise;
1155
+ if (_shuttingDown) return null;
1156
+
1157
+ _shuttingDown = true;
1158
+
1159
+ _shutdownPromise = (async function() {
1160
+ var deadline = Date.now() + 5000;
1161
+ var shouldAbort = !!force;
1162
+
1163
+ if (_initPromise) {
1164
+ try {
1165
+ await Promise.race([
1166
+ _initPromise.catch(function() { return null; }),
1167
+ waitMs(Math.max(0, deadline - Date.now())),
1168
+ ]);
1169
+ } catch (e) {}
1170
+ }
1171
+
1172
+ if (shouldAbort && _activeQueries.length > 0) {
1173
+ var active = _activeQueries.slice();
1174
+ for (var i = 0; i < active.length; i++) {
1175
+ try {
1176
+ if (active[i] && active[i].abort) active[i].abort();
1177
+ } catch (e) {}
1178
+ }
1179
+ await waitForRefCount(0, Math.max(0, deadline - Date.now()));
1180
+ }
1181
+
1182
+ if (_appServer) {
1183
+ await stopAppServer(deadline);
1184
+ }
1185
+
1186
+ clearRuntimeState();
1187
+ _shuttingDown = false;
1188
+ _shutdownPromise = null;
1189
+ return true;
1190
+ })().catch(function(err) {
1191
+ clearRuntimeState();
1192
+ _shuttingDown = false;
1193
+ _shutdownPromise = null;
1194
+ throw err;
1195
+ });
1196
+
1197
+ return _shutdownPromise;
1198
+ }
943
1199
 
944
1200
  var adapter = {
945
1201
  vendor: "codex",
946
1202
 
947
1203
  init: function(initOpts) {
1204
+ if (_shuttingDown) {
1205
+ return Promise.reject(createShutdownError());
1206
+ }
1207
+
1208
+ var effectiveInitOpts = Object.assign({}, _defaultInitOpts, initOpts || {});
1209
+
948
1210
  // Already initialized - return cached result
949
1211
  if (_appServer && _appServer.started && _cachedModels.length > 0) {
950
- return Promise.resolve({
951
- models: _cachedModels,
952
- defaultModel: "gpt-5.4",
953
- skills: [],
954
- slashCommands: [],
955
- fastModeState: null,
956
- capabilities: {
957
- thinking: true,
958
- betas: false,
959
- rewind: false,
960
- sessionResume: true,
961
- promptSuggestions: true,
962
- elicitation: true,
963
- fileCheckpointing: false,
964
- contextCompacting: false,
965
- toolPolicy: ["ask", "allow-all"],
966
- },
967
- });
1212
+ return Promise.resolve(buildReadyResponse([]));
968
1213
  }
969
1214
 
970
1215
  // Deduplicate concurrent init calls
971
1216
  if (_initPromise) return _initPromise;
972
1217
 
973
1218
  _initPromise = (async function() {
974
- _initOpts = initOpts;
975
-
976
1219
  var serverOpts = { cwd: _cwd };
977
1220
 
978
1221
  // Extract adapter options
979
- if (initOpts && initOpts.adapterOptions && initOpts.adapterOptions.CODEX) {
980
- var co = initOpts.adapterOptions.CODEX;
1222
+ if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
1223
+ var co = effectiveInitOpts.adapterOptions.CODEX;
981
1224
  if (co.apiKey) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_API_KEY: co.apiKey });
982
1225
  if (co.baseUrl) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_BASE_URL: co.baseUrl });
983
1226
  if (co.config) serverOpts.config = co.config;
@@ -1005,10 +1248,10 @@ function createCodexAdapter(opts) {
1005
1248
 
1006
1249
  // Track 2: Add clay-tools bridge server for in-app + remote MCP tools.
1007
1250
  var bridgePath = require("path").join(__dirname, "..", "mcp-bridge-server.js");
1008
- var clayPort = (initOpts && initOpts.clayPort) || process.env.CLAY_PORT || 2633;
1009
- var clayTls = (initOpts && initOpts.clayTls) || false;
1010
- var clayAuthToken = (initOpts && initOpts.clayAuthToken) || "";
1011
- var claySlug = (initOpts && initOpts.slug) || "";
1251
+ var clayPort = effectiveInitOpts.clayPort || process.env.CLAY_PORT || 2633;
1252
+ var clayTls = effectiveInitOpts.clayTls || false;
1253
+ var clayAuthToken = effectiveInitOpts.clayAuthToken || "";
1254
+ var claySlug = effectiveInitOpts.slug || _slug || "";
1012
1255
  try {
1013
1256
  if (require("fs").existsSync(bridgePath)) {
1014
1257
  var bridgeArgs = [bridgePath, "--port", String(clayPort), "--slug", claySlug];
@@ -1049,6 +1292,11 @@ function createCodexAdapter(opts) {
1049
1292
  });
1050
1293
  _appServer.notify("initialized", {});
1051
1294
 
1295
+ if (_shuttingDown) {
1296
+ await stopAppServer(Date.now() + 1000);
1297
+ throw createShutdownError();
1298
+ }
1299
+
1052
1300
  console.log("[codex] App-server initialized, models: gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2");
1053
1301
 
1054
1302
  _cachedModels = [
@@ -1097,26 +1345,15 @@ function createCodexAdapter(opts) {
1097
1345
  console.error("[codex] Failed to discover skills:", e.message);
1098
1346
  }
1099
1347
 
1348
+ if (_shuttingDown) {
1349
+ await stopAppServer(Date.now() + 1000);
1350
+ throw createShutdownError();
1351
+ }
1352
+
1100
1353
  _initPromise = null;
1354
+ updateLastActiveAt();
1101
1355
 
1102
- return {
1103
- models: _cachedModels,
1104
- defaultModel: "gpt-5.4",
1105
- skills: skillNames,
1106
- slashCommands: skillNames,
1107
- fastModeState: null,
1108
- capabilities: {
1109
- thinking: true,
1110
- betas: false,
1111
- rewind: false,
1112
- sessionResume: true,
1113
- promptSuggestions: true,
1114
- elicitation: true,
1115
- fileCheckpointing: false,
1116
- contextCompacting: false,
1117
- toolPolicy: ["ask", "allow-all"],
1118
- },
1119
- };
1356
+ return buildReadyResponse(skillNames);
1120
1357
  })();
1121
1358
 
1122
1359
  return _initPromise;
@@ -1134,12 +1371,31 @@ function createCodexAdapter(opts) {
1134
1371
  },
1135
1372
 
1136
1373
  createQuery: async function(queryOpts) {
1374
+ if (_shuttingDown) {
1375
+ throw createShutdownError();
1376
+ }
1377
+
1378
+ if (!_appServer || !_appServer.started) {
1379
+ await adapter.init(queryOpts || {});
1380
+ }
1381
+
1382
+ if (_shuttingDown) {
1383
+ throw createShutdownError();
1384
+ }
1385
+
1137
1386
  if (!_appServer || !_appServer.started) {
1138
1387
  throw new Error("[yoke/codex] Adapter not initialized. Call init() first.");
1139
1388
  }
1140
1389
 
1141
1390
  var model = queryOpts.model || "gpt-5.4";
1142
1391
  var ac = queryOpts.abortController || new AbortController();
1392
+ var activeEntry = {
1393
+ abort: function() {
1394
+ try {
1395
+ ac.abort();
1396
+ } catch (e) {}
1397
+ },
1398
+ };
1143
1399
 
1144
1400
  // Map YOKE options to Codex thread options
1145
1401
  var codexOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.CODEX) || {};
@@ -1176,7 +1432,33 @@ function createCodexAdapter(opts) {
1176
1432
 
1177
1433
  console.log("[yoke/codex] createQuery: model=" + model + " approval=" + handleOpts.approvalPolicy + " sandbox=" + handleOpts.sandboxMode);
1178
1434
 
1179
- var handle = createCodexQueryHandle(_appServer, handleOpts);
1435
+ _refCount++;
1436
+ registerActiveQuery(activeEntry);
1437
+
1438
+ var handle;
1439
+ try {
1440
+ handleOpts.onFinished = function() {
1441
+ removeActiveQuery(activeEntry);
1442
+ decrementRefCount();
1443
+ };
1444
+ handle = createCodexQueryHandle(_appServer, handleOpts);
1445
+ } catch (e) {
1446
+ removeActiveQuery(activeEntry);
1447
+ decrementRefCount();
1448
+ throw e;
1449
+ }
1450
+
1451
+ activeEntry.handle = handle;
1452
+ activeEntry.abort = function() {
1453
+ try {
1454
+ if (handle && typeof handle.abort === "function") {
1455
+ handle.abort();
1456
+ } else {
1457
+ ac.abort();
1458
+ }
1459
+ } catch (e) {}
1460
+ };
1461
+
1180
1462
  return handle;
1181
1463
  },
1182
1464
 
@@ -1243,10 +1525,19 @@ function createCodexAdapter(opts) {
1243
1525
 
1244
1526
  // Shutdown the app-server process
1245
1527
  shutdown: function() {
1246
- if (_appServer) {
1247
- _appServer.stop();
1248
- _appServer = null;
1249
- }
1528
+ return beginShutdown(true);
1529
+ },
1530
+
1531
+ shutdownIfIdle: function(idleMs) {
1532
+ if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
1533
+ if (_initPromise) return Promise.resolve(false);
1534
+ if (!_appServer) return Promise.resolve(false);
1535
+ if (_refCount > 0) return Promise.resolve(false);
1536
+ if (Date.now() - _lastActiveAt < (idleMs || 0)) return Promise.resolve(false);
1537
+ return beginShutdown(false).then(function() {
1538
+ console.log("[yoke/codex] Reclaimed idle adapter for project " + (_slug || _cwd));
1539
+ return true;
1540
+ });
1250
1541
  },
1251
1542
  };
1252
1543