clay-server 4.1.0-beta.8 → 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 (119) 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-pending-message-queue.js +71 -7
  20. package/lib/project-scheduled-messages.js +235 -52
  21. package/lib/project-scheduled-result-store.js +131 -0
  22. package/lib/project-scheduled-task-execution.js +35 -2
  23. package/lib/project-session-notes.js +11 -4
  24. package/lib/project-sessions.js +7 -0
  25. package/lib/project-user-message.js +72 -5
  26. package/lib/project.js +60 -5
  27. package/lib/public/app.js +27 -12
  28. package/lib/public/css/avatar-imprints.css +1 -1
  29. package/lib/public/css/command-palette.css +20 -0
  30. package/lib/public/css/git-panel.css +13 -12
  31. package/lib/public/css/git-placard.css +4 -4
  32. package/lib/public/css/input.css +0 -109
  33. package/lib/public/css/issues.css +78 -31
  34. package/lib/public/css/mobile-nav.css +7 -0
  35. package/lib/public/css/notifications-center.css +3 -3
  36. package/lib/public/css/pending-message-queue.css +3 -0
  37. package/lib/public/css/project-logs.css +15 -0
  38. package/lib/public/css/sidebar.css +7 -0
  39. package/lib/public/css/sticky-notes.css +29 -0
  40. package/lib/public/modules/app-connection.js +9 -1
  41. package/lib/public/modules/app-favicon.js +10 -0
  42. package/lib/public/modules/app-messages.js +49 -37
  43. package/lib/public/modules/app-notifications.js +2 -0
  44. package/lib/public/modules/app-panels.js +77 -23
  45. package/lib/public/modules/app-projects.js +3 -0
  46. package/lib/public/modules/app-rate-limit.js +148 -243
  47. package/lib/public/modules/avatar-imprint.js +25 -70
  48. package/lib/public/modules/avatar.js +12 -4
  49. package/lib/public/modules/chat-render-runtime.js +20 -2
  50. package/lib/public/modules/clay-issue-links.js +23 -2
  51. package/lib/public/modules/clay-log-links.js +11 -1
  52. package/lib/public/modules/context-view-preference.js +143 -0
  53. package/lib/public/modules/default-vendor.js +116 -0
  54. package/lib/public/modules/filebrowser-tabs.js +1 -0
  55. package/lib/public/modules/filebrowser.js +45 -18
  56. package/lib/public/modules/input.js +81 -20
  57. package/lib/public/modules/issues-render.js +36 -10
  58. package/lib/public/modules/issues.js +95 -13
  59. package/lib/public/modules/markdown.js +3 -1
  60. package/lib/public/modules/pane-bridge.js +14 -8
  61. package/lib/public/modules/pane-issue-bridge.js +27 -0
  62. package/lib/public/modules/pending-message-queue.js +43 -1
  63. package/lib/public/modules/permission-hints.js +13 -0
  64. package/lib/public/modules/profile.js +9 -9
  65. package/lib/public/modules/project-activation.js +49 -0
  66. package/lib/public/modules/project-logs-render.js +13 -3
  67. package/lib/public/modules/project-logs.js +45 -15
  68. package/lib/public/modules/right-workbench.js +44 -0
  69. package/lib/public/modules/scheduled-message-state.js +12 -36
  70. package/lib/public/modules/scheduled-result-client.js +33 -0
  71. package/lib/public/modules/scheduled-result-display.js +50 -0
  72. package/lib/public/modules/scheduled-result-notification.js +31 -0
  73. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  74. package/lib/public/modules/scheduled-tasks.js +9 -21
  75. package/lib/public/modules/sidebar-mobile.js +51 -3
  76. package/lib/public/modules/sidebar-sessions.js +65 -5
  77. package/lib/public/modules/split-session-boundary.js +28 -0
  78. package/lib/public/modules/split-view.js +42 -7
  79. package/lib/public/modules/sticky-notes-browser.js +130 -18
  80. package/lib/public/modules/sticky-notes.js +2 -3
  81. package/lib/public/modules/terminal.js +4 -4
  82. package/lib/public/modules/thinking-lifecycle.js +4 -1
  83. package/lib/public/modules/tools.js +5 -1
  84. package/lib/scheduled-run-state.js +10 -2
  85. package/lib/scheduler.js +1 -0
  86. package/lib/sdk-bridge.js +97 -61
  87. package/lib/sdk-message-processor.js +44 -13
  88. package/lib/sdk-skill-discovery.js +6 -28
  89. package/lib/sdk-turn-queue.js +14 -0
  90. package/lib/server-context-view.js +53 -0
  91. package/lib/server-default-vendor.js +60 -0
  92. package/lib/server.js +23 -6
  93. package/lib/session-notes-mcp-server.js +3 -5
  94. package/lib/session-pair-factory.js +2 -2
  95. package/lib/session-pair-mcp-server.js +5 -4
  96. package/lib/session-title-generator.js +89 -0
  97. package/lib/session-title-policy.js +8 -2
  98. package/lib/sessions.js +26 -1
  99. package/lib/user-presence.js +6 -0
  100. package/lib/users-context-view-preferences.js +65 -0
  101. package/lib/users-default-vendor-preferences.js +80 -0
  102. package/lib/users.js +12 -0
  103. package/lib/ws-schema.js +11 -0
  104. package/lib/yoke/acp-agent-profiles.js +15 -2
  105. package/lib/yoke/adapters/acp.js +3 -2
  106. package/lib/yoke/adapters/antigravity.js +2 -2
  107. package/lib/yoke/adapters/claude-worker.js +11 -12
  108. package/lib/yoke/adapters/claude.js +75 -22
  109. package/lib/yoke/adapters/codex.js +216 -29
  110. package/lib/yoke/adapters/kiro.js +6 -5
  111. package/lib/yoke/claude-context-usage.js +57 -0
  112. package/lib/yoke/claude-message-correlation.js +31 -0
  113. package/lib/yoke/claude-model-switch.js +15 -0
  114. package/lib/yoke/index.js +5 -1
  115. package/lib/yoke/mcp-bridge-server.js +18 -5
  116. package/lib/yoke/session-skill-catalog.js +45 -0
  117. package/lib/yoke/skill-discovery.js +60 -20
  118. package/lib/yoke/vendor-registry.js +1 -1
  119. package/package.json +5 -7
@@ -0,0 +1,80 @@
1
+ // Durable user-scoped New session vendor preference storage.
2
+
3
+ var PREFERENCE_KEY = "defaultVendorPreference";
4
+
5
+ function validVendor(value) {
6
+ return typeof value === "string" && /^[a-z0-9][a-z0-9_-]{0,39}$/.test(value);
7
+ }
8
+
9
+ function attachDefaultVendorPreferences(deps) {
10
+ var loadUsers = deps.loadUsers;
11
+ var saveUsers = deps.saveUsers;
12
+ var isMultiUser = deps.isMultiUser || function () { return false; };
13
+ var configModule = deps.configModule || require("./config");
14
+
15
+ function getRecord(data, userId) {
16
+ var records = Array.isArray(data.users) ? data.users : [];
17
+ for (var i = 0; i < records.length; i++) if (records[i] && records[i].id === userId) return records[i];
18
+ return null;
19
+ }
20
+
21
+ function readValue(value, source) {
22
+ if (value === undefined || value === null || value === "") return { present: false, vendor: null, source: null };
23
+ if (!validVendor(value)) return { present: true, vendor: null, source: source, error: "Default coding vendor is invalid." };
24
+ return { present: true, vendor: value, source: source };
25
+ }
26
+
27
+ function getDefaultVendor(userId) {
28
+ var data;
29
+ try { data = loadUsers(); } catch (error) { return { present: false, vendor: null, source: null, error: "Unable to read default coding vendor: " + (error.message || error) }; }
30
+ var record = getRecord(data, userId);
31
+ if (isMultiUser() && !record) return { present: false, vendor: null, source: null, error: "User not found." };
32
+ if (record && Object.prototype.hasOwnProperty.call(record, PREFERENCE_KEY)) return readValue(record[PREFERENCE_KEY], "explicit");
33
+ if (record) return { present: false, vendor: null, source: null };
34
+ if (userId === "default") {
35
+ var config;
36
+ try { config = configModule.loadConfig() || {}; } catch (configError) { return { present: false, vendor: null, source: null, error: "Unable to read default coding vendor: " + (configError.message || configError) }; }
37
+ if (Object.prototype.hasOwnProperty.call(config, PREFERENCE_KEY)) return readValue(config[PREFERENCE_KEY], "explicit");
38
+ }
39
+ return { present: false, vendor: null, source: null };
40
+ }
41
+
42
+ function setDefaultVendor(userId, vendor) {
43
+ if (vendor !== "" && !validVendor(vendor)) return { ok: false, error: "Default coding vendor is invalid." };
44
+ var data;
45
+ try { data = loadUsers(); } catch (error) { return { ok: false, error: "Unable to read default coding vendor: " + (error.message || error) }; }
46
+ var record = getRecord(data, userId);
47
+ if (isMultiUser() && !record) return { ok: false, error: "User not found." };
48
+ if (record) {
49
+ var nextData = Object.assign({}, data, { users: (data.users || []).map(function (item) {
50
+ if (item !== record) return item;
51
+ var next = Object.assign({}, item);
52
+ if (vendor) next.defaultVendorPreference = vendor;
53
+ else delete next.defaultVendorPreference;
54
+ return next;
55
+ }) });
56
+ try { saveUsers(nextData); } catch (saveError) { return { ok: false, error: "Unable to save default coding vendor: " + (saveError.message || saveError) }; }
57
+ if (!vendor && userId === "default") {
58
+ try {
59
+ var recordConfig = configModule.loadConfig() || {};
60
+ delete recordConfig[PREFERENCE_KEY];
61
+ configModule.saveConfig(Object.assign({}, recordConfig));
62
+ } catch (recordConfigError) { return { ok: false, error: "Unable to clear default coding vendor: " + (recordConfigError.message || recordConfigError) }; }
63
+ }
64
+ return { ok: true, vendor: vendor };
65
+ }
66
+ if (userId !== "default") return { ok: false, error: "User not found." };
67
+ try {
68
+ var current = configModule.loadConfig() || {};
69
+ var nextConfig = Object.assign({}, current);
70
+ if (vendor) nextConfig.defaultVendorPreference = vendor;
71
+ else delete nextConfig.defaultVendorPreference;
72
+ configModule.saveConfig(nextConfig);
73
+ return { ok: true, vendor: vendor };
74
+ } catch (configSaveError) { return { ok: false, error: "Unable to save default coding vendor: " + (configSaveError.message || configSaveError) }; }
75
+ }
76
+
77
+ return { getDefaultVendor: getDefaultVendor, setDefaultVendor: setDefaultVendor, validVendor: validVendor };
78
+ }
79
+
80
+ module.exports = { attachDefaultVendorPreferences: attachDefaultVendorPreferences, validVendor: validVendor, PREFERENCE_KEY: PREFERENCE_KEY };
package/lib/users.js CHANGED
@@ -11,6 +11,8 @@ var { attachHomeSurfacePreferences } = require("./users-home-surface-preferences
11
11
  var { attachExperimentalPreferences } = require("./users-experimental-preferences");
12
12
  var { attachScheduledTaskPreferences } = require("./users-scheduled-task-preferences");
13
13
  var { attachDefaultAiPreferences } = require("./users-default-ai-preferences");
14
+ var { attachDefaultVendorPreferences } = require("./users-default-vendor-preferences");
15
+ var { attachContextViewPreferences } = require("./users-context-view-preferences");
14
16
 
15
17
  var USERS_FILE = path.join(CONFIG_DIR, "users.json");
16
18
 
@@ -404,6 +406,8 @@ var homeSurfacePreferences = attachHomeSurfacePreferences({ loadUsers: loadUsers
404
406
  var experimentalPreferences = attachExperimentalPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
405
407
  var scheduledTaskPreferences = attachScheduledTaskPreferences({ loadUsers: loadUsers, saveUsers: saveUsers });
406
408
  var defaultAiPreferences = attachDefaultAiPreferences({ loadUsers: loadUsers, saveUsers: saveUsers, isMultiUser: auth.isMultiUser });
409
+ var defaultVendorPreferences = attachDefaultVendorPreferences({ loadUsers: loadUsers, saveUsers: saveUsers, isMultiUser: auth.isMultiUser });
410
+ var contextViewPreferences = attachContextViewPreferences({ loadUsers: loadUsers, saveUsers: saveUsers, isMultiUser: auth.isMultiUser });
407
411
 
408
412
  // Alias auth functions
409
413
  var isMultiUser = auth.isMultiUser;
@@ -464,6 +468,10 @@ var getScheduledTaskInterviewEngine = scheduledTaskPreferences.get;
464
468
  var setScheduledTaskInterviewEngine = scheduledTaskPreferences.set;
465
469
  var getDefaultAiPreference = defaultAiPreferences.getDefaultAiPreference;
466
470
  var setDefaultAiPreference = defaultAiPreferences.setDefaultAiPreference;
471
+ var getDefaultVendorPreference = defaultVendorPreferences.getDefaultVendor;
472
+ var setDefaultVendorPreference = defaultVendorPreferences.setDefaultVendor;
473
+ var getContextViewPreference = contextViewPreferences.get;
474
+ var setContextViewPreference = contextViewPreferences.set;
467
475
 
468
476
  module.exports = {
469
477
  USERS_FILE: USERS_FILE,
@@ -545,6 +553,10 @@ module.exports = {
545
553
  setScheduledTaskInterviewEngine: setScheduledTaskInterviewEngine,
546
554
  getDefaultAiPreference: getDefaultAiPreference,
547
555
  setDefaultAiPreference: setDefaultAiPreference,
556
+ getDefaultVendorPreference: getDefaultVendorPreference,
557
+ setDefaultVendorPreference: setDefaultVendorPreference,
558
+ getContextViewPreference: getContextViewPreference,
559
+ setContextViewPreference: setContextViewPreference,
548
560
  getDeletedBuiltinKeys: getDeletedBuiltinKeys,
549
561
  addDeletedBuiltinKey: addDeletedBuiltinKey,
550
562
  removeDeletedBuiltinKey: removeDeletedBuiltinKey,
package/lib/ws-schema.js CHANGED
@@ -81,6 +81,11 @@ var schema = {
81
81
  "scheduled_task_update": { direction: "c2s", handler: "lib/project-scheduled-tasks.js", description: "Update an exact schedule revision" },
82
82
  "scheduled_task_cancel": { direction: "c2s", handler: "lib/project-scheduled-tasks.js", description: "Cancel the active schedule interview or draft" },
83
83
  "scheduled_task_action_result": { direction: "s2c", handler: "lib/public/modules/scheduled-tasks.js", description: "Correlated create or edit result" },
84
+ "scheduled_task_results_state": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Authorized per-user unread Scheduled Tasks result state" },
85
+ "scheduled_task_result_created": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Live terminal Scheduled Tasks result notification" },
86
+ "scheduled_task_result_mark_read": { direction: "c2s", handler: "lib/project-logs.js", description: "Mark an exact displayed scheduled result read for the current user" },
87
+ "scheduled_task_result_open_session": { direction: "c2s", handler: "lib/project-logs.js", description: "Resolve the hidden Driver for an authorized scheduled result" },
88
+ "scheduled_task_result_session": { direction: "s2c", handler: "lib/public/modules/project-logs.js", description: "Authorized hidden scheduled Driver navigation target" },
84
89
  "search_results": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Session title search results" },
85
90
  "search_content_results": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full-text content search results" },
86
91
  "fork_complete": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Fork succeeded, includes new session ID" },
@@ -718,6 +723,12 @@ var schema = {
718
723
  "default_ai_get": { direction: "c2s", handler: "lib/server-default-ai.js", description: "Read the authenticated user's shared Default AI and available providers" },
719
724
  "default_ai_catalog_get": { direction: "c2s", handler: "lib/server-default-ai.js", description: "Read one installed provider catalog for the Default AI picker" },
720
725
  "default_ai_set": { direction: "c2s", handler: "lib/server-default-ai.js", description: "Validate and persist the authenticated user's shared Default AI" },
726
+ "default_vendor_get": { direction: "c2s", handler: "lib/server-default-vendor.js", description: "Read the authenticated user's New session vendor preference" },
727
+ "default_vendor_set": { direction: "c2s", handler: "lib/server-default-vendor.js", description: "Validate and persist the authenticated user's New session vendor preference" },
728
+ "default_vendor_state": { direction: "s2c", handler: "lib/public/modules/default-vendor.js", description: "Correlated or same-user-broadcast New session vendor preference" },
729
+ "context_view_get": { direction: "c2s", handler: "lib/server-context-view.js", description: "Read the authenticated user's context display preference" },
730
+ "context_view_set": { direction: "c2s", handler: "lib/server-context-view.js", description: "Persist the authenticated user's context display preference" },
731
+ "context_view_state": { direction: "s2c", handler: "lib/public/modules/context-view-preference.js", description: "Correlated or same-user-broadcast context display preference" },
721
732
  "default_ai_state": { direction: "s2c", handler: "lib/public/modules/default-ai.js", description: "Correlated or same-user-broadcast canonical Default AI state" },
722
733
  "default_ai_catalog": { direction: "s2c", handler: "lib/public/modules/default-ai.js", description: "Correlated Default AI provider catalog" },
723
734
  "home_mate_history": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's initial or refreshed chat history" },
@@ -82,13 +82,26 @@ function isSafeOpenCodePermission(value) {
82
82
  }
83
83
 
84
84
  function validateOpenCodeConfig(config) {
85
- if (!config || config.permission !== "ask") {
85
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
86
+ throw new Error("OpenCode resolved configuration is malformed");
87
+ }
88
+ var globalPermission = config.permission;
89
+ var globalAsk = globalPermission === "ask" || (
90
+ isSafeOpenCodePermission(globalPermission) && globalPermission["*"] === "ask"
91
+ );
92
+ if (!globalAsk) {
86
93
  throw new Error("OpenCode resolved configuration does not preserve global ask permissions");
87
94
  }
95
+ if (config.agent !== undefined && (!config.agent || typeof config.agent !== "object" || Array.isArray(config.agent))) {
96
+ throw new Error("OpenCode resolved configuration has malformed agents");
97
+ }
88
98
  var agents = config.agent || {};
89
99
  var names = Object.keys(agents);
90
100
  for (var i = 0; i < names.length; i++) {
91
- var agent = agents[names[i]] || {};
101
+ var agent = agents[names[i]];
102
+ if (!agent || typeof agent !== "object" || Array.isArray(agent)) {
103
+ throw new Error("OpenCode agent has malformed resolved configuration: " + names[i]);
104
+ }
92
105
  if (agent.permission === undefined) continue;
93
106
  if (!isSafeOpenCodePermission(agent.permission)) {
94
107
  throw new Error("OpenCode agent has unsafe resolved permissions: " + names[i]);
@@ -44,6 +44,7 @@ function createAcpAdapter(vendor, opts) {
44
44
  var defaultModel = driver.defaultModel || cachedModels[0] || "auto";
45
45
  var activeHandles = [];
46
46
  var lastActiveAt = Date.now();
47
+ var skillEnv = opts.env || process.env;
47
48
 
48
49
  function context(extra) {
49
50
  return Object.assign({
@@ -96,7 +97,7 @@ function createAcpAdapter(vendor, opts) {
96
97
  }
97
98
 
98
99
  function readyResult() {
99
- var skills = skillDiscovery.discoverSkills(cwd).map(function(skill) { return skill.name; });
100
+ var skills = skillDiscovery.discoverSkills(cwd, { env: skillEnv }).map(function(skill) { return skill.name; });
100
101
  var result = {
101
102
  models: cachedModels.slice(),
102
103
  defaultModel: defaultModel,
@@ -220,7 +221,7 @@ function createAcpAdapter(vendor, opts) {
220
221
  if (!initialized || !acp || !acp.started) await adapter.init(queryOpts);
221
222
  if (shuttingDown) throw new Error(driver.displayName + " adapter is shutting down");
222
223
  var controller = queryOpts.abortController || new AbortController();
223
- var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || cwd);
224
+ var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || cwd, queryOpts.skillOptions || { env: queryOpts.env || skillEnv });
224
225
  var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
225
226
  var acpOptions = (queryOpts.adapterOptions && queryOpts.adapterOptions.ACP) || {};
226
227
  var handle = null;
@@ -353,12 +353,12 @@ function createAntigravityAdapter(opts) {
353
353
 
354
354
  var adapter = {
355
355
  vendor: "antigravity",
356
- init: async function() {
356
+ init: async function(initOpts) {
357
357
  if (!binaryPath) binaryPath = findBinary();
358
358
  if (!binaryPath) throw new Error("Antigravity CLI binary not found: agy");
359
359
  var fetched = opts._fetchModels ? await opts._fetchModels(binaryPath, cwd) : await fetchModels(binaryPath, cwd);
360
360
  if (fetched && fetched.length) cachedModels = fetched;
361
- var skills = skillDiscovery.discoverSkills(cwd).map(function(skill) { return skill.name; });
361
+ var skills = skillDiscovery.discoverSkills(cwd, { env: (initOpts && initOpts.env) || null }).map(function(skill) { return skill.name; });
362
362
  return {
363
363
  models: cachedModels.slice(),
364
364
  defaultModel: cachedModels[0] || "auto",
@@ -16,6 +16,8 @@ try { require("fs").writeSync(2, "[sdk-worker] BOOT pid=" + process.pid + " uid=
16
16
  var net = require("net");
17
17
  var crypto = require("crypto");
18
18
  var path = require("path");
19
+ var claudeContextUsage = require(path.join(__dirname, "../claude-context-usage"));
20
+ var claudeModelSwitch = require(path.join(__dirname, "../claude-model-switch"));
19
21
  var fs = require("fs");
20
22
 
21
23
  var socketPath = process.argv[2];
@@ -265,6 +267,9 @@ function handleMessage(msg) {
265
267
  case "abort":
266
268
  if (abortController) abortController.abort();
267
269
  break;
270
+ case "get_context_usage":
271
+ claudeContextUsage.respond(queryInstance, msg, sendToDaemon);
272
+ break;
268
273
  case "set_model":
269
274
  handleSetModel(msg);
270
275
  break;
@@ -322,6 +327,8 @@ function canUseTool(toolName, input, opts) {
322
327
  input: input,
323
328
  toolUseId: opts.toolUseID || "",
324
329
  decisionReason: opts.decisionReason || "",
330
+ defaultToNo: opts.defaultToNo === true,
331
+ suppressAlwaysAllowRule: opts.suppressAlwaysAllowRule === true,
325
332
  });
326
333
  return new Promise(function(resolve) {
327
334
  pendingPermissions[requestId] = resolve;
@@ -544,6 +551,10 @@ async function handleQueryStart(msg) {
544
551
  return onUserDialog(request, dialogOpts);
545
552
  };
546
553
 
554
+ if (options.reportModelSwitches) {
555
+ claudeModelSwitch.attach(options, function(change) { sendToDaemon({ type: "model_switched", change: change }); });
556
+ }
557
+ delete options.reportModelSwitches;
547
558
  perf("creating query instance");
548
559
  try {
549
560
  queryInstance = sdk.query({
@@ -600,18 +611,6 @@ async function handleQueryStart(msg) {
600
611
  }
601
612
  sendToDaemon({ type: "sdk_event", event: event });
602
613
  }
603
- perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "), fetching context usage");
604
- // Fetch context usage breakdown before queryInstance is cleared
605
- try {
606
- if (queryInstance && typeof queryInstance.getContextUsage === "function") {
607
- var ctxUsage = await queryInstance.getContextUsage();
608
- sendToDaemon({ type: "context_usage", data: ctxUsage });
609
- perf("context usage sent");
610
- }
611
- } catch (e) {
612
- // Non-fatal: SDK may have already shut down
613
- console.error("[sdk-worker] getContextUsage failed (non-fatal):", e.message);
614
- }
615
614
  perf("sending query_done");
616
615
  sendToDaemon({ type: "query_done" });
617
616
  } catch (err) {
@@ -13,6 +13,9 @@ var { spawn } = require("child_process");
13
13
  var { resolveOsUserInfo } = require("../../os-users");
14
14
  var skillDiscovery = require("../skill-discovery");
15
15
  var claudeUserInput = require("../claude-user-input");
16
+ var claudeCorrelation = require("../claude-message-correlation");
17
+ var claudeContextUsage = require("../claude-context-usage");
18
+ var claudeModelSwitch = require("../claude-model-switch");
16
19
 
17
20
  // SDK-specific knowledge stays in the adapter (not the relay): the host dialog
18
21
  // kinds this client can render, declared to the SDK when an onUserDialog
@@ -53,20 +56,39 @@ var PER_TASK_STOP_AFFORDANCE = true;
53
56
  // queryOpts.systemPromptSnapshot. A bare string prompt cannot be recorded, so
54
57
  // it is promoted to the custom form the SDK requires.
55
58
  function applySystemPromptSnapshot(systemPrompt, enabled) {
56
- if (!enabled || !systemPrompt) return systemPrompt;
59
+ if (!systemPrompt) return systemPrompt;
60
+ enabled = enabled === true;
57
61
  if (typeof systemPrompt === "string" || Array.isArray(systemPrompt)) {
58
- return { type: "custom", prompt: systemPrompt, snapshot: true };
62
+ return { type: "custom", prompt: systemPrompt, snapshot: enabled };
59
63
  }
60
64
  if (typeof systemPrompt === "object") {
61
- return Object.assign({}, systemPrompt, { snapshot: true });
65
+ return Object.assign({}, systemPrompt, { snapshot: enabled });
62
66
  }
63
67
  return systemPrompt;
64
68
  }
65
69
 
66
- function getSharedSkillPlugins(homeDir, existingPlugins) {
70
+ function getSharedSkillPlugins(homeDir, existingPlugins, cwd, env) {
67
71
  var plugins = Array.isArray(existingPlugins) ? existingPlugins.slice() : [];
68
- var bridgePath = skillDiscovery.ensureClaudeCodexPlugin(homeDir ? { homeDir: homeDir } : null);
72
+ // undefined means normal in-process Claude and may use the configured
73
+ // daemon home; null means a failed mapped-user resolution and must fail
74
+ // closed without constructing any personal bridges.
75
+ if (homeDir === null) return plugins;
76
+ var effectiveHome = homeDir === undefined ? require("../../config").REAL_HOME || os.homedir() : homeDir;
77
+ var rootOptions = { homeDir: effectiveHome, env: env, scopedHome: !!homeDir };
78
+ var roots = skillDiscovery.getSkillRoots(cwd, rootOptions);
79
+ var codexRoot = roots.filter(function (root) { return root.source === "codex-user"; })[0];
80
+ var codexOpts = codexRoot ? { homeDir: effectiveHome, source: codexRoot.path, name: "codex" } : null;
81
+ if (codexRoot && codexRoot.path !== path.join(effectiveHome, ".codex", "skills")) {
82
+ codexOpts.name = "codex-" + crypto.createHash("sha256").update(codexRoot.path).digest("hex").slice(0, 12);
83
+ }
84
+ var bridgePath = skillDiscovery.ensureClaudeCodexPlugin(codexOpts);
69
85
  if (bridgePath) plugins.push({ type: "local", path: bridgePath, skipMcpDiscovery: true });
86
+ for (var i = 0; i < roots.length; i++) {
87
+ if (roots[i].source === "codex-user" || roots[i].source === "claude-user" || roots[i].source === "claude-project") continue;
88
+ var rootId = crypto.createHash("sha256").update(roots[i].source + "\0" + path.resolve(roots[i].path)).digest("hex").slice(0, 12);
89
+ var sharedPath = skillDiscovery.ensureClaudeCodexPlugin({ homeDir: effectiveHome, source: roots[i].path, name: "shared-" + roots[i].source + "-" + rootId });
90
+ if (sharedPath) plugins.push({ type: "local", path: sharedPath, skipMcpDiscovery: true });
91
+ }
70
92
  return plugins;
71
93
  }
72
94
 
@@ -88,16 +110,18 @@ function buildWorkerQueryOptions(queryOpts, claudeOpts, workerCwd) {
88
110
  if (claudeOpts.permissionMode) queryOptions.permissionMode = claudeOpts.permissionMode;
89
111
  if (claudeOpts.allowDangerouslySkipPermissions) queryOptions.allowDangerouslySkipPermissions = true;
90
112
  if (claudeOpts.settings) queryOptions.settings = claudeOpts.settings;
91
- if (queryOpts.appendSystemPrompt) {
113
+ if (queryOpts.systemPrompt) queryOptions.systemPrompt = applySystemPromptSnapshot(queryOpts.systemPrompt, queryOpts.systemPromptSnapshot);
114
+ if (!queryOpts.systemPrompt && queryOpts.appendSystemPrompt) {
92
115
  queryOptions.systemPrompt = applySystemPromptSnapshot(
93
116
  { type: "preset", preset: "claude_code", append: queryOpts.appendSystemPrompt },
94
117
  queryOpts.systemPromptSnapshot
95
118
  );
96
119
  }
97
- queryOptions.plugins = queryOpts.skipSkills ? (claudeOpts.plugins || []) : getSharedSkillPlugins(claudeOpts.originalHome, claudeOpts.plugins);
120
+ queryOptions.plugins = queryOpts.skipSkills ? (claudeOpts.plugins || []) : getSharedSkillPlugins(claudeOpts.originalHome, claudeOpts.plugins, workerCwd, queryOpts.env);
98
121
  if (queryOpts.toolServerDescriptors) queryOptions.mcpServerDescriptors = queryOpts.toolServerDescriptors;
99
122
  if (queryOpts.onUserDialog) queryOptions.supportedDialogKinds = SUPPORTED_DIALOG_KINDS;
100
123
  queryOptions.perTaskStopAffordance = PER_TASK_STOP_AFFORDANCE;
124
+ if (queryOpts.onModelSwitch) queryOptions.reportModelSwitches = true;
101
125
  if (queryOpts.model) queryOptions.model = queryOpts.model;
102
126
  if (queryOpts.effort) queryOptions.effort = queryOpts.effort;
103
127
  if (queryOpts.resumeSessionId) queryOptions.resume = queryOpts.resumeSessionId;
@@ -203,6 +227,11 @@ function createMessageQueue() {
203
227
  function flattenEvent(raw) {
204
228
  // session_id and uuid are cross-cutting: attach to any event that has them
205
229
  var base = {};
230
+ if (raw.user_message_uuid) base.userMessageId = raw.user_message_uuid;
231
+ if (Array.isArray(raw.user_message_uuids)) base.userMessageIds = raw.user_message_uuids.slice();
232
+ if (raw.resume_reason) base.resumeReason = raw.resume_reason;
233
+ if (Number.isInteger(raw.result_index)) base.resultIndex = raw.result_index;
234
+ if (raw.local_command) base.localCommand = raw.local_command;
206
235
  if (raw.session_id) base.sessionId = raw.session_id;
207
236
  if (raw.uuid) {
208
237
  base.uuid = raw.uuid;
@@ -418,6 +447,7 @@ function flattenEvent(raw) {
418
447
  // --- result ---
419
448
  if (raw.type === "result") {
420
449
  base.yokeType = "result";
450
+ if (Number.isInteger(raw.queued_turn_count) && raw.queued_turn_count >= 0) base.queuedTurnCount = raw.queued_turn_count;
421
451
  base.cost = raw.total_cost_usd;
422
452
  base.duration = raw.duration_ms;
423
453
  base.usage = raw.usage || null;
@@ -517,6 +547,7 @@ function normalizeBackgroundTasks(tasks) {
517
547
  // Wraps a raw SDK query object with the YOKE QueryHandle interface.
518
548
  // Events are flattened via flattenEvent before yielding.
519
549
  function createQueryHandle(rawQuery, messageQueue, abortController) {
550
+ var correlation = claudeCorrelation.createCorrelation();
520
551
  var closed = false;
521
552
  var handle = {
522
553
  // Opaque adapter state (null for in-process queries)
@@ -530,7 +561,7 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
530
561
  return rawIter.next().then(function(result) {
531
562
  if (result.done) closed = true;
532
563
  if (result.done) return result;
533
- return { value: flattenEvent(result.value), done: false };
564
+ return { value: correlation.result(result.value, flattenEvent(result.value)), done: false };
534
565
  }, function(err) {
535
566
  closed = true;
536
567
  throw err;
@@ -539,6 +570,8 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
539
570
  };
540
571
  },
541
572
 
573
+ getSubmittedMessageCount: correlation.submitted,
574
+
542
575
  pushMessage: function(text, images) {
543
576
  if (closed) return false;
544
577
  var content = [];
@@ -551,7 +584,10 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
551
584
  }
552
585
  }
553
586
  if (text) content.push({ type: "text", text: text });
554
- return messageQueue.push({ type: "user", message: { role: "user", content: content } });
587
+ var message = correlation.message(content);
588
+ var accepted = messageQueue.push(message);
589
+ if (accepted) correlation.accepted(message);
590
+ return accepted;
555
591
  },
556
592
 
557
593
  setModel: function(model) {
@@ -605,9 +641,9 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
605
641
  return Promise.resolve(null);
606
642
  },
607
643
 
608
- getContextUsage: function() {
644
+ getContextUsage: function(options) {
609
645
  if (rawQuery && typeof rawQuery.getContextUsage === "function") {
610
- return rawQuery.getContextUsage();
646
+ return rawQuery.getContextUsage(claudeContextUsage.options(options));
611
647
  }
612
648
  return Promise.resolve(null);
613
649
  },
@@ -979,7 +1015,9 @@ function serializeWorkerValue(value, seen) {
979
1015
  // in-process QueryHandle. This allows processQueryStream to iterate a worker
980
1016
  // query identically to an in-process query.
981
1017
 
982
- function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest) {
1018
+ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest, onModelSwitch) {
1019
+ var contextRequests = claudeContextUsage.createRequests(worker);
1020
+ var correlation = claudeCorrelation.createCorrelation();
983
1021
  // Async iterable state
984
1022
  var iterQueue = [];
985
1023
  var iterWaiting = null;
@@ -1003,6 +1041,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1003
1041
  }
1004
1042
 
1005
1043
  function endIter() {
1044
+ contextRequests.close();
1006
1045
  if (iterEnded) return;
1007
1046
  iterEnded = true;
1008
1047
  if (iterWaiting) {
@@ -1013,6 +1052,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1013
1052
  }
1014
1053
 
1015
1054
  function errorIter(err) {
1055
+ contextRequests.close();
1016
1056
  if (iterEnded) return;
1017
1057
  iterEnded = true;
1018
1058
  iterError = err;
@@ -1028,7 +1068,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1028
1068
  worker.onMessage(function(msg) {
1029
1069
  switch (msg.type) {
1030
1070
  case "sdk_event":
1031
- pushToIter(flattenEvent(msg.event));
1071
+ pushToIter(correlation.result(msg.event, flattenEvent(msg.event)));
1032
1072
  break;
1033
1073
 
1034
1074
  case "permission_request":
@@ -1036,6 +1076,8 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1036
1076
  canUseTool(msg.toolName, msg.input, {
1037
1077
  toolUseID: msg.toolUseId,
1038
1078
  decisionReason: msg.decisionReason,
1079
+ defaultToNo: msg.defaultToNo === true,
1080
+ suppressAlwaysAllowRule: msg.suppressAlwaysAllowRule === true,
1039
1081
  signal: { addEventListener: function() {} },
1040
1082
  }).then(function(result) {
1041
1083
  worker.send({ type: "permission_response", requestId: msg.requestId, result: result });
@@ -1116,6 +1158,12 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1116
1158
  }
1117
1159
  break;
1118
1160
 
1161
+ case "model_switched":
1162
+ if (onModelSwitch) onModelSwitch(msg.change);
1163
+ break;
1164
+ case "context_usage_response":
1165
+ contextRequests.receive(msg);
1166
+ break;
1119
1167
  case "context_usage":
1120
1168
  case "model_changed":
1121
1169
  case "effort_changed":
@@ -1198,6 +1246,8 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1198
1246
  };
1199
1247
  },
1200
1248
 
1249
+ getSubmittedMessageCount: correlation.submitted,
1250
+
1201
1251
  pushMessage: function(text, images) {
1202
1252
  if (iterEnded || (worker.process && (worker.process.killed || worker.process.exitCode != null))) return false;
1203
1253
  var content = [];
@@ -1210,8 +1260,10 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1210
1260
  }
1211
1261
  }
1212
1262
  if (text) content.push({ type: "text", text: text });
1213
- var userMsg = { type: "user", message: { role: "user", content: content } };
1214
- return worker.send({ type: "push_message", content: userMsg }) === true;
1263
+ var userMsg = correlation.message(content);
1264
+ var accepted = worker.send({ type: "push_message", content: userMsg }) === true;
1265
+ if (accepted) correlation.accepted(userMsg);
1266
+ return accepted;
1215
1267
  },
1216
1268
 
1217
1269
  setModel: function(model) {
@@ -1250,8 +1302,8 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1250
1302
  return Promise.resolve(null);
1251
1303
  },
1252
1304
 
1253
- getContextUsage: function() {
1254
- return Promise.resolve(null);
1305
+ getContextUsage: function(options) {
1306
+ return iterEnded ? Promise.resolve(null) : contextRequests.request(options);
1255
1307
  },
1256
1308
 
1257
1309
  supportedModels: function() {
@@ -1389,7 +1441,7 @@ function createClaudeAdapter(opts) {
1389
1441
  abortController: ac,
1390
1442
  settings: { disableAllHooks: true },
1391
1443
  };
1392
- warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
1444
+ warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
1393
1445
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
1394
1446
 
1395
1447
  if (initOpts && initOpts.dangerouslySkipPermissions) {
@@ -1603,8 +1655,9 @@ function createClaudeAdapter(opts) {
1603
1655
  if (co.allowDangerouslySkipPermissions) sdkOptions.allowDangerouslySkipPermissions = true;
1604
1656
  if (co.resumeSessionAt) sdkOptions.resumeSessionAt = co.resumeSessionAt;
1605
1657
  if (co.settings) sdkOptions.settings = co.settings;
1606
- sdkOptions.plugins = queryOpts.skipSkills ? (co.plugins || []) : getSharedSkillPlugins(co.originalHome, co.plugins);
1658
+ sdkOptions.plugins = queryOpts.skipSkills ? (co.plugins || []) : getSharedSkillPlugins(co.originalHome, co.plugins, queryOpts.cwd || _cwd, queryOpts.env);
1607
1659
 
1660
+ claudeModelSwitch.attach(sdkOptions, queryOpts.onModelSwitch);
1608
1661
  var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
1609
1662
  return createQueryHandle(rawQuery, mq, ac);
1610
1663
  },
@@ -1742,7 +1795,7 @@ function createClaudeAdapter(opts) {
1742
1795
  // Create the worker query handle (sets up message handler on worker)
1743
1796
  var workerUserInputHandler = queryOpts.userInputMode === "fallback" ? null : queryOpts.onUserInputRequest;
1744
1797
  var workerElicitationHandler = queryOpts.userInputMode === "fallback" ? null : queryOpts.onElicitation;
1745
- var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, workerElicitationHandler, queryOpts.callMcpTool, queryOpts.onUserDialog, workerUserInputHandler);
1798
+ var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, workerElicitationHandler, queryOpts.callMcpTool, queryOpts.onUserDialog, workerUserInputHandler, queryOpts.onModelSwitch);
1746
1799
 
1747
1800
  // Wait for worker to be ready before sending query_start
1748
1801
  if (!reusingWorker) {
@@ -1862,7 +1915,7 @@ function createClaudeAdapter(opts) {
1862
1915
  abortController: ac,
1863
1916
  settings: { disableAllHooks: true },
1864
1917
  };
1865
- warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
1918
+ warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
1866
1919
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
1867
1920
 
1868
1921
  if (initOpts && initOpts.dangerouslySkipPermissions) {
@@ -1944,7 +1997,7 @@ function createClaudeAdapter(opts) {
1944
1997
  }
1945
1998
 
1946
1999
  var warmupOptions = { cwd: workerCwd, settingSources: ["user", "project", "local"], settings: { disableAllHooks: true } };
1947
- warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
2000
+ warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
1948
2001
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
1949
2002
  if (initOpts && initOpts.dangerouslySkipPermissions) {
1950
2003
  warmupOptions.permissionMode = "bypassPermissions";