clay-server 2.47.0-beta.2 → 2.47.0-beta.4

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.
@@ -3,7 +3,6 @@ import { escapeHtml } from './utils.js';
3
3
  import { iconHtml, refreshIcons } from './icons.js';
4
4
  import { store } from './store.js';
5
5
  import { hideKnowledge } from './mate-knowledge.js';
6
- import { hideMateDatastorePanel } from './mate-datastore-ui.js';
7
6
  import { isSchedulerOpen, closeScheduler } from './scheduler.js';
8
7
  import { hideNotes } from './sticky-notes.js';
9
8
  import { openSearch as openSessionSearch } from './session-search.js';
@@ -135,7 +134,6 @@ export function initMateSidebar(wsGetter) {
135
134
  }
136
135
 
137
136
  export function showMateSidebar(mateId, mateData) {
138
- if (hideMateDatastorePanel) hideMateDatastorePanel();
139
137
  currentMateId = mateId;
140
138
  currentMate = mateData;
141
139
  cachedSessions = [];
@@ -359,7 +357,6 @@ export function getMateSessions() {
359
357
  }
360
358
 
361
359
  export function hideMateSidebar() {
362
- if (hideMateDatastorePanel) hideMateDatastorePanel();
363
360
  currentMateId = null;
364
361
  currentMate = null;
365
362
  cachedSessions = [];
@@ -24,6 +24,8 @@
24
24
  // Codex sessions never receive a transcript_state (no JSONL), so the
25
25
  // overlay stays disabled — selection copy still works normally.
26
26
 
27
+ import { store } from './store.js';
28
+
27
29
  import { copyToClipboard, showToast } from './utils.js';
28
30
  import { getWs } from './ws-ref.js';
29
31
 
@@ -629,9 +631,16 @@ function requestTranscript(localId) {
629
631
  export function attachTuiGrab(xterm, containerEl, localId, opts) {
630
632
  if (!xterm || !containerEl || !localId) return;
631
633
  clearActive();
632
- // For non-claude vendors (e.g. Codex) we don't even bother — no
633
- // transcript exists on disk, so there's nothing to match against.
634
- if (opts && opts.vendor && opts.vendor !== "claude") return;
634
+ // Vendors without TUI sessions have no transcript to match against.
635
+ if (opts && opts.vendor) {
636
+ var vendors = store.get('vendorInfo') || {};
637
+ var info = vendors[opts.vendor];
638
+ var legacyTuiVendors = ["claude"];
639
+ var supportsTui = info
640
+ ? Array.isArray(info.sessionModes) && info.sessionModes.indexOf("tui") !== -1
641
+ : legacyTuiVendors.indexOf(opts.vendor) !== -1;
642
+ if (!supportsTui) return;
643
+ }
635
644
 
636
645
  var overlayEl = buildOverlay(containerEl);
637
646
  active = {
package/lib/sdk-bridge.js CHANGED
@@ -10,6 +10,7 @@ var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discover
10
10
  var { isSafeBashSegment } = require("./safe-bash-commands");
11
11
  var { createMessageQueue } = require("./sdk-message-queue");
12
12
  var { attachMessageProcessor } = require("./sdk-message-processor");
13
+ var yoke = require("./yoke");
13
14
 
14
15
  // Extract serializable tool descriptors from MCP server instances.
15
16
  // Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
@@ -112,10 +113,11 @@ function createSDKBridge(opts) {
112
113
  var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
113
114
  // mcpServers may be either a static object or a getter function. The
114
115
  // getter form lets callers gate individual servers at call time (e.g.
115
- // clay-browser is only exposed while the Chrome extension is connected).
116
+ // clay-browser is only exposed while the Chrome extension is connected)
117
+ // and bind session-scoped servers (clay-sessions) to the querying session.
116
118
  var _mcpServersSrc = opts.mcpServers || null;
117
- function getMcpServers() {
118
- if (typeof _mcpServersSrc === "function") return _mcpServersSrc() || null;
119
+ function getMcpServers(forSession) {
120
+ if (typeof _mcpServersSrc === "function") return _mcpServersSrc(forSession) || null;
119
121
  return _mcpServersSrc;
120
122
  }
121
123
  var getRemoteMcpServers = opts.getRemoteMcpServers || null;
@@ -127,7 +129,6 @@ function createSDKBridge(opts) {
127
129
  var _cachedFreshAuthAt = 0;
128
130
 
129
131
  function getFreshAuthState(force) {
130
- var yoke = require("./yoke");
131
132
  var now = Date.now();
132
133
  if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
133
134
  return _cachedFreshAuthState;
@@ -150,10 +151,13 @@ function createSDKBridge(opts) {
150
151
  }
151
152
 
152
153
  function getLoginCommand(vendor) {
153
- if (vendor === "codex") return "codex login --device-auth";
154
- if (vendor === "claude") return "claude login";
155
- if (vendor === "kiro") return "kiro-cli login";
156
- return (vendor || "claude") + " login";
154
+ var info = yoke.getVendorInfo(vendor);
155
+ return (info && info.loginCommand) || "claude login";
156
+ }
157
+
158
+ function supportsOsUserIsolation(vendor) {
159
+ var info = yoke.getVendorInfo(vendor);
160
+ return !info || info.osUserIsolation !== false;
157
161
  }
158
162
 
159
163
  function notifyAuthRequired(session, title, body, authLinuxUser, canAutoLogin, loginCommand) {
@@ -220,15 +224,25 @@ function createSDKBridge(opts) {
220
224
  }
221
225
 
222
226
  function sendModelInfoForVendor(vendor, model) {
227
+ var resolvedVendor = vendor || (adapter && adapter.vendor) || "claude";
223
228
  send({
224
229
  type: "model_info",
225
230
  model: model || "",
226
- models: getModelsForVendor(vendor),
227
- vendor: vendor || (adapter && adapter.vendor) || "claude",
231
+ models: getModelsForVendor(resolvedVendor),
232
+ vendor: resolvedVendor,
233
+ capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[resolvedVendor]) || {},
228
234
  availableVendors: sm.availableVendors || [],
229
235
  installedVendors: sm.installedVendors || [],
230
236
  });
231
237
  }
238
+
239
+ function rememberAdapterReady(vendor, result) {
240
+ if (!vendor || !result) return;
241
+ sm.modelsByVendor = sm.modelsByVendor || {};
242
+ sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
243
+ if (Array.isArray(result.models)) sm.modelsByVendor[vendor] = result.models;
244
+ sm.capabilitiesByVendor[vendor] = result.capabilities || {};
245
+ }
232
246
  var onTurnDone = opts.onTurnDone || null;
233
247
 
234
248
  // --- Idle session reaper ---
@@ -534,12 +548,6 @@ function createSDKBridge(opts) {
534
548
  }
535
549
  }
536
550
 
537
- // Auto-approve Mate datastore tools. These are scoped to the active Mate
538
- // project and already enforce SQL policy server-side.
539
- if (toolName.indexOf("mcp__clay-datastore__") === 0) {
540
- return { behavior: "allow", updatedInput: input };
541
- }
542
-
543
551
  // Auto-approve clay-history tools. Mounted only on Clay (host agent)
544
552
  // sessions and strictly read-only by design — search and read across
545
553
  // the user's own workspace data.
@@ -869,7 +877,8 @@ function createSDKBridge(opts) {
869
877
  var canAutoLogin = !usersModule.isMultiUser()
870
878
  || !!authLinuxUser
871
879
  || (authUser && authUser.role === "admin");
872
- var authVendorLabel = session.vendor === "codex" ? "Codex" : (session.vendor === "kiro" ? "Kiro CLI" : "Claude Code");
880
+ var authInfo = yoke.getVendorInfo(session.vendor);
881
+ var authVendorLabel = (authInfo && authInfo.displayName) || "Claude Code";
873
882
  var authTitle = authVendorLabel + " is not logged in.";
874
883
  var authMsg = {
875
884
  type: "auth_required",
@@ -1070,10 +1079,9 @@ function createSDKBridge(opts) {
1070
1079
  async function startQuery(session, text, images, linuxUser) {
1071
1080
  async function ensureVendorReady(vendor) {
1072
1081
  if (!vendor) return null;
1073
- if (linuxUser && vendor === "kiro") return null;
1082
+ if (linuxUser && !supportsOsUserIsolation(vendor)) return null;
1074
1083
  var vendorAdapter = adapters[vendor] || null;
1075
1084
  if (!vendorAdapter) {
1076
- var yoke = require("./yoke");
1077
1085
  vendorAdapter = await yoke.lazyCreateAdapter(adapters, vendor, {
1078
1086
  cwd: cwd,
1079
1087
  dangerouslySkipPermissions: dangerouslySkipPermissions,
@@ -1083,8 +1091,11 @@ function createSDKBridge(opts) {
1083
1091
  clayAuthToken: clayAuthToken,
1084
1092
  slug: slug,
1085
1093
  });
1086
- } else if ((!sm.modelsByVendor || !sm.modelsByVendor[vendor]) && typeof vendorAdapter.init === "function") {
1087
- await vendorAdapter.init({
1094
+ }
1095
+ var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[vendor]
1096
+ || !sm.modelsByVendor || !sm.modelsByVendor[vendor];
1097
+ if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
1098
+ var readyResult = await vendorAdapter.init({
1088
1099
  cwd: cwd,
1089
1100
  dangerouslySkipPermissions: dangerouslySkipPermissions,
1090
1101
  linuxUser: linuxUser || undefined,
@@ -1093,6 +1104,7 @@ function createSDKBridge(opts) {
1093
1104
  clayAuthToken: clayAuthToken,
1094
1105
  slug: slug,
1095
1106
  });
1107
+ rememberAdapterReady(vendor, readyResult);
1096
1108
  }
1097
1109
  if (vendorAdapter) {
1098
1110
  sm.availableVendors = getAvailableVendors(linuxUser);
@@ -1100,6 +1112,11 @@ function createSDKBridge(opts) {
1100
1112
  if (!sm.modelsByVendor[vendor] && typeof vendorAdapter.supportedModels === "function") {
1101
1113
  sm.modelsByVendor[vendor] = await vendorAdapter.supportedModels();
1102
1114
  }
1115
+ var vendorModels = getModelsForVendor(vendor);
1116
+ var modelForVendor = resolveModelInList(vendorModels, sm.currentModel)
1117
+ || modelEntryValue(vendorModels[0])
1118
+ || "";
1119
+ sendModelInfoForVendor(vendor, modelForVendor);
1103
1120
  }
1104
1121
  return vendorAdapter;
1105
1122
  }
@@ -1108,11 +1125,13 @@ function createSDKBridge(opts) {
1108
1125
  // OS-isolated session would expose the daemon user's HOME and Kiro
1109
1126
  // credentials to another user, so refuse until the adapter has a
1110
1127
  // per-user worker path.
1111
- if (linuxUser && session.vendor === "kiro") {
1112
- console.error("[sdk-bridge] Refusing Kiro for OS-isolated user " + linuxUser + ": per-user ACP spawning is not implemented");
1128
+ if (linuxUser && !supportsOsUserIsolation(session.vendor)) {
1129
+ var isolatedVendorInfo = yoke.getVendorInfo(session.vendor);
1130
+ var isolatedVendorName = (isolatedVendorInfo && isolatedVendorInfo.displayName) || session.vendor || "This vendor";
1131
+ console.error("[sdk-bridge] Refusing " + isolatedVendorName + " for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1113
1132
  sendAndRecord(session, {
1114
1133
  type: "error",
1115
- text: "Kiro is not available for OS-isolated users yet.",
1134
+ text: isolatedVendorName + " is not available for OS-isolated users yet.",
1116
1135
  });
1117
1136
  sendAndRecord(session, { type: "done", code: 1 });
1118
1137
  return;
@@ -1139,7 +1158,9 @@ function createSDKBridge(opts) {
1139
1158
  }
1140
1159
  // If still not available after lazy check, send auth_required
1141
1160
  if (session.vendor && !adapters[session.vendor]) {
1142
- var vendorName = session.vendor.charAt(0).toUpperCase() + session.vendor.slice(1);
1161
+ var missingVendorInfo = yoke.getVendorInfo(session.vendor);
1162
+ var vendorName = (missingVendorInfo && missingVendorInfo.displayName)
1163
+ || session.vendor.charAt(0).toUpperCase() + session.vendor.slice(1);
1143
1164
  var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
1144
1165
  var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
1145
1166
  var canAutoLogin = !usersModule.isMultiUser()
@@ -1311,7 +1332,7 @@ function createSDKBridge(opts) {
1311
1332
 
1312
1333
  var codexConfig = getCodexConfig(sm);
1313
1334
  var kiroConfig = getKiroConfig(sm);
1314
- var mergedMcpServers = mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined;
1335
+ var mergedMcpServers = mergeMcpServers(getMcpServers(session), getRemoteMcpServers) || undefined;
1315
1336
 
1316
1337
  // Derive an explicit session title for fresh queries so the SDK records
1317
1338
  // it at session creation and skips its own auto-generation. This also
@@ -1520,25 +1541,26 @@ function createSDKBridge(opts) {
1520
1541
  }
1521
1542
 
1522
1543
  // Claude: check if binary is in PATH
1523
- if (tryLookup("claude")) result.push("claude");
1544
+ if (tryLookup(yoke.getVendorInfo("claude").binaryName)) result.push("claude");
1524
1545
 
1525
1546
  // Codex: check bundled binary or PATH
1526
1547
  var codexBin = null;
1527
1548
  try {
1528
1549
  codexBin = require("./yoke/codex-app-server").findCodexPath();
1529
1550
  } catch (e) {}
1530
- if ((codexBin && fs.existsSync(codexBin)) || tryLookup("codex")) result.push("codex");
1551
+ if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
1531
1552
 
1532
1553
  // Kiro has no per-user ACP spawn path yet. Do not advertise the daemon's
1533
1554
  // binary to an OS-isolated user, even if that user also has Kiro installed.
1534
- if (!linuxUser) {
1555
+ var kiroInfo = yoke.getVendorInfo("kiro");
1556
+ if (!linuxUser || kiroInfo.osUserIsolation) {
1535
1557
  var kiroBin = null;
1536
1558
  try {
1537
1559
  kiroBin = require("./yoke/kiro-acp-server").findKiroPath();
1538
1560
  } catch (e) {}
1539
- if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup("kiro-cli")) result.push("kiro");
1561
+ if ((kiroBin && fs.existsSync(kiroBin)) || tryLookup(kiroInfo.binaryName)) result.push("kiro");
1540
1562
  } else {
1541
- console.log("[sdk-bridge] Kiro hidden for OS-isolated user " + linuxUser + ": per-user ACP spawning is not implemented");
1563
+ console.log("[sdk-bridge] " + kiroInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1542
1564
  }
1543
1565
 
1544
1566
  return result;
@@ -1546,7 +1568,7 @@ function createSDKBridge(opts) {
1546
1568
 
1547
1569
  function getAvailableVendors(linuxUser) {
1548
1570
  return Object.keys(adapters).filter(function(vendor) {
1549
- return !(linuxUser && vendor === "kiro");
1571
+ return !(linuxUser && !supportsOsUserIsolation(vendor));
1550
1572
  });
1551
1573
  }
1552
1574
 
@@ -1591,10 +1613,7 @@ function createSDKBridge(opts) {
1591
1613
  }
1592
1614
  sm.availableModels = result.models || [];
1593
1615
  // Store per-vendor models and capabilities
1594
- sm.modelsByVendor = sm.modelsByVendor || {};
1595
- sm.modelsByVendor[defaultVendor] = result.models || [];
1596
- sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
1597
- sm.capabilitiesByVendor[defaultVendor] = result.capabilities || {};
1616
+ rememberAdapterReady(defaultVendor, result);
1598
1617
  } catch (e) {
1599
1618
  if (e && e.name !== "AbortError" && !(e.message && e.message.indexOf("aborted") !== -1)) {
1600
1619
  send({ type: "error", text: "Failed to load " + defaultVendor + " SDK: " + (e.message || e) });
@@ -1,4 +1,5 @@
1
1
  var usersModule = require("./users");
2
+ var yoke = require("./yoke");
2
3
 
3
4
  function attachMessageProcessor(ctx) {
4
5
  var sm = ctx.sm;
@@ -50,12 +51,9 @@ function attachMessageProcessor(ctx) {
50
51
  var canAutoLogin = !usersModule.isMultiUser()
51
52
  || !!authLinuxUser
52
53
  || (authUser && authUser.role === "admin");
53
- var authTitle = session.vendor === "codex"
54
- ? "Codex is not logged in."
55
- : (session.vendor === "kiro" ? "Kiro CLI is not logged in." : "Claude Code is not logged in.");
56
- var loginCommand = session.vendor === "codex"
57
- ? "codex login --device-auth"
58
- : (session.vendor === "kiro" ? "kiro-cli login" : "claude login");
54
+ var vendorInfo = yoke.getVendorInfo(session.vendor);
55
+ var authTitle = ((vendorInfo && vendorInfo.displayName) || "Claude Code") + " is not logged in.";
56
+ var loginCommand = (vendorInfo && vendorInfo.loginCommand) || "claude login";
59
57
  var _nmLogin = getNotificationsModule();
60
58
  sendAndRecord(session, {
61
59
  type: "auth_required",
@@ -0,0 +1,56 @@
1
+ // Session Spawn MCP Server for Clay
2
+ // Provides SDK-free tool definitions for agent-driven session fan-out.
3
+
4
+ var z;
5
+ try { z = require("zod"); } catch (e) { z = null; }
6
+
7
+ function buildShape(props, required) {
8
+ if (!z) return {};
9
+ var shape = {};
10
+ var keys = Object.keys(props);
11
+ for (var i = 0; i < keys.length; i++) {
12
+ var key = keys[i];
13
+ var prop = props[key];
14
+ var field;
15
+ if (prop.type === "number") field = z.number();
16
+ else if (prop.type === "boolean") field = z.boolean();
17
+ else if (prop.enum) field = z.enum(prop.enum);
18
+ else field = z.string();
19
+ if (prop.description) field = field.describe(prop.description);
20
+ if (!required || required.indexOf(key) === -1) field = field.optional();
21
+ shape[key] = field;
22
+ }
23
+ return shape;
24
+ }
25
+
26
+ function getToolDefs(handlers) {
27
+ return [
28
+ {
29
+ name: "spawn_sessions",
30
+ description: "Create sibling work sessions in this project. Set forkFromCurrent to copy this session's full conversation context into every child. Children share the project's working directory, so prefer parallel analysis or other non-conflicting work over concurrent edits. Results can be polled with check_spawned_sessions; spawned children cannot spawn further sessions.",
31
+ inputSchema: buildShape({
32
+ sessions: { type: "string", description: "JSON array of objects: [{\"title\":\"Task title\",\"prompt\":\"Task instructions\"}]" },
33
+ vendor: { type: "string", description: "Optional vendor id. Defaults to the parent session vendor, then the project default." },
34
+ forkFromCurrent: { type: "boolean", description: "Children start with a copy of this session's full conversation context. Defaults to false." },
35
+ }, ["sessions"]),
36
+ handler: function (args) {
37
+ return handlers.spawn(args || {});
38
+ },
39
+ },
40
+ {
41
+ name: "check_spawned_sessions",
42
+ description: "Check the status of sessions spawned by this session. By default only direct children of the calling session are returned.",
43
+ inputSchema: buildShape({
44
+ parentOnly: { type: "boolean", description: "When false, return all spawned sessions in this project. Defaults to true." },
45
+ }),
46
+ handler: function (args) {
47
+ return handlers.check(args || {});
48
+ },
49
+ },
50
+ ];
51
+ }
52
+
53
+ module.exports = {
54
+ buildShape: buildShape,
55
+ getToolDefs: getToolDefs,
56
+ };
package/lib/sessions.js CHANGED
@@ -108,6 +108,7 @@ function createSessionManager(opts) {
108
108
  if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
109
109
  if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
110
110
  if (session.loop) metaObj.loop = session.loop;
111
+ if (session.spawn) metaObj.spawn = session.spawn;
111
112
  if (session.debateState) metaObj.debateState = session.debateState;
112
113
  if (session.debateSetupMode) metaObj.debateSetupMode = true;
113
114
  var meta = JSON.stringify(metaObj);
@@ -207,6 +208,7 @@ function createSessionManager(opts) {
207
208
  };
208
209
  if (m.vendor) session.vendor = m.vendor;
209
210
  if (m.loop) session.loop = m.loop;
211
+ if (m.spawn) session.spawn = m.spawn;
210
212
  if (m.debateState) session.debateState = m.debateState;
211
213
  if (m.debateSetupMode) session.debateSetupMode = true;
212
214
  if (m.ownerId) session.ownerId = m.ownerId;
@@ -394,6 +396,7 @@ function createSessionManager(opts) {
394
396
  isProcessing: s.isProcessing,
395
397
  lastActivity: s.lastActivity || s.createdAt || 0,
396
398
  loop: loop,
399
+ spawn: s.spawn || null,
397
400
  ownerId: s.ownerId || null,
398
401
  sessionVisibility: s.sessionVisibility || "shared",
399
402
  bookmarked: !!s.bookmarked,
package/lib/ws-schema.js CHANGED
@@ -473,20 +473,6 @@ var schema = {
473
473
  "memory_delete": { direction: "c2s", handler: "lib/project.js", description: "Delete a memory entry by index" },
474
474
  "memory_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Memory entry was deleted" },
475
475
 
476
- // -----------------------------------------------------------------------
477
- // Mate datastore
478
- // -----------------------------------------------------------------------
479
- "mate_db_tables": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "List schema objects in the current Mate datastore" },
480
- "mate_db_describe": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Describe a table or view in the current Mate datastore" },
481
- "mate_db_query": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run read-only SQL against the current Mate datastore" },
482
- "mate_db_exec": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run schema or write SQL against the current Mate datastore" },
483
- "mate_db_tables_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Schema objects returned from a Mate datastore" },
484
- "mate_db_describe_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Table description returned from a Mate datastore" },
485
- "mate_db_query_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Query results from a Mate datastore" },
486
- "mate_db_exec_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Execution summary from a Mate datastore" },
487
- "mate_db_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore error" },
488
- "mate_db_change": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore changed" },
489
-
490
476
  // -----------------------------------------------------------------------
491
477
  // Loop (automated task runner)
492
478
  // -----------------------------------------------------------------------
@@ -1180,6 +1180,11 @@ function createClaudeAdapter(opts) {
1180
1180
  slashCommands: [],
1181
1181
  fastModeState: null,
1182
1182
  capabilities: {
1183
+ effort: true,
1184
+ fork: true,
1185
+ rollback: false,
1186
+ sessionListing: true,
1187
+ sessionRename: true,
1183
1188
  thinking: true,
1184
1189
  betas: true,
1185
1190
  rewind: true,
@@ -1556,6 +1561,11 @@ function createClaudeAdapter(opts) {
1556
1561
  slashCommands: r.slashCommands || [],
1557
1562
  fastModeState: r.fastModeState || null,
1558
1563
  capabilities: {
1564
+ effort: true,
1565
+ fork: true,
1566
+ rollback: false,
1567
+ sessionListing: true,
1568
+ sessionRename: true,
1559
1569
  thinking: true,
1560
1570
  betas: true,
1561
1571
  rewind: true,
@@ -1628,6 +1638,11 @@ function createClaudeAdapter(opts) {
1628
1638
  slashCommands: [],
1629
1639
  fastModeState: null,
1630
1640
  capabilities: {
1641
+ effort: true,
1642
+ fork: true,
1643
+ rollback: false,
1644
+ sessionListing: true,
1645
+ sessionRename: true,
1631
1646
  thinking: true,
1632
1647
  betas: true,
1633
1648
  rewind: true,
@@ -1708,6 +1723,11 @@ function createClaudeAdapter(opts) {
1708
1723
  slashCommands: r.slashCommands || [],
1709
1724
  fastModeState: r.fastModeState || null,
1710
1725
  capabilities: {
1726
+ effort: true,
1727
+ fork: true,
1728
+ rollback: false,
1729
+ sessionListing: true,
1730
+ sessionRename: true,
1711
1731
  thinking: true,
1712
1732
  betas: true,
1713
1733
  rewind: true,
@@ -1737,4 +1757,9 @@ function createClaudeAdapter(opts) {
1737
1757
  module.exports = {
1738
1758
  createClaudeAdapter: createClaudeAdapter,
1739
1759
  createMessageQueue: createMessageQueue,
1760
+ contractTestKit: {
1761
+ createMessageQueue: createMessageQueue,
1762
+ createQueryHandle: createQueryHandle,
1763
+ normalizeEvent: flattenEvent,
1764
+ },
1740
1765
  };
@@ -597,6 +597,27 @@ function flattenEvent(notification, state) {
597
597
  return events;
598
598
  }
599
599
 
600
+ function createEventState(model) {
601
+ return {
602
+ blockCounter: 0,
603
+ threadId: null,
604
+ turnStarted: false,
605
+ lastUsage: null,
606
+ lastInputTokens: null,
607
+ done: false,
608
+ aborted: false,
609
+ loopStarted: false,
610
+ model: model || "gpt-5.6-terra",
611
+ textBlocks: {},
612
+ textLengths: {},
613
+ thinkingBlocks: {},
614
+ thinkingLengths: {},
615
+ toolBlocks: {},
616
+ commandInputs: {},
617
+ planTexts: {},
618
+ };
619
+ }
620
+
600
621
  // --- QueryHandle ---
601
622
 
602
623
  function createCodexQueryHandle(appServer, queryOpts) {
@@ -611,25 +632,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
611
632
  return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
612
633
  }
613
634
 
614
- var state = {
615
- blockCounter: 0,
616
- threadId: null,
617
- turnStarted: false,
618
- lastUsage: null,
619
- lastInputTokens: null, // from thread/tokenUsage/updated
620
- done: false,
621
- aborted: false,
622
- loopStarted: false,
623
- model: queryOpts.model || "gpt-5.6-terra",
624
- // Track incremental text deltas
625
- textBlocks: {}, // itemId -> true (text_start sent)
626
- textLengths: {}, // itemId -> last sent length
627
- thinkingBlocks: {}, // itemId -> blockId
628
- thinkingLengths: {}, // itemId -> last sent length
629
- toolBlocks: {}, // itemId -> blockId (for tool_start dedup)
630
- commandInputs: {}, // itemId -> command captured from approval/start events
631
- planTexts: {}, // itemId -> streamed plan text
632
- };
635
+ var state = createEventState(queryOpts.model);
633
636
 
634
637
  // Internal event buffer for async iterator
635
638
  var eventBuffer = [];
@@ -867,7 +870,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
867
870
  var threadParams = {
868
871
  model: queryOpts.model || "gpt-5.6-terra",
869
872
  sandbox: queryOpts.sandboxMode || "workspace-write",
870
- approvalPolicy: queryOpts.approvalPolicy || "on-failure",
873
+ approvalPolicy: queryOpts.approvalPolicy || "on-request",
871
874
  cwd: queryOpts.cwd,
872
875
  skipGitRepoCheck: true,
873
876
  };
@@ -1139,6 +1142,11 @@ function createCodexAdapter(opts) {
1139
1142
  slashCommands: skillNames || [],
1140
1143
  fastModeState: null,
1141
1144
  capabilities: {
1145
+ effort: true,
1146
+ fork: true,
1147
+ rollback: true,
1148
+ sessionListing: false,
1149
+ sessionRename: false,
1142
1150
  thinking: true,
1143
1151
  betas: false,
1144
1152
  rewind: false,
@@ -1463,7 +1471,7 @@ function createCodexAdapter(opts) {
1463
1471
  if (queryOpts.toolPolicy === "allow-all") {
1464
1472
  handleOpts.approvalPolicy = "never";
1465
1473
  } else {
1466
- handleOpts.approvalPolicy = codexOpts.approvalPolicy || "on-failure";
1474
+ handleOpts.approvalPolicy = codexOpts.approvalPolicy || "on-request";
1467
1475
  }
1468
1476
 
1469
1477
  // Sandbox mode
@@ -1590,4 +1598,9 @@ function createCodexAdapter(opts) {
1590
1598
 
1591
1599
  module.exports = {
1592
1600
  createCodexAdapter: createCodexAdapter,
1601
+ contractTestKit: {
1602
+ createEventState: createEventState,
1603
+ createQueryHandle: createCodexQueryHandle,
1604
+ normalizeEvent: flattenEvent,
1605
+ },
1593
1606
  };
@@ -377,30 +377,19 @@ function finalToolContent(state, callId, update) {
377
377
  return extractRawOutput(update.rawOutput);
378
378
  }
379
379
 
380
- // --- QueryHandle ---
381
-
382
- function createKiroQueryHandle(acp, queryOpts) {
383
- var abortController = queryOpts.abortController;
384
- var systemPrompt = queryOpts.systemPrompt || "";
385
- var canUseTool = queryOpts.canUseTool || null;
386
- var onFinished = queryOpts.onFinished || null;
387
-
388
- function isCancelled() {
389
- return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
390
- }
391
-
392
- var state = {
380
+ function createEventState(opts) {
381
+ opts = opts || {};
382
+ return {
393
383
  blockCounter: 0,
394
- sessionId: queryOpts.resumeSessionId || null,
395
- model: queryOpts.model || "auto",
396
- engine: queryOpts.engine || KIRO_DEFAULTS.engine,
384
+ sessionId: opts.resumeSessionId || null,
385
+ model: opts.model || "auto",
386
+ engine: opts.engine || KIRO_DEFAULTS.engine,
397
387
  lastInputTokens: null,
398
- contextWindow: queryOpts.contextWindow || null,
388
+ contextWindow: opts.contextWindow || null,
399
389
  done: false,
400
390
  aborted: false,
401
391
  loopStarted: false,
402
392
  loadingSession: false,
403
- // per-turn block tracking (reset each turn)
404
393
  textBlockOpen: false,
405
394
  textBlockId: null,
406
395
  thinkBlockOpen: false,
@@ -409,6 +398,21 @@ function createKiroQueryHandle(acp, queryOpts) {
409
398
  toolMeta: {},
410
399
  toolContent: {},
411
400
  };
401
+ }
402
+
403
+ // --- QueryHandle ---
404
+
405
+ function createKiroQueryHandle(acp, queryOpts) {
406
+ var abortController = queryOpts.abortController;
407
+ var systemPrompt = queryOpts.systemPrompt || "";
408
+ var canUseTool = queryOpts.canUseTool || null;
409
+ var onFinished = queryOpts.onFinished || null;
410
+
411
+ function isCancelled() {
412
+ return state.aborted || (abortController && abortController.signal && abortController.signal.aborted);
413
+ }
414
+
415
+ var state = createEventState(queryOpts);
412
416
 
413
417
  // Async iterator plumbing
414
418
  var eventBuffer = [];
@@ -864,6 +868,11 @@ function createKiroAdapter(opts) {
864
868
  slashCommands: skillNames || [],
865
869
  fastModeState: null,
866
870
  capabilities: {
871
+ effort: false,
872
+ fork: false,
873
+ rollback: false,
874
+ sessionListing: false,
875
+ sessionRename: false,
867
876
  thinking: true,
868
877
  betas: false,
869
878
  rewind: false,
@@ -872,7 +881,7 @@ function createKiroAdapter(opts) {
872
881
  elicitation: false,
873
882
  fileCheckpointing: false,
874
883
  contextCompacting: true,
875
- toolPolicy: ["ask", "allow-all"],
884
+ toolPolicy: ["ask"],
876
885
  },
877
886
  };
878
887
  }
@@ -1153,4 +1162,9 @@ function createKiroAdapter(opts) {
1153
1162
 
1154
1163
  module.exports = {
1155
1164
  createKiroAdapter: createKiroAdapter,
1165
+ contractTestKit: {
1166
+ createEventState: createEventState,
1167
+ createQueryHandle: createKiroQueryHandle,
1168
+ normalizeEvent: flattenUpdate,
1169
+ },
1156
1170
  };