clay-server 4.1.0-beta.9 → 4.1.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.
Files changed (111) hide show
  1. package/lib/context-usage.js +47 -0
  2. package/lib/issues-mcp-server.js +1 -1
  3. package/lib/issues-service.js +3 -1
  4. package/lib/loop-registry-occurrence.js +1 -0
  5. package/lib/notes.js +20 -3
  6. package/lib/os-users.js +6 -0
  7. package/lib/project-connection.js +14 -3
  8. package/lib/project-file-path.js +13 -0
  9. package/lib/project-file-watch.js +19 -8
  10. package/lib/project-filesystem.js +10 -16
  11. package/lib/project-http.js +53 -48
  12. package/lib/project-logs-mcp-server.js +8 -8
  13. package/lib/project-logs-schema.js +1 -1
  14. package/lib/project-logs-service.js +46 -13
  15. package/lib/project-logs-store.js +17 -15
  16. package/lib/project-logs.js +70 -0
  17. package/lib/project-loop.js +1 -0
  18. package/lib/project-notifications.js +1 -1
  19. package/lib/project-scheduled-result-store.js +131 -0
  20. package/lib/project-scheduled-task-execution.js +35 -2
  21. package/lib/project-session-notes.js +11 -4
  22. package/lib/project-sessions.js +7 -0
  23. package/lib/project-user-message.js +47 -0
  24. package/lib/project.js +53 -3
  25. package/lib/public/app.js +26 -9
  26. package/lib/public/css/avatar-imprints.css +1 -1
  27. package/lib/public/css/command-palette.css +20 -0
  28. package/lib/public/css/git-panel.css +13 -12
  29. package/lib/public/css/git-placard.css +4 -4
  30. package/lib/public/css/issues.css +78 -31
  31. package/lib/public/css/mobile-nav.css +7 -0
  32. package/lib/public/css/notifications-center.css +3 -3
  33. package/lib/public/css/project-logs.css +15 -0
  34. package/lib/public/css/sidebar.css +7 -0
  35. package/lib/public/css/sticky-notes.css +29 -0
  36. package/lib/public/modules/app-connection.js +9 -1
  37. package/lib/public/modules/app-favicon.js +10 -0
  38. package/lib/public/modules/app-messages.js +43 -12
  39. package/lib/public/modules/app-notifications.js +2 -0
  40. package/lib/public/modules/app-panels.js +77 -23
  41. package/lib/public/modules/app-projects.js +2 -0
  42. package/lib/public/modules/app-rate-limit.js +148 -61
  43. package/lib/public/modules/avatar-imprint.js +25 -70
  44. package/lib/public/modules/avatar.js +12 -4
  45. package/lib/public/modules/chat-render-runtime.js +20 -0
  46. package/lib/public/modules/clay-issue-links.js +23 -2
  47. package/lib/public/modules/clay-log-links.js +11 -1
  48. package/lib/public/modules/context-view-preference.js +143 -0
  49. package/lib/public/modules/default-vendor.js +116 -0
  50. package/lib/public/modules/filebrowser-tabs.js +1 -0
  51. package/lib/public/modules/filebrowser.js +45 -18
  52. package/lib/public/modules/issues-render.js +36 -10
  53. package/lib/public/modules/issues.js +95 -13
  54. package/lib/public/modules/markdown.js +3 -1
  55. package/lib/public/modules/pane-bridge.js +14 -8
  56. package/lib/public/modules/pane-issue-bridge.js +27 -0
  57. package/lib/public/modules/permission-hints.js +13 -0
  58. package/lib/public/modules/profile.js +9 -9
  59. package/lib/public/modules/project-activation.js +49 -0
  60. package/lib/public/modules/project-logs-render.js +13 -3
  61. package/lib/public/modules/project-logs.js +45 -15
  62. package/lib/public/modules/right-workbench.js +44 -0
  63. package/lib/public/modules/scheduled-result-client.js +33 -0
  64. package/lib/public/modules/scheduled-result-display.js +50 -0
  65. package/lib/public/modules/scheduled-result-notification.js +31 -0
  66. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  67. package/lib/public/modules/scheduled-tasks.js +9 -21
  68. package/lib/public/modules/sidebar-mobile.js +51 -3
  69. package/lib/public/modules/sidebar-sessions.js +65 -5
  70. package/lib/public/modules/split-session-boundary.js +28 -0
  71. package/lib/public/modules/split-view.js +42 -7
  72. package/lib/public/modules/sticky-notes-browser.js +130 -18
  73. package/lib/public/modules/sticky-notes.js +2 -3
  74. package/lib/public/modules/terminal.js +4 -4
  75. package/lib/public/modules/thinking-lifecycle.js +4 -1
  76. package/lib/public/modules/tools.js +5 -1
  77. package/lib/scheduled-run-state.js +10 -2
  78. package/lib/scheduler.js +1 -0
  79. package/lib/sdk-bridge.js +97 -61
  80. package/lib/sdk-message-processor.js +44 -13
  81. package/lib/sdk-skill-discovery.js +6 -28
  82. package/lib/sdk-turn-queue.js +14 -0
  83. package/lib/server-context-view.js +53 -0
  84. package/lib/server-default-vendor.js +60 -0
  85. package/lib/server.js +23 -6
  86. package/lib/session-notes-mcp-server.js +3 -5
  87. package/lib/session-pair-factory.js +2 -2
  88. package/lib/session-title-generator.js +89 -0
  89. package/lib/session-title-policy.js +8 -2
  90. package/lib/sessions.js +26 -1
  91. package/lib/user-presence.js +6 -0
  92. package/lib/users-context-view-preferences.js +65 -0
  93. package/lib/users-default-vendor-preferences.js +80 -0
  94. package/lib/users.js +12 -0
  95. package/lib/ws-schema.js +11 -0
  96. package/lib/yoke/acp-agent-profiles.js +15 -2
  97. package/lib/yoke/adapters/acp.js +3 -2
  98. package/lib/yoke/adapters/antigravity.js +2 -2
  99. package/lib/yoke/adapters/claude-worker.js +11 -12
  100. package/lib/yoke/adapters/claude.js +75 -22
  101. package/lib/yoke/adapters/codex.js +216 -29
  102. package/lib/yoke/adapters/kiro.js +6 -5
  103. package/lib/yoke/claude-context-usage.js +57 -0
  104. package/lib/yoke/claude-message-correlation.js +31 -0
  105. package/lib/yoke/claude-model-switch.js +15 -0
  106. package/lib/yoke/index.js +5 -1
  107. package/lib/yoke/mcp-bridge-server.js +18 -5
  108. package/lib/yoke/session-skill-catalog.js +45 -0
  109. package/lib/yoke/skill-discovery.js +60 -20
  110. package/lib/yoke/vendor-registry.js +1 -1
  111. package/package.json +5 -7
@@ -29,7 +29,7 @@ var CODEX_REASONING_DEFAULTS = {
29
29
  model_supports_reasoning_summaries: true,
30
30
  };
31
31
  var DEFAULT_CODEX_MODEL = "gpt-6-astra";
32
- var CODEX_QUERY_BOUND_TOOL_CAPABILITY = { mode: "dynamic", resume: "persisted-catalog-only" };
32
+ var CODEX_QUERY_BOUND_TOOL_CAPABILITY = { mode: "dynamic", resume: "session-mcp-recovery" };
33
33
  var CODEX_NOTE_TOOLS = {
34
34
  list_notes: true,
35
35
  write_note: true,
@@ -181,6 +181,11 @@ function flattenEvent(notification, state) {
181
181
 
182
182
  if (method === "turn/started") {
183
183
  state.turnStarted = true;
184
+ state.currentTurnId = params.turnId || params.turn_id || (params.turn && params.turn.id) || null;
185
+ state.lastInputTokens = null;
186
+ state.lastCachedTokens = null;
187
+ state.lastOutputTokens = null;
188
+ state.currentContextUsage = null;
184
189
  var userUuid = generateUuid();
185
190
  events.push({ yokeType: "turn_start", uuid: userUuid, messageType: "user" });
186
191
  return events;
@@ -194,16 +199,38 @@ function flattenEvent(notification, state) {
194
199
  if (turnStatus === "interrupted" || state.aborted) {
195
200
  events.push({ yokeType: "interrupted" });
196
201
  }
197
- var inputTokens = state.lastInputTokens !== null && state.lastInputTokens !== undefined
198
- ? state.lastInputTokens : (usage ? (usage.input_tokens || 0) : 0);
199
- var outputTokens = (usage ? (usage.output_tokens || 0) : 0) || state.lastOutputTokens || 0;
200
- var cachedTokens = (usage ? (usage.cached_input_tokens || 0) : 0) || state.lastCachedTokens || 0;
202
+ // Generic usage reporting is independent from current context occupancy.
203
+ // The latter is supplied only by a valid thread/tokenUsage snapshot.
204
+ // Some normal protocol completions omit usage even though the preceding
205
+ // token snapshot carried useful per-turn accounting. Keep that fallback
206
+ // for the generic usage panel, but never use it as context occupancy.
207
+ var inputTokens = usage && typeof usage.input_tokens === "number"
208
+ ? usage.input_tokens : (typeof state.lastInputTokens === "number" ? state.lastInputTokens : 0);
209
+ var outputTokens = usage && typeof usage.output_tokens === "number"
210
+ ? usage.output_tokens : (typeof state.lastOutputTokens === "number" ? state.lastOutputTokens : 0);
211
+ var cachedTokens = usage && typeof usage.cached_input_tokens === "number"
212
+ ? usage.cached_input_tokens : (typeof state.lastCachedTokens === "number" ? state.lastCachedTokens : 0);
201
213
  var hasTokenData = inputTokens > 0 || outputTokens > 0;
202
214
  var resultModelUsage = {};
203
215
  // Codex reports the real window in thread/tokenUsage/updated. Dropping it
204
216
  // made the client fall back to its model-name table, which has no entry
205
217
  // for codex model ids and defaulted to Claude's 200k denominator.
206
- resultModelUsage[state.model] = { contextWindow: state.modelContextWindow || null };
218
+ var contextSnapshot = { valid: false };
219
+ if (state.currentContextUsage
220
+ && typeof state.currentContextUsage.input_tokens === "number"
221
+ && typeof state.currentContextUsage.contextWindow === "number"
222
+ && state.currentContextUsage.contextWindow > 0) {
223
+ contextSnapshot = {
224
+ valid: true,
225
+ inputTokens: state.currentContextUsage.input_tokens,
226
+ contextWindow: state.currentContextUsage.contextWindow,
227
+ };
228
+ if (state.currentContextUsage.turnId) contextSnapshot.turnId = state.currentContextUsage.turnId;
229
+ }
230
+ resultModelUsage[state.model] = {
231
+ contextWindow: state.modelContextWindow || null,
232
+ contextSnapshot: contextSnapshot,
233
+ };
207
234
  var assistantUuid = generateUuid();
208
235
  events.push({
209
236
  yokeType: "result",
@@ -683,24 +710,36 @@ function flattenEvent(notification, state) {
683
710
  if (method === "thread/tokenUsage/updated") {
684
711
  var tu = params.tokenUsage;
685
712
  if (tu) {
713
+ var tokenTurnId = params.turnId || params.turn_id || tu.turnId || null;
714
+ if (tokenTurnId && state.currentTurnId && tokenTurnId !== state.currentTurnId) {
715
+ state.lastInputTokens = null;
716
+ state.lastCachedTokens = null;
717
+ state.lastOutputTokens = null;
718
+ state.currentContextUsage = null;
719
+ return events;
720
+ }
721
+ var currentWindow = null;
686
722
  var turnUsage = tu.lastTurn;
687
723
  if (turnUsage) {
688
724
  var candidateInput = typeof turnUsage.inputTokens === "number" ? turnUsage.inputTokens : null;
689
- var currentWindow = typeof tu.modelContextWindow === "number" ? tu.modelContextWindow : null;
690
- state.lastInputTokens = candidateInput !== null && (currentWindow === null || candidateInput <= currentWindow) ? candidateInput : null;
725
+ currentWindow = typeof tu.modelContextWindow === "number" && isFinite(tu.modelContextWindow) && tu.modelContextWindow > 0
726
+ ? tu.modelContextWindow : null;
727
+ state.lastInputTokens = candidateInput !== null && isFinite(candidateInput) && candidateInput >= 0 && currentWindow !== null && candidateInput <= currentWindow ? candidateInput : null;
691
728
  state.lastCachedTokens = turnUsage.cachedInputTokens || 0;
692
729
  state.lastOutputTokens = turnUsage.outputTokens || 0;
693
- state.currentContextUsage = state.lastInputTokens !== null &&
694
- (currentWindow === null || state.lastInputTokens <= currentWindow)
695
- ? { input_tokens: state.lastInputTokens, contextWindow: currentWindow }
696
- : { input_tokens: null, contextWindow: currentWindow };
730
+ if (state.lastInputTokens !== null && currentWindow !== null) {
731
+ state.currentContextUsage = { input_tokens: state.lastInputTokens, contextWindow: currentWindow };
732
+ if (tokenTurnId || state.currentTurnId) state.currentContextUsage.turnId = tokenTurnId || state.currentTurnId;
733
+ } else {
734
+ state.currentContextUsage = null;
735
+ }
697
736
  } else {
698
737
  state.lastInputTokens = null;
699
738
  state.lastCachedTokens = null;
700
739
  state.lastOutputTokens = null;
701
740
  state.currentContextUsage = null;
702
741
  }
703
- if (tu.modelContextWindow) state.modelContextWindow = tu.modelContextWindow;
742
+ if (currentWindow !== null) state.modelContextWindow = currentWindow;
704
743
  }
705
744
  return events;
706
745
  }
@@ -750,6 +789,7 @@ function createEventState(model) {
750
789
  lastCachedTokens: null,
751
790
  lastOutputTokens: null,
752
791
  currentContextUsage: null,
792
+ currentTurnId: null,
753
793
  modelContextWindow: null,
754
794
  done: false,
755
795
  aborted: false,
@@ -1123,16 +1163,25 @@ function createCodexQueryHandle(appServer, queryOpts) {
1123
1163
 
1124
1164
  var threadResult;
1125
1165
  if (queryOpts.resumeSessionId) {
1126
- // ThreadResumeParams in the installed app-server schema has no
1127
- // dynamicTools field. New tools therefore require a new Driver/Worker
1128
- // thread; resumed threads can only refresh handlers for persisted tools.
1129
- threadResult = await appServer.send("thread/resume", {
1166
+ // ThreadResumeParams has no dynamicTools field, but its supported
1167
+ // config override can start an exact-query MCP bridge without making
1168
+ // a replacement thread or changing the saved conversation history.
1169
+ var resumeParams = {
1130
1170
  threadId: queryOpts.resumeSessionId,
1131
1171
  model: threadParams.model,
1132
1172
  sandbox: threadParams.sandbox,
1133
1173
  approvalPolicy: threadParams.approvalPolicy,
1134
1174
  cwd: threadParams.cwd,
1135
- }, 60000);
1175
+ };
1176
+ if (queryOpts.sessionMcpServer && !queryOpts.isolatedRecovery) {
1177
+ resumeParams.config = { mcp_servers: {} };
1178
+ resumeParams.config.mcp_servers[queryOpts.sessionMcpServer.name] = {
1179
+ command: queryOpts.sessionMcpServer.command,
1180
+ args: queryOpts.sessionMcpServer.args || [],
1181
+ env: queryOpts.sessionMcpServer.env || {},
1182
+ };
1183
+ }
1184
+ threadResult = await appServer.send("thread/resume", resumeParams, 60000);
1136
1185
  } else {
1137
1186
  threadResult = await appServer.send("thread/start", threadParams, 60000);
1138
1187
  }
@@ -1173,7 +1222,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1173
1222
 
1174
1223
  // Explicit references use Codex's native skill input item regardless of
1175
1224
  // which vendor owns the SKILL.md file.
1176
- var availableSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd);
1225
+ var availableSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd, queryOpts.skillOptions || { env: queryOpts.env });
1177
1226
  var skillItemsToInject = [];
1178
1227
  var injected = {};
1179
1228
  for (var ii = 0; ii < input.length; ii++) {
@@ -1288,7 +1337,10 @@ function createCodexQueryHandle(appServer, queryOpts) {
1288
1337
  },
1289
1338
 
1290
1339
  getContextUsage: function() {
1291
- return Promise.resolve(state.currentContextUsage);
1340
+ return Promise.resolve(state.currentContextUsage || {
1341
+ unavailable: true,
1342
+ reason: "current_context_unavailable",
1343
+ });
1292
1344
  },
1293
1345
 
1294
1346
  abort: function() {
@@ -1384,11 +1436,25 @@ function createCodexAdapter(opts) {
1384
1436
  for (var i = 0; i < listeners.length; i++) listeners[i]();
1385
1437
  }
1386
1438
  var _initPromise = null;
1439
+ var _initializing = false;
1387
1440
  var _shutdownPromise = null;
1388
1441
  var _refCount = 0;
1389
1442
  var _lastActiveAt = Date.now();
1390
1443
  var _shuttingDown = false;
1391
1444
  var _activeQueries = [];
1445
+ var _recoveryHandoff = null;
1446
+ var _recoveryRuntimes = [];
1447
+ var _recoveryCleanup = Promise.resolve();
1448
+ var _recoveryCleanupTasks = [];
1449
+
1450
+ function scopedSkillOptions(env) {
1451
+ if (!_runtimeLinuxUser) return { env: env };
1452
+ try {
1453
+ return Object.assign(skillDiscovery.optionsForOsUser(_resolveOsUserInfo(_runtimeLinuxUser)), { env: env });
1454
+ } catch (e) {
1455
+ return { disable: true };
1456
+ }
1457
+ }
1392
1458
 
1393
1459
  function withoutLinuxUser(callOpts) {
1394
1460
  var cleaned = Object.assign({}, callOpts || {});
@@ -1455,6 +1521,95 @@ function createCodexAdapter(opts) {
1455
1521
  updateLastActiveAt();
1456
1522
  }
1457
1523
 
1524
+ function waitForRecoveryHandoff() {
1525
+ return _recoveryHandoff || Promise.resolve();
1526
+ }
1527
+
1528
+ function retainRecoveryRuntime(runtime) {
1529
+ _recoveryRuntimes.push(runtime);
1530
+ }
1531
+
1532
+ function removeRecoveryRuntime(runtime) {
1533
+ var next = [];
1534
+ for (var i = 0; i < _recoveryRuntimes.length; i++) {
1535
+ if (_recoveryRuntimes[i] !== runtime) next.push(_recoveryRuntimes[i]);
1536
+ }
1537
+ _recoveryRuntimes = next;
1538
+ }
1539
+
1540
+ function cleanupRecoveryRuntime(runtime) {
1541
+ for (var i = 0; i < _recoveryCleanupTasks.length; i++) {
1542
+ if (_recoveryCleanupTasks[i].runtime === runtime) return _recoveryCleanupTasks[i].promise;
1543
+ }
1544
+ var task = { runtime: runtime, promise: null };
1545
+ _recoveryCleanupTasks.push(task);
1546
+ var priorCleanup = _recoveryCleanup;
1547
+ task.promise = Promise.resolve(priorCleanup).catch(function() {
1548
+ return null;
1549
+ }).then(function() {
1550
+ return runtime.shutdown();
1551
+ }).catch(function(error) {
1552
+ console.error("[yoke/codex] Isolated recovery runtime shutdown failed:", error.message || error);
1553
+ }).then(function() {
1554
+ removeRecoveryRuntime(runtime);
1555
+ var remainingTasks = [];
1556
+ for (var ti = 0; ti < _recoveryCleanupTasks.length; ti++) {
1557
+ if (_recoveryCleanupTasks[ti] !== task) remainingTasks.push(_recoveryCleanupTasks[ti]);
1558
+ }
1559
+ _recoveryCleanupTasks = remainingTasks;
1560
+ });
1561
+ _recoveryCleanup = task.promise;
1562
+ return task.promise;
1563
+ }
1564
+
1565
+ function shutdownRecoveryRuntimes() {
1566
+ var runtimes = _recoveryRuntimes.slice();
1567
+ if (!runtimes.length) return _recoveryCleanup;
1568
+ return Promise.all(runtimes.map(function(runtime) {
1569
+ return cleanupRecoveryRuntime(runtime);
1570
+ })).then(function() {
1571
+ _recoveryRuntimes = [];
1572
+ });
1573
+ }
1574
+
1575
+ async function createIsolatedRecoveryQuery(queryOpts) {
1576
+ var releaseHandoff;
1577
+ var priorHandoff = _recoveryHandoff;
1578
+ var myHandoff = new Promise(function(resolve) { releaseHandoff = resolve; });
1579
+ _recoveryHandoff = myHandoff;
1580
+ await (priorHandoff || Promise.resolve());
1581
+ try {
1582
+ await _recoveryCleanup;
1583
+ if (_refCount > 0 || _initializing) {
1584
+ throw new Error("Codex resumed-tool recovery is waiting for an active shared runtime; retry after its current queries finish.");
1585
+ }
1586
+ if (_appServer) {
1587
+ var stopped = await adapter.shutdownIfIdle(0);
1588
+ if (!stopped) throw new Error("Codex resumed-tool recovery could not safely release the shared runtime; retry after its current queries finish.");
1589
+ }
1590
+ var recoveryRuntime = createCodexAdapter(Object.assign({}, _defaultInitOpts, {
1591
+ recoveryRuntime: true,
1592
+ recoverySessionMcpServer: queryOpts.sessionMcpServer,
1593
+ }));
1594
+ retainRecoveryRuntime(recoveryRuntime);
1595
+ var recoveryOpts = Object.assign({}, queryOpts, {
1596
+ isolatedRecovery: true,
1597
+ onRuntimeFinished: function() {
1598
+ return cleanupRecoveryRuntime(recoveryRuntime);
1599
+ },
1600
+ });
1601
+ try {
1602
+ return await recoveryRuntime.createQuery(recoveryOpts);
1603
+ } catch (error) {
1604
+ await cleanupRecoveryRuntime(recoveryRuntime);
1605
+ throw error;
1606
+ }
1607
+ } finally {
1608
+ releaseHandoff();
1609
+ if (_recoveryHandoff === myHandoff) _recoveryHandoff = null;
1610
+ }
1611
+ }
1612
+
1458
1613
  function buildReadyResponse(skillNames) {
1459
1614
  return {
1460
1615
  models: _cachedModels,
@@ -1487,6 +1642,7 @@ function createCodexAdapter(opts) {
1487
1642
  function clearRuntimeState() {
1488
1643
  _appServer = null;
1489
1644
  _initPromise = null;
1645
+ _initializing = false;
1490
1646
  _cachedModels = [];
1491
1647
  _refCount = 0;
1492
1648
  _activeQueries = [];
@@ -1541,7 +1697,7 @@ function createCodexAdapter(opts) {
1541
1697
  var deadline = Date.now() + 5000;
1542
1698
  var shouldAbort = !!force;
1543
1699
 
1544
- if (_initPromise) {
1700
+ if (_initializing && _initPromise) {
1545
1701
  try {
1546
1702
  await Promise.race([
1547
1703
  _initPromise.catch(function() { return null; }),
@@ -1606,9 +1762,10 @@ function createCodexAdapter(opts) {
1606
1762
  }
1607
1763
 
1608
1764
  // Deduplicate concurrent init calls
1609
- if (_initPromise) return _initPromise;
1765
+ if (_initializing && _initPromise) return _initPromise;
1610
1766
 
1611
- _initPromise = (async function() {
1767
+ _initializing = true;
1768
+ var initPromise = (async function() {
1612
1769
  var serverOpts = { cwd: _cwd, env: effectiveInitOpts.env || null };
1613
1770
  if (_runtimeLinuxUser) {
1614
1771
  serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
@@ -1628,7 +1785,7 @@ function createCodexAdapter(opts) {
1628
1785
 
1629
1786
  // Track 1: Read local MCP server definitions from ~/.clay/mcp.json
1630
1787
  // and inject into Codex config so Codex manages them natively.
1631
- var mcpServerConfig = {};
1788
+ var mcpServerConfig = Object.assign({}, serverOpts.config.mcp_servers || {});
1632
1789
  try {
1633
1790
  var mcpLocal = require("../../mcp-local");
1634
1791
  var localMcpServers = mcpLocal.readMergedServers();
@@ -1651,6 +1808,7 @@ function createCodexAdapter(opts) {
1651
1808
  var clayPort = effectiveInitOpts.clayPort || process.env.CLAY_PORT || 2633;
1652
1809
  var clayTls = effectiveInitOpts.clayTls || false;
1653
1810
  var clayAuthToken = effectiveInitOpts.clayAuthToken || "";
1811
+ var clayBridgeToken = effectiveInitOpts.clayBridgeToken || "";
1654
1812
  var claySlug = effectiveInitOpts.slug || _slug || "";
1655
1813
  try {
1656
1814
  if (require("fs").existsSync(bridgePath)) {
@@ -1658,6 +1816,7 @@ function createCodexAdapter(opts) {
1658
1816
  if (clayTls) bridgeArgs.push("--tls");
1659
1817
  var bridgeEnv = {};
1660
1818
  if (clayAuthToken) bridgeEnv.CLAY_AUTH_TOKEN = clayAuthToken;
1819
+ if (clayBridgeToken) bridgeEnv.CLAY_BRIDGE_TOKEN = clayBridgeToken;
1661
1820
  mcpServerConfig["clay-tools"] = {
1662
1821
  command: process.execPath,
1663
1822
  args: bridgeArgs,
@@ -1668,6 +1827,17 @@ function createCodexAdapter(opts) {
1668
1827
  console.error("[codex] Failed to configure clay-tools bridge:", e.message);
1669
1828
  }
1670
1829
 
1830
+ // Recovery uses a dedicated app-server so the query-local bridge can
1831
+ // coexist with every server already configured for this runtime.
1832
+ var recoveryMcpServer = effectiveInitOpts.recoverySessionMcpServer;
1833
+ if (recoveryMcpServer && recoveryMcpServer.name && recoveryMcpServer.command) {
1834
+ mcpServerConfig[recoveryMcpServer.name] = {
1835
+ command: recoveryMcpServer.command,
1836
+ args: recoveryMcpServer.args || [],
1837
+ env: recoveryMcpServer.env || {},
1838
+ };
1839
+ }
1840
+
1671
1841
  if (Object.keys(mcpServerConfig).length > 0) {
1672
1842
  serverOpts.config = Object.assign({}, serverOpts.config || {}, {
1673
1843
  mcp_servers: mcpServerConfig,
@@ -1706,7 +1876,8 @@ function createCodexAdapter(opts) {
1706
1876
  // triggering and skills/list use the same merged inventory.
1707
1877
  var skillNames = [];
1708
1878
  try {
1709
- var roots = skillDiscovery.getSkillRoots(_cwd);
1879
+ var skillOptions = scopedSkillOptions(effectiveInitOpts.env);
1880
+ var roots = skillOptions.disable ? [] : skillDiscovery.getSkillRoots(_cwd, skillOptions);
1710
1881
  var extraRootPaths = [];
1711
1882
  for (var ri = 0; ri < roots.length; ri++) {
1712
1883
  if (roots[ri].source !== "codex-user" && path.isAbsolute(roots[ri].path)) extraRootPaths.push(roots[ri].path);
@@ -1735,7 +1906,7 @@ function createCodexAdapter(opts) {
1735
1906
  }
1736
1907
  // Direct discovery remains the fallback for older Codex app-server
1737
1908
  // versions and keeps the GUI inventory vendor-neutral.
1738
- var sharedSkills = skillDiscovery.discoverSkills(_cwd);
1909
+ var sharedSkills = skillDiscovery.discoverSkills(_cwd, skillOptions);
1739
1910
  for (var csn = 0; csn < sharedSkills.length; csn++) {
1740
1911
  if (skillNames.indexOf(sharedSkills[csn].name) === -1) skillNames.push(sharedSkills[csn].name);
1741
1912
  }
@@ -1749,13 +1920,20 @@ function createCodexAdapter(opts) {
1749
1920
  throw createShutdownError();
1750
1921
  }
1751
1922
 
1752
- _initPromise = null;
1753
1923
  updateLastActiveAt();
1754
1924
 
1755
1925
  return buildReadyResponse(skillNames);
1756
1926
  })();
1757
1927
 
1758
- return _initPromise;
1928
+ _initPromise = initPromise;
1929
+ initPromise.then(function() {
1930
+ _initializing = false;
1931
+ if (_initPromise === initPromise) _initPromise = null;
1932
+ }, function() {
1933
+ _initializing = false;
1934
+ if (_initPromise === initPromise) _initPromise = null;
1935
+ });
1936
+ return initPromise;
1759
1937
  },
1760
1938
 
1761
1939
  supportedModels: function() {
@@ -1784,6 +1962,10 @@ function createCodexAdapter(opts) {
1784
1962
  if (_shuttingDown) {
1785
1963
  throw createShutdownError();
1786
1964
  }
1965
+ if (queryOpts && queryOpts.sessionMcpServer && queryOpts.resumeSessionId && !queryOpts.isolatedRecovery) {
1966
+ return createIsolatedRecoveryQuery(queryOpts);
1967
+ }
1968
+ await waitForRecoveryHandoff();
1787
1969
 
1788
1970
  if (!_appServer || !_appServer.started) {
1789
1971
  await adapter.init(queryOpts || {});
@@ -1822,8 +2004,11 @@ function createCodexAdapter(opts) {
1822
2004
  onElicitation: queryOpts.userInputMode === "fallback" ? null : (queryOpts.onElicitation || null),
1823
2005
  onUserInputRequest: queryOpts.userInputMode === "fallback" ? null : (queryOpts.onUserInputRequest || null),
1824
2006
  resumeSessionId: queryOpts.resumeSessionId || null,
2007
+ sessionMcpServer: queryOpts.sessionMcpServer || null,
2008
+ isolatedRecovery: queryOpts.isolatedRecovery === true,
1825
2009
  registerBackgroundTaskReset: registerBackgroundTaskReset,
1826
2010
  ephemeral: queryOpts.ephemeral === true,
2011
+ skillOptions: scopedSkillOptions(queryOpts.env),
1827
2012
  };
1828
2013
 
1829
2014
  // Reasoning effort
@@ -1856,6 +2041,7 @@ function createCodexAdapter(opts) {
1856
2041
  handleOpts.onFinished = function() {
1857
2042
  removeActiveQuery(activeEntry);
1858
2043
  decrementRefCount();
2044
+ if (typeof queryOpts.onRuntimeFinished === "function") queryOpts.onRuntimeFinished();
1859
2045
  };
1860
2046
  handle = createCodexQueryHandle(_appServer, handleOpts);
1861
2047
  } catch (e) {
@@ -1970,6 +2156,7 @@ function createCodexAdapter(opts) {
1970
2156
  return Promise.all([
1971
2157
  beginShutdown(true),
1972
2158
  shutdownUserRuntimes("shutdown"),
2159
+ shutdownRecoveryRuntimes(),
1973
2160
  ]).then(function() { return true; });
1974
2161
  },
1975
2162
 
@@ -1997,7 +2184,7 @@ function createCodexAdapter(opts) {
1997
2184
  });
1998
2185
  }
1999
2186
  if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
2000
- if (_initPromise) return Promise.resolve(false);
2187
+ if (_initializing) return Promise.resolve(false);
2001
2188
  if (!_appServer) return Promise.resolve(false);
2002
2189
  if (_refCount > 0) return Promise.resolve(false);
2003
2190
  if (Date.now() - _lastActiveAt < (idleMs || 0)) return Promise.resolve(false);
@@ -18,7 +18,7 @@ var INITIALIZE_TIMEOUT_MS = require("../interface").INITIALIZE_TIMEOUT_MS;
18
18
  var skillDiscovery = require("../skill-discovery");
19
19
  var { KIRO_DEFAULTS } = require("../../kiro-defaults");
20
20
 
21
- function injectSharedSkillContent(message, skills) {
21
+ function injectSharedSkillContent(message, skills, readOpts) {
22
22
  var text = "";
23
23
  if (typeof message === "string") text = message;
24
24
  else if (Array.isArray(message)) {
@@ -26,7 +26,7 @@ function injectSharedSkillContent(message, skills) {
26
26
  if (message[i] && message[i].type === "text") text += "\n" + (message[i].text || "");
27
27
  }
28
28
  }
29
- var content = skillDiscovery.readReferencedSkills(text, skills, 32768);
29
+ var content = skillDiscovery.readReferencedSkills(text, skills, 32768, readOpts);
30
30
  if (!content) return message;
31
31
  var appendix = "\n\nUse the following shared skill instructions for this turn:\n" + content;
32
32
  if (typeof message === "string") return message + appendix;
@@ -682,7 +682,7 @@ function createKiroQueryHandle(acp, queryOpts) {
682
682
  while (!isCancelled()) {
683
683
  resetTurnState();
684
684
 
685
- var messageWithSkills = injectSharedSkillContent(currentMessage, queryOpts.skills || []);
685
+ var messageWithSkills = injectSharedSkillContent(currentMessage, queryOpts.skills || [], queryOpts.skillOptions);
686
686
  var input = typeof messageWithSkills === "string"
687
687
  ? [{ type: "text", text: messageWithSkills }]
688
688
  : messageWithSkills;
@@ -1012,7 +1012,7 @@ function createKiroAdapter(opts) {
1012
1012
  _defaultModel = "auto";
1013
1013
  }
1014
1014
 
1015
- var skillNames = skillDiscovery.discoverSkills(_cwd).map(function(skill) { return skill.name; });
1015
+ var skillNames = skillDiscovery.discoverSkills(_cwd, { env: (initOpts && initOpts.env) || null }).map(function(skill) { return skill.name; });
1016
1016
  console.log("[yoke/kiro] ACP initialized, models: " + _cachedModels.length + ", skills: " + skillNames.length);
1017
1017
 
1018
1018
  updateLastActiveAt();
@@ -1065,7 +1065,7 @@ function createKiroAdapter(opts) {
1065
1065
  var model = queryOpts.model || _defaultModel || "auto";
1066
1066
  var ac = queryOpts.abortController || new AbortController();
1067
1067
  var kiroOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.KIRO) || {};
1068
- var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || _cwd);
1068
+ var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || _cwd, queryOpts.skillOptions || { env: queryOpts.env || null });
1069
1069
  var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
1070
1070
 
1071
1071
  var activeEntry = { abort: function() { try { ac.abort(); } catch (e) {} } };
@@ -1079,6 +1079,7 @@ function createKiroAdapter(opts) {
1079
1079
  systemPrompt: queryOpts.systemPrompt || "",
1080
1080
  appendSystemPrompt: [queryOpts.appendSystemPrompt, skillIndex].filter(function (part) { return !!part; }).join("\n\n"),
1081
1081
  skills: sharedSkills,
1082
+ skillOptions: queryOpts.skillOptions || { env: queryOpts.env || null },
1082
1083
  abortController: ac,
1083
1084
  canUseTool: queryOpts.canUseTool || null,
1084
1085
  resumeSessionId: queryOpts.resumeSessionId || null,
@@ -0,0 +1,57 @@
1
+ var crypto = require("crypto");
2
+
3
+ function options(value) {
4
+ return { detail: value && value.detail === "full" ? "full" : "summary" };
5
+ }
6
+
7
+ function createRequests(worker) {
8
+ var pending = new Map();
9
+ return {
10
+ request: function(value) {
11
+ return new Promise(function(resolve, reject) {
12
+ var id = crypto.randomUUID();
13
+ var timer = setTimeout(function() {
14
+ pending.delete(id);
15
+ reject(new Error("Context usage request timed out"));
16
+ }, 30000);
17
+ pending.set(id, { resolve: resolve, reject: reject, timer: timer });
18
+ var accepted = false;
19
+ try { accepted = worker.send({ type: "get_context_usage", requestId: id, options: options(value) }); }
20
+ catch (error) {
21
+ clearTimeout(timer);
22
+ pending.delete(id);
23
+ reject(error);
24
+ return;
25
+ }
26
+ if (!accepted) {
27
+ clearTimeout(timer);
28
+ pending.delete(id);
29
+ resolve(null);
30
+ }
31
+ });
32
+ },
33
+ receive: function(message) {
34
+ var entry = pending.get(message.requestId);
35
+ if (!entry) return;
36
+ pending.delete(message.requestId);
37
+ clearTimeout(entry.timer);
38
+ if (message.error) entry.reject(new Error(message.error));
39
+ else entry.resolve(message.data || null);
40
+ },
41
+ close: function() {
42
+ pending.forEach(function(entry) { clearTimeout(entry.timer); entry.resolve(null); });
43
+ pending.clear();
44
+ },
45
+ };
46
+ }
47
+
48
+ async function respond(query, message, send) {
49
+ try {
50
+ var data = query && typeof query.getContextUsage === "function" ? await query.getContextUsage(options(message.options)) : null;
51
+ send({ type: "context_usage_response", requestId: message.requestId, data: data });
52
+ } catch (error) {
53
+ send({ type: "context_usage_response", requestId: message.requestId, error: error.message });
54
+ }
55
+ }
56
+
57
+ module.exports = { options: options, createRequests: createRequests, respond: respond };
@@ -0,0 +1,31 @@
1
+ // Match merged Claude replies to the messages admitted by this query.
2
+ var crypto = require("crypto");
3
+
4
+ function createCorrelation() {
5
+ var pending = new Set();
6
+ var submitted = 0;
7
+ return {
8
+ message: function(content) {
9
+ return { type: "user", uuid: crypto.randomUUID(), message: { role: "user", content: content } };
10
+ },
11
+ accepted: function(message) { pending.add(message.uuid); submitted++; },
12
+ submitted: function() { return submitted; },
13
+ result: function(raw, event) {
14
+ if (raw.type !== "result") return event;
15
+ event.submittedMessageCount = submitted;
16
+ var ids = Array.isArray(raw.user_message_uuids) ? raw.user_message_uuids : [];
17
+ if (raw.user_message_uuid) ids = ids.concat(raw.user_message_uuid);
18
+ var matched = 0;
19
+ ids.forEach(function(id) {
20
+ if (pending.delete(id)) matched++;
21
+ });
22
+ // Only count UUIDs admitted by this query, once each. Older providers
23
+ // without correlation fields retain the bridge's legacy accounting.
24
+ if (matched) event.answeredUserMessageCount = matched;
25
+ else if (!ids.length && pending.size) pending.delete(pending.values().next().value);
26
+ return event;
27
+ },
28
+ };
29
+ }
30
+
31
+ module.exports = { createCorrelation: createCorrelation };
@@ -0,0 +1,15 @@
1
+ function attach(options, notify) {
2
+ if (typeof notify !== "function") return;
3
+ var hooks = Object.assign({}, options.hooks || {});
4
+ hooks.PostModelSwitch = (hooks.PostModelSwitch || []).concat([{
5
+ hooks: [async function(input) {
6
+ if (input.from_model && input.to_model && input.from_model !== input.to_model) {
7
+ notify({ fromModel: input.from_model, toModel: input.to_model, source: input.source || "unknown" });
8
+ }
9
+ return {};
10
+ }],
11
+ }]);
12
+ options.hooks = hooks;
13
+ }
14
+
15
+ module.exports = { attach: attach };
package/lib/yoke/index.js CHANGED
@@ -379,7 +379,11 @@ function createAdapters(opts) {
379
379
 
380
380
  if (installed.codex && supportsConfiguredIsolation("codex")) {
381
381
  try {
382
- adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers });
382
+ adapters.codex = createAdapter({
383
+ vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers,
384
+ clayPort: opts.clayPort, clayTls: opts.clayTls,
385
+ clayAuthToken: opts.clayAuthToken, clayBridgeToken: opts.clayBridgeToken,
386
+ });
383
387
  auth.codex = true;
384
388
  } catch (e) {
385
389
  console.error("[yoke] Failed to create adapter for codex:", e.message);