clay-server 5.2.0-beta.5 → 5.2.0-beta.6

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 (151) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-skill-install.js +1 -33
  4. package/lib/cli-sessions.js +1 -300
  5. package/lib/codex-defaults.js +1 -28
  6. package/lib/context-usage.js +3 -2
  7. package/lib/daemon.js +5 -55
  8. package/lib/default-ai-runtime.js +4 -8
  9. package/lib/email-mcp-server.js +1 -1
  10. package/lib/kiro-defaults.js +1 -22
  11. package/lib/mcp-proxy.js +2 -32
  12. package/lib/mcp-query-bridge.js +1 -1
  13. package/lib/mcp-source-adapters.js +1 -91
  14. package/lib/mcp-source-context.js +1 -42
  15. package/lib/mcp-source-discovery.js +1 -169
  16. package/lib/mcp-source-plugins.js +1 -68
  17. package/lib/mcp-source-restrictions.js +1 -47
  18. package/lib/mcp-tool-policy.js +1 -26
  19. package/lib/os-user-diagnostics.js +2 -4
  20. package/lib/os-users.js +4 -65
  21. package/lib/project-connection.js +8 -7
  22. package/lib/project-http.js +4 -4
  23. package/lib/project-mcp.js +2 -2
  24. package/lib/project-pair-result-delivery.js +2 -2
  25. package/lib/project-pair-usage.js +12 -13
  26. package/lib/project-pending-message-queue.js +3 -1
  27. package/lib/project-session-history-actions.js +5 -3
  28. package/lib/project-session-pair.js +1 -1
  29. package/lib/project-session-runtime-actions.js +2 -1
  30. package/lib/project-session-spawn.js +2 -3
  31. package/lib/project-session-vendor.js +2 -1
  32. package/lib/project-sessions.js +18 -26
  33. package/lib/project-user-message.js +1 -0
  34. package/lib/project-vendor-login.js +6 -36
  35. package/lib/public/app.js +1 -0
  36. package/lib/public/css/messages.css +6 -2
  37. package/lib/public/modules/app-favicon.js +11 -2
  38. package/lib/public/modules/app-header.js +4 -2
  39. package/lib/public/modules/app-loop-ui.js +2 -0
  40. package/lib/public/modules/app-messages.js +36 -5
  41. package/lib/public/modules/app-notifications.js +1 -2
  42. package/lib/public/modules/app-panels.js +61 -123
  43. package/lib/public/modules/app-rate-limit.js +7 -27
  44. package/lib/public/modules/chat-render-runtime.js +3 -0
  45. package/lib/public/modules/mcp-connection-view.js +1 -1
  46. package/lib/public/modules/permission-control.js +5 -4
  47. package/lib/public/modules/scheduler.js +2 -0
  48. package/lib/public/modules/session-create-form.js +10 -10
  49. package/lib/public/modules/settings-defaults.js +27 -28
  50. package/lib/public/modules/tools.js +2 -1
  51. package/lib/public/modules/tui-grab.js +1 -2
  52. package/lib/public/modules/vendor-login-browser.js +5 -3
  53. package/lib/public/modules/vendor-login.js +2 -2
  54. package/lib/public/modules/vendor-priority.js +15 -1
  55. package/lib/public/muse-avatar.svg +4 -0
  56. package/lib/safe-bash-commands.js +3 -45
  57. package/lib/sdk-bridge.js +127 -395
  58. package/lib/sdk-foreground-admission.js +16 -0
  59. package/lib/sdk-mcp-descriptors.js +2 -37
  60. package/lib/sdk-message-processor.js +50 -17
  61. package/lib/sdk-quota-snapshot.js +1 -1
  62. package/lib/sdk-response-recovery.js +1 -155
  63. package/lib/sdk-worker.js +2 -677
  64. package/lib/server.js +1 -1
  65. package/lib/session-fork-history.js +21 -0
  66. package/lib/session-hygiene.js +3 -35
  67. package/lib/session-permission-mode.js +4 -3
  68. package/lib/session-skill-scope.js +11 -0
  69. package/lib/sessions.js +17 -89
  70. package/lib/tool-llm.js +1 -1
  71. package/lib/tui-transcript-index.js +1 -132
  72. package/lib/vendor-login-browser.js +1 -11
  73. package/lib/yoke/acp-agent-profiles.js +5 -3
  74. package/lib/yoke/adapters/antigravity.js +3 -14
  75. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  76. package/lib/yoke/adapters/claude.js +7 -42
  77. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  78. package/lib/yoke/adapters/codex.js +1 -0
  79. package/lib/yoke/adapters/kiro.js +3 -1
  80. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  81. package/lib/yoke/adapters/muse.js +122 -0
  82. package/lib/yoke/auth-events.js +17 -0
  83. package/lib/yoke/claude-hook-installer.js +267 -0
  84. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  85. package/lib/yoke/claude-maintenance.js +55 -0
  86. package/lib/yoke/claude-permission-patterns.js +53 -0
  87. package/lib/yoke/claude-response-recovery.js +155 -0
  88. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  89. package/lib/yoke/clay-skill-install.js +33 -0
  90. package/lib/yoke/cli-sessions.js +300 -0
  91. package/lib/yoke/codex-app-server.js +7 -12
  92. package/lib/yoke/configuration.js +13 -0
  93. package/lib/yoke/discovery.js +70 -0
  94. package/lib/yoke/errors.js +8 -0
  95. package/lib/yoke/host-environment.js +7 -0
  96. package/lib/yoke/index.js +9 -51
  97. package/lib/yoke/interface.js +116 -0
  98. package/lib/yoke/kiro-acp-server.js +5 -5
  99. package/lib/yoke/legacy-sessions.js +89 -0
  100. package/lib/yoke/login-protocol.js +25 -0
  101. package/lib/yoke/maintenance.js +12 -0
  102. package/lib/yoke/mcp-proxy.js +32 -0
  103. package/lib/yoke/mcp-source-adapters.js +91 -0
  104. package/lib/yoke/mcp-source-context.js +42 -0
  105. package/lib/yoke/mcp-source-discovery.js +169 -0
  106. package/lib/yoke/mcp-source-plugins.js +68 -0
  107. package/lib/yoke/mcp-source-restrictions.js +47 -0
  108. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  109. package/lib/yoke/model-preferences.js +6 -0
  110. package/lib/yoke/muse-artifacts.js +36 -0
  111. package/lib/yoke/muse-controls.js +50 -0
  112. package/lib/yoke/muse-input.js +64 -0
  113. package/lib/yoke/muse-items.js +157 -0
  114. package/lib/yoke/muse-policy.js +52 -0
  115. package/lib/yoke/muse-query.js +428 -0
  116. package/lib/yoke/muse-runtime.js +211 -0
  117. package/lib/yoke/muse-services.js +79 -0
  118. package/lib/yoke/muse-skills.js +79 -0
  119. package/lib/yoke/muse-turns.js +71 -0
  120. package/lib/yoke/options/acp.js +2 -0
  121. package/lib/yoke/options/antigravity.js +2 -0
  122. package/lib/yoke/options/claude.js +21 -0
  123. package/lib/yoke/options/codex-defaults.js +29 -0
  124. package/lib/yoke/options/codex-settings.js +21 -0
  125. package/lib/yoke/options/codex.js +6 -0
  126. package/lib/yoke/options/kiro-defaults.js +22 -0
  127. package/lib/yoke/options/kiro.js +5 -0
  128. package/lib/yoke/options/muse.js +5 -0
  129. package/lib/yoke/provider-contract.js +5 -0
  130. package/lib/yoke/providers/antigravity-executable.js +8 -0
  131. package/lib/yoke/providers/claude-executable.js +40 -0
  132. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  133. package/lib/yoke/providers/claude-process.js +97 -0
  134. package/lib/yoke/providers/claude-provision.js +75 -0
  135. package/lib/yoke/providers/codex-errors.js +8 -0
  136. package/lib/yoke/providers/muse-executable.js +17 -0
  137. package/lib/yoke/provisioning.js +6 -0
  138. package/lib/yoke/query-options.js +10 -0
  139. package/lib/yoke/response-recovery.js +41 -0
  140. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  141. package/lib/yoke/sdk-server-factory.js +7 -0
  142. package/lib/yoke/session-history.js +8 -0
  143. package/lib/yoke/session-tool-transport.js +31 -13
  144. package/lib/yoke/settings.js +23 -0
  145. package/lib/yoke/source-locations.js +4 -0
  146. package/lib/yoke/title-query.js +18 -7
  147. package/lib/yoke/tui-transcript-index.js +132 -0
  148. package/lib/yoke/usage.js +48 -0
  149. package/lib/yoke/user-input.js +13 -0
  150. package/lib/yoke/vendor-registry.js +81 -1
  151. package/package.json +3 -2
package/lib/sdk-bridge.js CHANGED
@@ -1,12 +1,9 @@
1
- const crypto = require("crypto");
2
- var codexQuestionGuidance = require("./yoke/user-input-guidance").codexQuestionGuidance;
1
+ var crypto = require("crypto");
3
2
  var fs = require("fs");
4
3
  var path = require("path");
5
4
  var execSync = require("child_process").execSync;
6
5
  var execFileSync = require("child_process").execFileSync;
7
6
  var usersModule = require("./users");
8
- var { getCodexConfig } = require("./codex-defaults");
9
- var { getKiroConfig } = require("./kiro-defaults");
10
7
  var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
11
8
  var skillDiscovery = require("./yoke/skill-discovery");
12
9
  var { isSafeBashSegment } = require("./safe-bash-commands");
@@ -19,7 +16,7 @@ var sessionTitlePolicy = require("./session-title-policy");
19
16
  var sessionTitleGenerator = require("./session-title-generator");
20
17
  var loopInterview = require("./project-loop-interview");
21
18
 
22
- var extractMcpDescriptors = require("./sdk-mcp-descriptors").extractMcpDescriptors;
19
+ var extractMcpDescriptors = require("./yoke/sdk-mcp-descriptors").extractMcpDescriptors;
23
20
 
24
21
  // Call an MCP tool handler by server name and tool name.
25
22
  // Returns a promise that resolves with the tool result.
@@ -137,7 +134,7 @@ function createSDKBridge(opts) {
137
134
  }];
138
135
  }
139
136
 
140
- function getCodexSessionMcpServer(session, queryGeneration) {
137
+ function getSessionMcpServer(session, queryGeneration) {
141
138
  if (!session || !Number.isInteger(queryGeneration)) return null;
142
139
  var bridgeArgs = [
143
140
  path.join(__dirname, "yoke", "mcp-bridge-server.js"),
@@ -165,25 +162,16 @@ function createSDKBridge(opts) {
165
162
  return _cachedFreshAuthState;
166
163
  }
167
164
 
168
- function isAuthErrorMessage(errDetail) {
169
- if (!errDetail) return false;
170
- var errLower = String(errDetail).toLowerCase();
171
- return errLower.indexOf("not logged in") !== -1
172
- || errLower.indexOf("unauthenticated") !== -1
173
- || errLower.indexOf("authentication") !== -1
174
- || errLower.indexOf("sign in") !== -1
175
- || errLower.indexOf("log in") !== -1
176
- || errLower.indexOf("please login") !== -1;
177
- }
165
+ var isAuthErrorMessage = require("./yoke/auth-events").isAuthenticationError;
178
166
 
179
167
  function getLoginCommand(vendor) {
180
168
  var info = yoke.getVendorInfo(vendor);
181
- return (info && info.loginCommand) || "claude login";
169
+ return (info && info.loginCommand) || "";
182
170
  }
183
171
 
184
172
  function supportsOsUserIsolation(vendor) {
185
173
  var info = yoke.getVendorInfo(vendor);
186
- return !info || info.osUserIsolation !== false;
174
+ return !!info && info.osUserIsolation === true;
187
175
  }
188
176
 
189
177
  function notifyAuthRequired(session, title, body, authLinuxUser, canAutoLogin, loginCommand) {
@@ -390,7 +378,8 @@ function createSDKBridge(opts) {
390
378
  var sendAndRecord = msgProcessor.sendAndRecord;
391
379
  var sendToSession = msgProcessor.sendToSession;
392
380
 
393
- var responseRecovery = require("./sdk-response-recovery").attachResponseRecovery(Object.assign({
381
+ var responseRecovery = require("./yoke/response-recovery").attachResponseRecovery(Object.assign({
382
+ getProvider: function(session) { var vendor = session.vendor || sm.defaultVendor || adapter && adapter.vendor; return adapters[vendor] || vendor || adapter; },
394
383
  sessions: sm.sessions,
395
384
  changed: onProcessingChanged,
396
385
  record: sendAndRecord,
@@ -503,44 +492,7 @@ function createSDKBridge(opts) {
503
492
  // Ensures the linux user's .claude project directory exists and is writable,
504
493
  // then pre-copies CLI session file if needed. Called before starting a query
505
494
  // so the worker can resume from the correct session file.
506
- function ensureLinuxUserProjectDir(linuxUser, session) {
507
- try {
508
- var configMod = require("./config");
509
- var osUsersMod = require("./os-users");
510
- var originalHome = configMod.REAL_HOME || require("os").homedir();
511
- var linuxUserHome = osUsersMod.getLinuxUserHome(linuxUser);
512
- var uid = osUsersMod.getLinuxUserUid(linuxUser);
513
- if (originalHome !== linuxUserHome && uid != null) {
514
- var projectSlug = (cwd || "").replace(/\//g, "-");
515
- var dstDir = path.join(linuxUserHome, ".claude", "projects", projectSlug);
516
- // Create and chown the project directory once
517
- if (!fs.existsSync(dstDir)) {
518
- fs.mkdirSync(dstDir, { recursive: true });
519
- try { execFileSync("chown", ["-R", String(uid), path.join(linuxUserHome, ".claude")]); } catch (e2) {}
520
- } else {
521
- try {
522
- var dirStat = fs.statSync(dstDir);
523
- if (dirStat.uid !== uid) {
524
- execFileSync("chown", [String(uid), dstDir]);
525
- }
526
- } catch (e2) {}
527
- }
528
- // Pre-copy CLI session file so the worker can resume the conversation
529
- if (session.cliSessionId) {
530
- var sessionFileName = session.cliSessionId + ".jsonl";
531
- var srcFile = path.join(originalHome, ".claude", "projects", projectSlug, sessionFileName);
532
- var dstFile = path.join(dstDir, sessionFileName);
533
- if (fs.existsSync(srcFile) && !fs.existsSync(dstFile)) {
534
- fs.copyFileSync(srcFile, dstFile);
535
- try { execFileSync("chown", [String(uid), dstFile]); } catch (e2) {}
536
- console.log("[sdk-bridge] Pre-copied CLI session " + session.cliSessionId + " to " + linuxUser);
537
- }
538
- }
539
- }
540
- } catch (copyErr) {
541
- console.log("[sdk-bridge] Dir setup / session pre-copy skipped:", copyErr.message);
542
- }
543
- }
495
+
544
496
 
545
497
  // --- SDK query lifecycle ---
546
498
 
@@ -986,58 +938,13 @@ function createSDKBridge(opts) {
986
938
  * Only returns processes whose cwd matches our project directory.
987
939
  * Returns an array of { pid, command } for each conflicting process found.
988
940
  */
989
- function findConflictingClaude() {
990
- try {
991
- var output = execFileSync("ps", ["ax", "-o", "pid,command"], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
992
- var lines = output.trim().split("\n");
993
- var candidates = [];
994
- for (var i = 1; i < lines.length; i++) { // skip header
995
- var line = lines[i].trim();
996
- var m = line.match(/^(\d+)\s+(.+)/);
997
- if (!m) continue;
998
- var pid = parseInt(m[1], 10);
999
- var cmd = m[2];
1000
- // Skip our own process
1001
- if (pid === process.pid) continue;
1002
- // Skip node processes (our daemon, dev watchers, etc.)
1003
- if (/\bnode\b/.test(cmd.split(/\s/)[0])) continue;
1004
- // Match actual claude binary (e.g. /Users/x/.claude/local/claude, /usr/local/bin/claude)
1005
- if (/\/claude(\s|$)/.test(cmd) || /^claude(\s|$)/.test(cmd)) {
1006
- candidates.push({ pid: pid, command: cmd.substring(0, 200) });
1007
- }
1008
- }
1009
-
1010
- // Filter to only processes whose cwd matches our project
1011
- var results = [];
1012
- for (var j = 0; j < candidates.length; j++) {
1013
- var c = candidates[j];
1014
- try {
1015
- // Use /proc/<pid>/cwd symlink (always available on Linux, no lsof dependency)
1016
- var procCwd = fs.readlinkSync("/proc/" + c.pid + "/cwd");
1017
- if (procCwd === cwd) {
1018
- results.push(c);
1019
- }
1020
- } catch (e) {
1021
- // /proc read failed — include as candidate anyway (conservative)
1022
- results.push(c);
1023
- }
1024
- }
1025
- return results;
1026
- } catch (e) {
1027
- return [];
1028
- }
941
+ function findConflictingProcesses(session, error) {
942
+ var service = require("./yoke/provider-contract").resolve(getAdapterForSession(session)).processControl;
943
+ return service && typeof service.shouldInspect === "function" && service.shouldInspect(error) && typeof service.findConflicts === "function" ? service.findConflicts(cwd) : [];
1029
944
  }
1030
-
1031
- /**
1032
- * Verify that a PID is actually a claude binary process (not arbitrary).
1033
- */
1034
- function isClaudeProcess(pid) {
1035
- try {
1036
- var output = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] }).trim();
1037
- return /\/claude(\s|$)/.test(output) || /^claude(\s|$)/.test(output);
1038
- } catch (e) {
1039
- return false;
1040
- }
945
+ function isProviderProcess(pid, session) {
946
+ var service = require("./yoke/provider-contract").resolve(session ? getAdapterForSession(session) : adapter).processControl;
947
+ return !!(service && typeof service.verifyProcess === "function" && service.verifyProcess(pid));
1041
948
  }
1042
949
 
1043
950
  function ownsQueryRuntime(session, queryInstance, queryGeneration) {
@@ -1080,7 +987,8 @@ function createSDKBridge(opts) {
1080
987
  return true;
1081
988
  }
1082
989
 
1083
- async function processQueryStream(session, codexCatalogCandidate) {
990
+ async function processQueryStream(session, catalogCandidate, queryContract) {
991
+ queryContract = queryContract || yoke.sessionTools.contract(getAdapterForSession(session));
1084
992
  // Capture references at start so we only clean up OUR resources in finally,
1085
993
  // not resources from a newer query that may have been created after an abort.
1086
994
  var myQueryInstance = session.queryInstance;
@@ -1146,19 +1054,14 @@ function createSDKBridge(opts) {
1146
1054
  // permission state. Setter acknowledgement deliberately does not
1147
1055
  // change this field.
1148
1056
  recordRuntimeEffectivePermissionMode(session, myQueryInstance, myQueryGeneration, msg);
1149
- if (codexCatalogCandidate && msg && msg.yokeType === "session_started" && msg.sessionId
1057
+ if (catalogCandidate && msg && msg.yokeType === "session_started" && msg.sessionId
1150
1058
  && session.queryInstance === myQueryInstance
1151
- && Number(session._sdkQueryGeneration || 0) === codexCatalogCandidate.generation
1059
+ && Number(session._sdkQueryGeneration || 0) === catalogCandidate.generation
1152
1060
  && session.cliSessionId === msg.sessionId
1153
1061
  && (!sm.sessions || typeof sm.sessions.get !== "function" || sm.sessions.get(session.localId) === session)) {
1154
- if (codexCatalogCandidate.version) session.codexDynamicToolCatalogVersion = codexCatalogCandidate.version;
1155
- if (codexCatalogCandidate.userInputVersion) session.codexUserInputToolCatalogVersion = codexCatalogCandidate.userInputVersion;
1156
- if (codexCatalogCandidate.sharedBrowserVersion) session.codexSharedBrowserToolCatalogVersion = codexCatalogCandidate.sharedBrowserVersion;
1157
- if (codexCatalogCandidate.issuesVersion) session.codexIssuesToolCatalogVersion = codexCatalogCandidate.issuesVersion;
1158
- if (codexCatalogCandidate.youVersion) session.codexYouToolCatalogVersion = codexCatalogCandidate.youVersion;
1159
- if (codexCatalogCandidate.linearVersion) session.codexLinearToolCatalogVersion = codexCatalogCandidate.linearVersion;
1062
+ queryContract.recordCatalog(session, catalogCandidate.versions);
1160
1063
  sm.saveSessionFile(session);
1161
- codexCatalogCandidate = null;
1064
+ catalogCandidate = null;
1162
1065
  }
1163
1066
  }
1164
1067
  // (getContextUsage moved to processSDKMessage result handler -- fire-and-forget)
@@ -1176,9 +1079,7 @@ function createSDKBridge(opts) {
1176
1079
  onProcessingChanged();
1177
1080
  send({ type: "status", processing: false });
1178
1081
  sendAndRecord(session, { type: "thinking_stop" });
1179
- var interruptMsg = (session.vendor && session.vendor !== "claude")
1180
- ? "\u25a0 Conversation interrupted - tell the model what to do differently."
1181
- : "Interrupted \u00b7 What should Claude do instead?";
1082
+ var interruptMsg = (queryContract && queryContract.interruptionMessage) || "■ Conversation interrupted - tell the model what to do differently.";
1182
1083
  sendAndRecord(session, { type: "info", text: interruptMsg });
1183
1084
  sendAndRecord(session, { type: "done", code: 0 });
1184
1085
  sm.broadcastSessionList();
@@ -1213,9 +1114,7 @@ function createSDKBridge(opts) {
1213
1114
  session._lastTurnInterrupted = true;
1214
1115
  if (!session.destroying) {
1215
1116
  sendAndRecord(session, { type: "thinking_stop" });
1216
- var interruptMsg2 = (session.vendor && session.vendor !== "claude")
1217
- ? "\u25a0 Conversation interrupted - tell the model what to do differently."
1218
- : "Interrupted \u00b7 What should Claude do instead?";
1117
+ var interruptMsg2 = (queryContract && queryContract.interruptionMessage) || "■ Conversation interrupted - tell the model what to do differently.";
1219
1118
  sendAndRecord(session, { type: "info", text: interruptMsg2 });
1220
1119
  sendAndRecord(session, { type: "done", code: 0 });
1221
1120
  }
@@ -1231,21 +1130,17 @@ function createSDKBridge(opts) {
1231
1130
  console.error("[sdk-bridge] Query stream error for session " + session.localId + ":", errDetail);
1232
1131
  console.error("[sdk-bridge] Stack:", err.stack || "(no stack)");
1233
1132
 
1234
- // Check for conflicting Claude processes only on exit code 1
1235
- var isExitCode1 = err.exitCode === 1 || (err.message && err.message.indexOf("exited with code 1") !== -1);
1236
- var conflicts = isExitCode1 ? findConflictingClaude() : [];
1133
+ var conflicts = findConflictingProcesses(session, err);
1237
1134
  if (conflicts.length > 0) {
1238
- console.error("[sdk-bridge] Found " + conflicts.length + " conflicting Claude process(es):", conflicts.map(function(c) { return "PID " + c.pid; }).join(", "));
1135
+ console.error("[sdk-bridge] Found " + conflicts.length + " conflicting provider process(es):", conflicts.map(function(c) { return "PID " + c.pid; }).join(", "));
1239
1136
  sendAndRecord(session, {
1240
1137
  type: "process_conflict",
1241
- text: "Another Claude Code process is already running in this project.",
1138
+ text: "Another provider process is already running in this project.",
1242
1139
  processes: conflicts,
1243
1140
  });
1244
1141
  } else {
1245
1142
  var errLower = errDetail.toLowerCase();
1246
- var isContextOverflow = errLower.indexOf("prompt is too long") !== -1
1247
- || errLower.indexOf("context_length") !== -1
1248
- || errLower.indexOf("maximum context length") !== -1;
1143
+ var isContextOverflow = require("./yoke/errors").contextOverflow(errDetail);
1249
1144
  var isAuthError = isAuthErrorMessage(errDetail);
1250
1145
  if (isContextOverflow) {
1251
1146
  sendAndRecord(session, {
@@ -1292,7 +1187,7 @@ function createSDKBridge(opts) {
1292
1187
  // chat message already sent above
1293
1188
  }
1294
1189
  } else {
1295
- sendAndRecord(session, { type: "error", text: "Claude process error: " + err.message });
1190
+ sendAndRecord(session, { type: "error", text: "Provider process error: " + err.message });
1296
1191
  }
1297
1192
  }
1298
1193
  sendAndRecord(session, { type: "done", code: 1 });
@@ -1375,20 +1270,16 @@ function createSDKBridge(opts) {
1375
1270
  if (session.queryInstance) return { query: session.queryInstance, isTemp: false, cleanup: function() {} };
1376
1271
 
1377
1272
  var handle;
1273
+ var selectedAdapter = getAdapterForSession(session);
1378
1274
  try {
1379
- handle = await adapter.createQuery({
1275
+ handle = await selectedAdapter.createQuery({
1380
1276
  cwd: cwd,
1381
1277
  resumeSessionId: session.cliSessionId,
1382
1278
  env: getRuntimeEnv(session),
1383
- adapterOptions: {
1384
- CLAUDE: {
1385
- settingSources: ["user", "project", "local"],
1386
- enableFileCheckpointing: true,
1387
- },
1388
- },
1279
+ adapterOptions: require("./yoke/query-options").map(getAdapterForSession(session), { nativePermissionMode: "default" }),
1389
1280
  });
1390
1281
  } catch (e) {
1391
- sendAndRecord(session, { type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
1282
+ sendAndRecord(session, { type: "error", text: "Failed to load provider session: " + (e.message || e) });
1392
1283
  throw e;
1393
1284
  }
1394
1285
 
@@ -1455,7 +1346,7 @@ function createSDKBridge(opts) {
1455
1346
  // abort the whole rewind instead of leaving local history trimmed
1456
1347
  // ahead of the real, un-rolled-back provider thread.
1457
1348
  if (!turnId) {
1458
- throw new Error("No turn id recorded for this turn; cannot roll back the Codex thread.");
1349
+ throw new Error("No turn id recorded for this turn; cannot roll back the provider thread.");
1459
1350
  }
1460
1351
  await sessionAdapter.rollbackThread(session.cliSessionId, turnId, {
1461
1352
  linuxUser: getSessionLinuxUser(session) || undefined,
@@ -1471,6 +1362,8 @@ function createSDKBridge(opts) {
1471
1362
 
1472
1363
  async function forkSessionUnified(session, uuid) {
1473
1364
  var sessionAdapter = getAdapterForSession(session);
1365
+ var localHistoryEndIndex = require("./session-fork-history").endIndex(session, uuid,
1366
+ require("./yoke/provider-contract").resolve(sessionAdapter).forkHistoryBoundary);
1474
1367
  var result = await sessionAdapter.forkSession(session.cliSessionId, {
1475
1368
  upToMessageId: uuid,
1476
1369
  dir: cwd,
@@ -1479,8 +1372,10 @@ function createSDKBridge(opts) {
1479
1372
  if (!result || !result.sessionId) throw new Error("Fork returned no session id");
1480
1373
 
1481
1374
  // Adapters with rollbackThread (e.g. Codex) use local history copy
1482
- if (typeof sessionAdapter.rollbackThread === "function") {
1483
- return { sessionId: result.sessionId, useLocalHistory: true };
1375
+ if (result.useLocalHistory === true || typeof sessionAdapter.rollbackThread === "function") {
1376
+ var localFork = { sessionId: result.sessionId, useLocalHistory: true };
1377
+ if (localHistoryEndIndex !== undefined) localFork.localHistoryEndIndex = localHistoryEndIndex;
1378
+ return localFork;
1484
1379
  }
1485
1380
  // Claude: read history from CLI session files
1486
1381
  return { sessionId: result.sessionId, useLocalHistory: false };
@@ -1502,9 +1397,8 @@ function createSDKBridge(opts) {
1502
1397
  }
1503
1398
  if (responseRecovery.stopping(session) && !retryState) return false;
1504
1399
  delete session._lastTurnInterrupted;
1505
- var claude = (session.vendor || adapter.vendor) === "claude";
1506
1400
  var payload = { text: text, images: images, linuxUser: linuxUser, beforePush: beforePush };
1507
- var recoveryState = claude ? responseRecovery.begin(session, payload, retryState) : null;
1401
+ var recoveryState = responseRecovery.begin(session, payload, retryState);
1508
1402
  if (recoveryState) recoveryState.handle = null;
1509
1403
  var guardedPush = function() {
1510
1404
  return !session.destroying && !session.taskStopRequested
@@ -1548,7 +1442,7 @@ function createSDKBridge(opts) {
1548
1442
  return false;
1549
1443
  } finally {
1550
1444
  session._queryStarting = false;
1551
- delete session._codexRecoveryWaiting;
1445
+ delete session._providerRecoveryWaiting;
1552
1446
  if (!session.queryInstance && session.pendingPush && session.pendingPush.length) session.pendingPush = [];
1553
1447
  if (!session.queryInstance && !session.isProcessing && !session._pendingMessageDrainPaused && !responseRecovery.stopping(session)) session.taskStopRequested = false;
1554
1448
  if (!responseRecovery.stopping(session) && !session.isProcessing && typeof opts.onSessionAvailable === "function") opts.onSessionAvailable(session);
@@ -1747,7 +1641,8 @@ function createSDKBridge(opts) {
1747
1641
 
1748
1642
  // Ensure Linux user project directory exists (runs in parallel with worker boot)
1749
1643
  if (linuxUser) {
1750
- ensureLinuxUserProjectDir(linuxUser, session);
1644
+ var preparation = require("./yoke/provider-contract").resolve(sessionAdapter).prepareIsolation;
1645
+ if (typeof preparation === "function") preparation(linuxUser, session, cwd);
1751
1646
  }
1752
1647
 
1753
1648
  session.blocks = {};
@@ -1764,100 +1659,27 @@ function createSDKBridge(opts) {
1764
1659
  session.abortController = new AbortController();
1765
1660
  }
1766
1661
 
1767
- // Build Claude-specific adapter options
1768
- var claudeOpts = {
1769
- settingSources: ["user", "project", "local"],
1770
- includePartialMessages: true,
1771
- enableFileCheckpointing: true,
1772
- extraArgs: { "replay-user-messages": null },
1773
- promptSuggestions: true,
1774
- agentProgressSummaries: true,
1775
- };
1776
-
1777
- // Per-loop settings override global defaults when present
1778
1662
  var ls = session.loopSettings || {};
1779
-
1780
- if (sm.currentBetas && sm.currentBetas.length > 0) {
1781
- claudeOpts.betas = sm.currentBetas;
1782
- }
1783
- var thinkingMode = ls.thinking || sm.currentThinking;
1784
- if (thinkingMode === "disabled") {
1785
- claudeOpts.thinking = { type: "disabled" };
1786
- } else if (thinkingMode === "budget") {
1787
- var budgetTokens = ls.thinkingBudget || sm.currentThinkingBudget;
1788
- if (budgetTokens) claudeOpts.thinking = { type: "enabled", budgetTokens: budgetTokens };
1789
- } else {
1790
- // Adaptive (the default). Request summarized display explicitly:
1791
- // current models default thinking.display to "omitted", which streams
1792
- // thinking blocks whose text is an EMPTY string -- the UI's thinking
1793
- // section stays blank even though thinking happens and is billed.
1794
- // (SDK ThinkingAdaptive: { type: "adaptive", display?: "summarized" | "omitted" })
1795
- claudeOpts.thinking = { type: "adaptive", display: "summarized" };
1796
- }
1797
-
1798
- if (ls.permissionMode) {
1799
- session._loopPermissionMode = ls.permissionMode;
1800
- }
1801
-
1802
- // Pass through any extra SDK settings from LOOP.json
1803
- if (ls.disableAllHooks !== undefined) {
1804
- claudeOpts.settings = Object.assign({}, claudeOpts.settings || {}, { disableAllHooks: ls.disableAllHooks });
1805
- }
1806
-
1807
- if (dangerouslySkipPermissions || (session.mode === "tui" && session.dangerouslySkipPermissions)) {
1808
- claudeOpts.allowDangerouslySkipPermissions = true;
1809
- claudeOpts.permissionMode = "bypassPermissions";
1810
- } else {
1811
- var globalMode = session.permissionMode || sm.currentPermissionMode || "default";
1812
- var effectiveDefault;
1813
- // GUI full access stays inside Clay's canUseTool callback. Passing the
1814
- // SDK bypass mode would require its process-level dangerous escape hatch
1815
- // and would skip Clay's permission policy entirely.
1816
- if (globalMode === "bypassPermissions") effectiveDefault = "default";
1817
- else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
1818
- else effectiveDefault = globalMode;
1819
- var modeToApply = session._loopPermissionMode || effectiveDefault;
1820
- // Always set permissionMode explicitly, including "default". SDK
1821
- // 0.3.286+ leaves an omitted permissionMode to Claude Code itself,
1822
- // which can apply a settings defaultMode or start in auto mode on
1823
- // third-party providers / with telemetry off -- silently widening
1824
- // past Clay's Ask policy. Sending "default" keeps the SDK's own
1825
- // decision point at the same "default" behavior Clay has always run
1826
- // under: a tool the SDK's own allowed/denied rules or allowedTools
1827
- // list already settles is not re-asked, but anything left undecided
1828
- // still reaches Clay's canUseTool callback, as before.
1829
- claudeOpts.permissionMode = modeToApply || "default";
1830
- }
1831
- if (session.youCurator) {
1832
- claudeOpts.permissionMode = "default";
1833
- claudeOpts.allowDangerouslySkipPermissions = false;
1834
- }
1835
- // Clear one-shot acceptEditsAfterStart regardless of which branch ran above,
1836
- // so the flag does not linger into subsequent turns.
1837
- if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1838
- if (session.cliSessionId && session.lastRewindUuid) {
1839
- claudeOpts.resumeSessionAt = session.lastRewindUuid;
1840
- delete session.lastRewindUuid;
1841
- sm.saveSessionFile(session);
1842
- }
1843
-
1844
- // Pass linuxUser to adapter for worker-based queries
1663
+ if (ls.permissionMode) session._loopPermissionMode = ls.permissionMode;
1664
+ var permissionMode = session.permissionMode || sm.currentPermissionMode || "default";
1665
+ var nativeMode = permissionMode === "bypassPermissions" ? "default" : session.acceptEditsAfterStart ? "acceptEdits" : permissionMode;
1666
+ var isolationHome = null;
1845
1667
  if (linuxUser) {
1846
- claudeOpts.linuxUser = linuxUser;
1847
- claudeOpts.singleTurn = !!session.singleTurn;
1848
- try {
1849
- claudeOpts.originalHome = require("./os-users").resolveOsUserInfo(linuxUser).home;
1850
- } catch (e) {
1851
- claudeOpts.originalHome = null;
1852
- }
1853
- claudeOpts.projectPath = session.cwd || null;
1854
- claudeOpts._perfT0 = t0;
1855
- // Pass previous worker state for reuse
1856
- if (session._adapterWorkerState) {
1857
- claudeOpts._workerState = session._adapterWorkerState;
1858
- session._adapterWorkerState = null;
1859
- }
1860
- }
1668
+ try { isolationHome = require("./os-users").resolveOsUserInfo(linuxUser).home; } catch (error) {}
1669
+ }
1670
+ var executionIntent = {
1671
+ settings: sm, permissionMode: permissionMode, nativePermissionMode: session._loopPermissionMode || nativeMode,
1672
+ nativeBypass: dangerouslySkipPermissions || session.mode === "tui" && session.dangerouslySkipPermissions,
1673
+ readOnly: !!session.youCurator, betas: sm.currentBetas,
1674
+ thinking: { mode: ls.thinking || sm.currentThinking, budget: ls.thinkingBudget || sm.currentThinkingBudget },
1675
+ disableHooks: ls.disableAllHooks, resumeAt: session.cliSessionId && session.lastRewindUuid || undefined,
1676
+ singleTurn: !!session.singleTurn, startedAt: t0,
1677
+ isolation: { user: linuxUser, home: isolationHome, projectPath: session.cwd || null, workerState: session._adapterWorkerState },
1678
+ sessionBridge: null,
1679
+ };
1680
+ if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1681
+ if (executionIntent.resumeAt) { delete session.lastRewindUuid; sm.saveSessionFile(session); }
1682
+ if (linuxUser && session._adapterWorkerState) session._adapterWorkerState = null;
1861
1683
 
1862
1684
  // Pick a model from the session or its vendor-specific default, then
1863
1685
  // validate it against the session vendor's known model list.
@@ -1883,20 +1705,21 @@ function createSDKBridge(opts) {
1883
1705
  sm.saveSessionFile(session);
1884
1706
  }
1885
1707
 
1886
- var codexConfig = getCodexConfig(sm);
1887
- var kiroConfig = getKiroConfig(sm);
1888
1708
  session._sdkQueryGeneration = Number(session._sdkQueryGeneration || 0) + 1;
1889
1709
  // Individual feature modules own their transport capability checks. Do not
1890
1710
  // suppress unrelated session tools merely because one feature cannot be
1891
1711
  // delivered by this provider.
1892
1712
  var sessionToolDefs = getSessionToolDefs(session) || [];
1893
1713
  var queryGeneration = Number(session._sdkQueryGeneration || 0);
1714
+ executionIntent.sessionBridge = getAcpMcpServers(session);
1715
+ var queryContract = yoke.sessionTools.contract(sessionAdapter);
1716
+ function needsCatalogRecovery(family, version) {
1717
+ return yoke.sessionTools.needsRecovery(sessionAdapter, session, family, version);
1718
+ }
1894
1719
  var hasYouCatalog = sessionToolDefs.some(function (tool) { return tool.name === "search_user_knowledge"; });
1895
- var recoverCodexYouTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
1896
- && hasYouCatalog && Number(session.codexYouToolCatalogVersion || 0) < 1;
1720
+ var recoverYouTools = needsCatalogRecovery("knowledge", 1) && hasYouCatalog;
1897
1721
  var hasLinearCatalog = sessionToolDefs.some(function (tool) { return tool.name === "link_session_linear"; });
1898
- var recoverCodexLinearTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
1899
- && hasLinearCatalog && Number(session.codexLinearToolCatalogVersion || 0) < 1 && canUseSessionTools(session) === true;
1722
+ var recoverLinearTools = needsCatalogRecovery("linear", 1) && hasLinearCatalog && canUseSessionTools(session) === true;
1900
1723
  var hasIssueCatalog = false;
1901
1724
  for (var issueToolIndex = 0; issueToolIndex < sessionToolDefs.length; issueToolIndex++) {
1902
1725
  if (sessionToolDefs[issueToolIndex].name === "create_issue") {
@@ -1905,19 +1728,16 @@ function createSDKBridge(opts) {
1905
1728
  }
1906
1729
  }
1907
1730
  var hasSharedBrowserCatalog = sessionToolDefs.some(function (definition) { return definition.name === "shared_browser"; });
1908
- var recoverCodexBrowserTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
1909
- && hasSharedBrowserCatalog && Number(session.codexSharedBrowserToolCatalogVersion || 0) < 6;
1910
- var recoverCodexIssueTools = sessionAdapter.vendor === "codex"
1911
- && !!session.cliSessionId
1912
- && hasIssueCatalog
1913
- && Number(session.codexIssuesToolCatalogVersion || 0) < 1;
1731
+ var recoverBrowserTools = needsCatalogRecovery("browser", 6) && hasSharedBrowserCatalog;
1732
+ var recoverIssueTools = needsCatalogRecovery("issues", 1) && hasIssueCatalog;
1914
1733
  function queryContextAvailable() {
1734
+ if (queryRetired()) return false;
1915
1735
  if (Number(session._sdkQueryGeneration || 0) !== queryGeneration) return false;
1916
1736
  if (sm && sm.sessions && typeof sm.sessions.get === "function" && sm.sessions.get(session.localId) !== session) return false;
1917
1737
  return canUseSessionTools(session) === true;
1918
1738
  }
1919
1739
  var hasMateScheduleTool = sessionToolDefs.some(function (tool) { return yoke.sessionTools.MATE_SCHEDULE_TOOLS.indexOf(tool.name) !== -1; });
1920
- var recoverCodexMateSchedules = sessionAdapter.vendor === "codex" && !!session.cliSessionId && hasMateScheduleTool && queryContextAvailable();
1740
+ var recoverMateSchedules = needsCatalogRecovery() && hasMateScheduleTool && queryContextAvailable();
1921
1741
  var hasScheduleTool = false;
1922
1742
  var hasScheduleInterviewTool = false;
1923
1743
  var scheduleInterviewActive = null;
@@ -1934,10 +1754,10 @@ function createSDKBridge(opts) {
1934
1754
  var activeLoopInterview = !isDriverOperatedSession(session) && !session.loopInterviewHandoff && !activeRun && !!loopInterview.activeInterviewId(session);
1935
1755
  var structuredInputSession = hasScheduleInterviewTool || activeLoopInterview || (!isDriverOperatedSession(session) && session.ralphCraftingMode === true && session.loop && session.loop.role === "crafting");
1936
1756
  var requestedUserInputMode = session.debateSetupMode || session.mateCreationMode ? "fallback" : "auto";
1937
- if (structuredInputSession || sessionAdapter.vendor === "codex") requestedUserInputMode = "fallback";
1757
+ if (structuredInputSession) requestedUserInputMode = "fallback";
1758
+ if (requestedUserInputMode === "auto" && queryContract.defaultUserInputMode) requestedUserInputMode = queryContract.defaultUserInputMode;
1938
1759
  var selectedUserInputMode = yoke.userInput.selectMode(sessionAdapter, requestedUserInputMode);
1939
- var recoverCodexQuestionTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
1940
- && Number(session.codexUserInputToolCatalogVersion || 0) < 1 && queryContextAvailable();
1760
+ var recoverQuestionTools = needsCatalogRecovery("questions", 1) && queryContextAvailable();
1941
1761
  function structuredInputContextAvailable() {
1942
1762
  if (!queryContextAvailable()) return false;
1943
1763
  // A schedule-triggered fallback shares the schedule feature's live
@@ -1979,26 +1799,33 @@ function createSDKBridge(opts) {
1979
1799
  return opts.onUserInputRequest(session, request, respond);
1980
1800
  };
1981
1801
  var localMcpServers = getMcpServers(session);
1982
- if (sessionAdapter.vendor === "claude" && hasScheduleTool && typeof sessionAdapter.createToolServer === "function") {
1802
+ if (queryContract.toolTransport === "sdk-mcp" && (hasScheduleTool || hasMateScheduleTool)
1803
+ && typeof sessionAdapter.createToolServer !== "function") {
1804
+ throw new Error("The adapter cannot mount native schedule tools without createToolServer.");
1805
+ }
1806
+ if (queryContract.toolTransport === "sdk-mcp" && hasScheduleTool) {
1983
1807
  var scheduledTaskToolDefs = [];
1984
1808
  for (var scheduledToolIndex = 0; scheduledToolIndex < sessionToolDefs.length; scheduledToolIndex++) {
1985
1809
  if (yoke.sessionTools.SCHEDULE_TOOLS.indexOf(sessionToolDefs[scheduledToolIndex].name) !== -1) scheduledTaskToolDefs.push(sessionToolDefs[scheduledToolIndex]);
1986
1810
  }
1987
1811
  if (scheduledTaskToolDefs.length) {
1988
1812
  var scheduledTaskServer = sessionAdapter.createToolServer({ name: "clay-scheduled-tasks", version: "1.0.0", tools: scheduledTaskToolDefs });
1989
- if (scheduledTaskServer) {
1990
- localMcpServers = Object.assign({}, localMcpServers || {});
1991
- localMcpServers["clay-scheduled-tasks"] = scheduledTaskServer;
1992
- }
1813
+ if (!scheduledTaskServer) throw new Error("The adapter could not mount scheduled-task tools.");
1814
+ localMcpServers = Object.assign({}, localMcpServers || {});
1815
+ localMcpServers["clay-scheduled-tasks"] = scheduledTaskServer;
1993
1816
  }
1994
1817
  }
1995
- if (sessionAdapter.vendor === "claude" && hasMateScheduleTool && typeof sessionAdapter.createToolServer === "function") {
1818
+ if (queryContract.toolTransport === "sdk-mcp" && hasMateScheduleTool) {
1996
1819
  localMcpServers = Object.assign({}, localMcpServers || {});
1997
1820
  localMcpServers["clay-mate-schedules"] = sessionAdapter.createToolServer({ name: "clay-mate-schedules", version: "1.0.0",
1998
1821
  tools: sessionToolDefs.filter(function (tool) { return yoke.sessionTools.MATE_SCHEDULE_TOOLS.indexOf(tool.name) !== -1; }) });
1822
+ if (!localMcpServers["clay-mate-schedules"]) throw new Error("The adapter could not mount Mate schedule tools.");
1999
1823
  }
2000
1824
  if (selectedUserInputMode === "fallback") {
2001
1825
  var userInputServer = yoke.userInput.createFallbackServer(sessionAdapter, sessionUserInputHandler, { force: true, maxQuestions: scheduleQuestionLimit });
1826
+ if (queryContract.toolTransport === "sdk-mcp" && !userInputServer) {
1827
+ throw new Error("The adapter could not mount structured-input tools.");
1828
+ }
2002
1829
  if (userInputServer) {
2003
1830
  localMcpServers = Object.assign({}, localMcpServers || {});
2004
1831
  localMcpServers[userInputServer.name || "yoke-user-input"] = userInputServer;
@@ -2035,9 +1862,9 @@ function createSDKBridge(opts) {
2035
1862
 
2036
1863
  var externalQueryBridge = require('./mcp-query-bridge').createQueryBridge({ valid: queryContextAvailable,
2037
1864
  getServers: function () { return getRemoteMcpServers ? getRemoteMcpServers(session) || {} : {}; } });
2038
- var externalToolDefs = sessionAdapter.vendor === 'codex' ? externalQueryBridge.dynamicDefs() : [];
1865
+ var externalToolDefs = queryContract.externalToolTransport === "dynamic" ? externalQueryBridge.dynamicDefs() : [];
2039
1866
  if (externalToolDefs.length) { sessionToolDefs = sessionToolDefs.concat(externalToolDefs); queryToolSnapshots.set(session, { generation: queryGeneration, defs: sessionToolDefs.slice() }); }
2040
- var recoverCodexExternalTools = sessionAdapter.vendor === 'codex' && !!session.cliSessionId && (externalToolDefs.length > 0 || isMate);
1867
+ var recoverExternalTools = needsCatalogRecovery() && (externalToolDefs.length > 0 || isMate);
2041
1868
 
2042
1869
  // Give fresh queries an immediate provisional label. The visible user
2043
1870
  // message in history wins over `text`, which may contain an internal
@@ -2054,7 +1881,6 @@ function createSDKBridge(opts) {
2054
1881
 
2055
1882
  var modelSwitchGeneration = session._queryGeneration;
2056
1883
  function queryRetired() {
2057
- if (sessionAdapter.vendor !== "claude") return false;
2058
1884
  return Number(session._sdkQueryGeneration || 0) !== queryGeneration || session.taskStopRequested
2059
1885
  || session.destroying || responseRecovery.stopping(session) || (handle && session.queryInstance !== handle);
2060
1886
  }
@@ -2072,7 +1898,7 @@ function createSDKBridge(opts) {
2072
1898
  clayBridgeToken: clayBridgeToken,
2073
1899
  linuxUser: linuxUser || undefined,
2074
1900
  env: sessionRuntimeEnv,
2075
- skillOptions: getSessionSkillOptions(session),
1901
+ skillOptions: require("./session-skill-scope").bind(session, sm.sessions, getSessionSkillOptions(session), queryContextAvailable, usersModule.isMultiUser()),
2076
1902
  // appendSystemPrompt, NOT systemPrompt: a plain-string systemPrompt
2077
1903
  // REPLACES the entire Claude Code system prompt (sdk.d.ts:2096), which
2078
1904
  // would strip the driver session down to the two-sentence pair preset.
@@ -2080,7 +1906,7 @@ function createSDKBridge(opts) {
2080
1906
  // codex/kiro: prepend to the first message).
2081
1907
  appendSystemPrompt: [
2082
1908
  typeof opts.getSessionSystemPrompt === "function" ? opts.getSessionSystemPrompt(session) : "",
2083
- sessionAdapter.vendor === "codex" && queryContextAvailable() ? codexQuestionGuidance() : "",
1909
+ typeof queryContract.questionGuidance === "function" && queryContextAvailable() ? queryContract.questionGuidance() : "",
2084
1910
  ].filter(Boolean).join("\n\n") || undefined,
2085
1911
  model: queryModel,
2086
1912
  // Effort is remembered per vendor and clamped to the session vendor's
@@ -2097,21 +1923,22 @@ function createSDKBridge(opts) {
2097
1923
  toolServers: mergedMcpServers,
2098
1924
  toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
2099
1925
  dynamicTools: sessionToolDefs,
2100
- // Codex persists dynamic tools with a thread. A stale authorized Driver
1926
+ // Conversation-scoped catalogs persist definitions. A stale authorized Driver
2101
1927
  // gets this exact-query bridge; its handlers still reauthorize live.
2102
- sessionMcpServer: (recoverCodexMateSchedules || recoverCodexIssueTools || recoverCodexQuestionTools || recoverCodexBrowserTools || recoverCodexLinearTools || recoverCodexYouTools || recoverCodexExternalTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
1928
+ sessionMcpServer: (recoverMateSchedules || recoverIssueTools || recoverQuestionTools || recoverBrowserTools || recoverLinearTools || recoverYouTools || recoverExternalTools) ? getSessionMcpServer(session, queryGeneration) : undefined,
2103
1929
  onRecoveryAdmission: function(waiting) {
2104
1930
  if (!queryContextAvailable()) return;
2105
- if (waiting) session._codexRecoveryWaiting = queryGeneration;
2106
- else delete session._codexRecoveryWaiting;
1931
+ if (waiting) session._providerRecoveryWaiting = queryGeneration;
1932
+ else delete session._providerRecoveryWaiting;
2107
1933
  },
2108
1934
  onRecoveryWaiting: function(waiting) {
2109
1935
  if (!queryContextAvailable()) return;
2110
1936
  sendAndRecord(session, { type: 'informational', level: 'info', content: waiting
2111
- ? 'Codex recovery is waiting for this conversation’s runtime to become safely available. Your message is retained; Stop cancels the wait.'
2112
- : 'Codex recovery is ready. Continuing your retained message.' });
1937
+ ? 'Tool recovery is waiting for this conversation’s runtime to become safely available. Your message is retained; Stop cancels the wait.'
1938
+ : 'Tool recovery is ready. Continuing your retained message.' });
2113
1939
  },
2114
1940
  callDynamicTool: function (toolName, args) {
1941
+ if (queryRetired() || !queryContextAvailable()) return Promise.reject(new Error("This session tool query is no longer available."));
2115
1942
  var toolDef = findSessionToolDef(toolName);
2116
1943
  if (toolDef && typeof toolDef.handler === "function") {
2117
1944
  return toolDef.handler(args || {});
@@ -2123,7 +1950,8 @@ function createSDKBridge(opts) {
2123
1950
  canUseTool: function(toolName, input, toolOpts) {
2124
1951
  if (queryRetired()) return { behavior: "deny", message: "This attempt has stopped." };
2125
1952
  responseRecovery.progress(session);
2126
- var nativeQuestionError = toolName === "AskUserQuestion" ? scheduleQuestionError(input) : null;
1953
+ var questionInput = yoke.userInput.toolQuestions(sessionAdapter, toolName, input);
1954
+ var nativeQuestionError = questionInput ? scheduleQuestionError(questionInput) : null;
2127
1955
  if (nativeQuestionError) return { behavior: "deny", message: nativeQuestionError };
2128
1956
  var permissionName = toolName;
2129
1957
  var permissionToolDef = findSessionToolDef(toolName);
@@ -2133,7 +1961,8 @@ function createSDKBridge(opts) {
2133
1961
  preToolUsePolicy: function(toolName, input) {
2134
1962
  if (queryRetired()) return Promise.resolve({ behavior: "deny", message: "This attempt has stopped." });
2135
1963
  responseRecovery.progress(session);
2136
- var nativeQuestionError = toolName === "AskUserQuestion" ? scheduleQuestionError(input) : null;
1964
+ var questionInput = yoke.userInput.toolQuestions(sessionAdapter, toolName, input);
1965
+ var nativeQuestionError = questionInput ? scheduleQuestionError(questionInput) : null;
2137
1966
  if (nativeQuestionError) return Promise.resolve({ behavior: "deny", message: nativeQuestionError });
2138
1967
  var permissionName = toolName;
2139
1968
  var permissionToolDef = findSessionToolDef(toolName);
@@ -2160,33 +1989,7 @@ function createSDKBridge(opts) {
2160
1989
  responseRecovery.progress(session);
2161
1990
  return callMcpToolHandler(mergedMcpServers, serverName, toolName, args);
2162
1991
  },
2163
- adapterOptions: {
2164
- CLAUDE: claudeOpts,
2165
- CODEX: {
2166
- // Always use "never" (auto-approve) because Clay handles tool
2167
- // permissions via its own UI (checkToolWhitelist + handleCanUseTool).
2168
- // Codex's native approval prompts are terminal-based and cannot be
2169
- // relayed through Clay's web UI, causing MCP tool calls to hang.
2170
- approvalPolicy: "never",
2171
- sandboxMode: session.youCurator ? "read-only" : codexConfig.sandbox,
2172
- webSearchMode: session.youCurator ? "disabled" : codexConfig.webSearch,
2173
- },
2174
- ACP: {
2175
- mcpServers: getAcpMcpServers(session),
2176
- },
2177
- ANTIGRAVITY: {
2178
- dangerouslySkipPermissions: dangerouslySkipPermissions
2179
- || session.permissionMode === "bypassPermissions",
2180
- },
2181
- KIRO: {
2182
- engine: kiroConfig.engine,
2183
- mode: kiroConfig.mode,
2184
- // ACP accepts additional stdio MCP servers per session. The bridge is
2185
- // bound to this Clay session so partner calls cannot leak across
2186
- // concurrent Kiro sessions or users.
2187
- mcpServers: getAcpMcpServers(session),
2188
- },
2189
- },
1992
+ adapterOptions: require("./yoke/query-options").map(sessionAdapter, executionIntent),
2190
1993
  };
2191
1994
 
2192
1995
  if (recoveryState) {
@@ -2265,11 +2068,9 @@ function createSDKBridge(opts) {
2265
2068
 
2266
2069
  // Store QueryHandle on session for iteration and control.
2267
2070
  session.queryInstance = handle;
2268
- // Capabilities come from the installed query handle, never a model-name
2269
- // table. `acceptEdits` and MCP overrides are Claude SDK semantics; a
2270
- // vendor may expose the generic setter without implementing either.
2271
- session.permissionCapabilities = require("./session-permission-mode").capabilities(session, sm.defaultVendor);
2272
- session.permissionCapabilities.mcpOverride = sessionAdapter.vendor === "claude" && typeof handle.setMcpPermissionModeOverride === "function";
2071
+ // An installed setter and the adapter's explicit protocol contract are
2072
+ // both required; generic handle stubs do not establish runtime support.
2073
+ session.permissionCapabilities = require("./session-permission-mode").capabilities(session, sm.defaultVendor, sessionAdapter);
2273
2074
  session.effectivePermissionMode = null;
2274
2075
  sm.saveSessionFile(session);
2275
2076
  sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effectivePermissionMode: null, permissionCapabilities: session.permissionCapabilities, mcpPermissionModeOverrides: session.mcpPermissionModeOverrides || {}, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
@@ -2353,32 +2154,27 @@ function createSDKBridge(opts) {
2353
2154
  }
2354
2155
 
2355
2156
  session.lastActivityAt = Date.now();
2356
- var codexCatalogCandidate = null;
2357
- if (sessionAdapter.vendor === "codex" && !resumedAtQueryStart) {
2157
+ var catalogCandidate = null;
2158
+ if (queryContract.catalogLifetime === "conversation" && typeof queryContract.recordCatalog === "function" && !resumedAtQueryStart) {
2358
2159
  var requiredCatalogTools = { begin_scheduled_task_interview: false, propose_scheduled_task: false, list_scheduled_tasks: false, read_scheduled_task: false, update_scheduled_task: false, report_scheduled_task_outcome: false, ask_user_questions: false };
2359
2160
  for (var catalogToolIndex = 0; catalogToolIndex < sessionToolDefs.length; catalogToolIndex++) {
2360
2161
  if (Object.prototype.hasOwnProperty.call(requiredCatalogTools, sessionToolDefs[catalogToolIndex].name)) requiredCatalogTools[sessionToolDefs[catalogToolIndex].name] = true;
2361
2162
  }
2362
2163
  var completeInterviewCatalog = requiredCatalogTools.begin_scheduled_task_interview && requiredCatalogTools.propose_scheduled_task && requiredCatalogTools.list_scheduled_tasks && requiredCatalogTools.read_scheduled_task && requiredCatalogTools.update_scheduled_task && requiredCatalogTools.ask_user_questions;
2363
2164
  if (completeInterviewCatalog || requiredCatalogTools.report_scheduled_task_outcome) {
2364
- codexCatalogCandidate = { generation: queryGeneration, version: yoke.sessionTools.CODEX_CATALOG_VERSION };
2365
- }
2366
- if (requiredCatalogTools.ask_user_questions) {
2367
- codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, userInputVersion: 1 });
2165
+ catalogCandidate = { generation: queryGeneration, versions: { schedules: yoke.sessionTools.SCHEDULE_CATALOG_VERSION } };
2368
2166
  }
2369
- if (hasYouCatalog) codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, youVersion: 1 });
2370
- if (hasLinearCatalog) codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, linearVersion: 1 });
2371
- if (hasSharedBrowserCatalog) {
2372
- codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, sharedBrowserVersion: 6 });
2373
- }
2374
- if (hasIssueCatalog) {
2375
- codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, {
2376
- generation: queryGeneration,
2377
- issuesVersion: 1,
2378
- });
2167
+ function markCatalog(family, version) {
2168
+ if (!catalogCandidate) catalogCandidate = { generation: queryGeneration, versions: {} };
2169
+ catalogCandidate.versions[family] = version;
2379
2170
  }
2171
+ if (requiredCatalogTools.ask_user_questions) markCatalog("questions", 1);
2172
+ if (hasYouCatalog) markCatalog("knowledge", 1);
2173
+ if (hasLinearCatalog) markCatalog("linear", 1);
2174
+ if (hasSharedBrowserCatalog) markCatalog("browser", 6);
2175
+ if (hasIssueCatalog) markCatalog("issues", 1);
2380
2176
  }
2381
- session.streamPromise = processQueryStream(session, codexCatalogCandidate).catch(function(err) {
2177
+ session.streamPromise = processQueryStream(session, catalogCandidate, queryContract).catch(function(err) {
2382
2178
  });
2383
2179
  return true;
2384
2180
  }
@@ -2392,7 +2188,7 @@ function createSDKBridge(opts) {
2392
2188
  function pushMessage(session, text, images, deliveryMeta) {
2393
2189
  if (session._responseRecoveryStopPending && !responseRecovery.stopping(session)) return false;
2394
2190
  function trackDelivery() {
2395
- if ((session.vendor || adapter.vendor) === "claude") responseRecovery.enqueue(session, {
2191
+ responseRecovery.enqueue(session, {
2396
2192
  text: text, images: images, linuxUser: session.lastLinuxUser || null,
2397
2193
  });
2398
2194
  }
@@ -2429,6 +2225,7 @@ function createSDKBridge(opts) {
2429
2225
  console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
2430
2226
  }
2431
2227
  if (delivered) {
2228
+ require('./sdk-foreground-admission').take(session);
2432
2229
  trackDelivery();
2433
2230
  delete session._lastTurnInterrupted;
2434
2231
  if (session._awaitingTurnResult) {
@@ -2535,65 +2332,8 @@ function createSDKBridge(opts) {
2535
2332
  // Detect which vendor binaries are installed for this user.
2536
2333
  // In multi-user mode, runs checks as the specific Linux user.
2537
2334
  function detectInstalledVendors(linuxUser) {
2538
- var execFileSync = require("child_process").execFileSync;
2539
- var fs = require("fs");
2540
- var result = [];
2541
-
2542
- function tryLookup(name) {
2543
- try {
2544
- if (linuxUser) {
2545
- execFileSync("su", ["-", linuxUser, "-c", "which " + name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
2546
- } else {
2547
- if (process.platform === "win32") execFileSync("where", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
2548
- else execFileSync("which", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
2549
- }
2550
- return true;
2551
- } catch (e) {
2552
- return false;
2553
- }
2554
- }
2555
-
2556
- // Claude: check if binary is in PATH
2557
- if (tryLookup(yoke.getVendorInfo("claude").binaryName)) result.push("claude");
2558
-
2559
- // Codex: check bundled binary or PATH
2560
- var codexBin = null;
2561
- try {
2562
- codexBin = require("./yoke/codex-app-server").findCodexPath();
2563
- } catch (e) {}
2564
- if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
2565
-
2566
- var subprocessVendorKeys = ["antigravity", "opencode", "kimi", "grok", "copilot", "qwen", "junie"];
2567
- var acpProfiles = require("./yoke/acp-agent-profiles");
2568
- for (var subprocessVendorIndex = 0; subprocessVendorIndex < subprocessVendorKeys.length; subprocessVendorIndex++) {
2569
- var subprocessVendor = subprocessVendorKeys[subprocessVendorIndex];
2570
- var subprocessInfo = yoke.getVendorInfo(subprocessVendor);
2571
- if (!linuxUser || subprocessInfo.osUserIsolation) {
2572
- if (subprocessVendor === "antigravity") {
2573
- if (tryLookup(subprocessInfo.binaryName)) result.push(subprocessVendor);
2574
- } else {
2575
- var acpProfile = acpProfiles.getAcpAgentProfile(subprocessVendor);
2576
- if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(subprocessVendor);
2577
- }
2578
- } else {
2579
- console.log("[sdk-bridge] " + subprocessInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
2580
- }
2581
- }
2582
-
2583
- // Kiro has no per-user ACP spawn path yet. Do not advertise the daemon's
2584
- // binary to an OS-isolated user, even if that user also has Kiro installed.
2585
- var kiroInfo = yoke.getVendorInfo("kiro");
2586
- if (!linuxUser || kiroInfo.osUserIsolation) {
2587
- var kiroBin = null;
2588
- try {
2589
- kiroBin = require("./yoke/kiro-acp-server").findKiroPath();
2590
- } catch (e) {}
2591
- if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup(kiroInfo.binaryName)) result.push("kiro");
2592
- } else {
2593
- console.log("[sdk-bridge] " + kiroInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
2594
- }
2595
-
2596
- return result;
2335
+ var installed = require("./yoke/discovery").installed({ user: linuxUser });
2336
+ return Object.keys(installed).filter(function(vendor) { return installed[vendor]; });
2597
2337
  }
2598
2338
 
2599
2339
  function getAvailableVendors(linuxUser) {
@@ -2850,16 +2590,7 @@ function createSDKBridge(opts) {
2850
2590
  message: "Read-only access. You cannot make changes via @mention.",
2851
2591
  });
2852
2592
  },
2853
- adapterOptions: {
2854
- CLAUDE: {
2855
- settingSources: ["user"],
2856
- includePartialMessages: true,
2857
- // Explicit, not omitted: SDK 0.3.286+ leaves an omitted
2858
- // permissionMode to Claude Code's own default/auto behavior,
2859
- // which could bypass the deny-by-default canUseTool above.
2860
- permissionMode: "default",
2861
- },
2862
- },
2593
+ adapterOptions: require("./yoke/query-options").map(mentionAdapter, { instructionSources: ["user"], readOnly: true }),
2863
2594
  });
2864
2595
  } catch (e) {
2865
2596
  opts.onError("Failed to create mention query: " + (e.message || e));
@@ -3004,7 +2735,8 @@ function createSDKBridge(opts) {
3004
2735
  setModel: setModel,
3005
2736
  setEffort: setEffort,
3006
2737
  setPermissionMode: setPermissionMode,
3007
- isClaudeProcess: isClaudeProcess,
2738
+ isProviderProcess: isProviderProcess,
2739
+ isClaudeProcess: isProviderProcess, // Compatibility export; production uses the provider contract.
3008
2740
  permissionPushTitle: permissionPushTitle,
3009
2741
  permissionPushBody: permissionPushBody,
3010
2742
  warmup: warmup,