clay-server 3.4.0-beta.2 → 3.4.0-beta.20

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 (80) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +35 -7
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-mate-interaction.js +21 -1
  6. package/lib/project-memory.js +3 -0
  7. package/lib/project-models.js +220 -0
  8. package/lib/project-session-handoff.js +162 -0
  9. package/lib/project-sessions.js +13 -45
  10. package/lib/project-worker-proposal.js +49 -14
  11. package/lib/project.js +38 -80
  12. package/lib/public/antigravity-avatar.png +0 -0
  13. package/lib/public/app.js +29 -0
  14. package/lib/public/copilot-avatar.svg +5 -0
  15. package/lib/public/css/command-palette.css +22 -2
  16. package/lib/public/css/icon-strip.css +29 -13
  17. package/lib/public/css/input.css +56 -0
  18. package/lib/public/css/mates.css +6 -0
  19. package/lib/public/css/menus.css +38 -25
  20. package/lib/public/css/messages.css +9 -0
  21. package/lib/public/css/pane.css +16 -3
  22. package/lib/public/css/pwa-mobile.css +17 -0
  23. package/lib/public/css/session-actions.css +98 -0
  24. package/lib/public/css/title-bar.css +19 -0
  25. package/lib/public/grok-avatar.svg +4 -0
  26. package/lib/public/index.html +29 -7
  27. package/lib/public/junie-avatar.svg +11 -0
  28. package/lib/public/kimi-avatar.svg +4 -0
  29. package/lib/public/modules/agent-config-selects.js +69 -0
  30. package/lib/public/modules/app-header.js +4 -22
  31. package/lib/public/modules/app-messages.js +54 -11
  32. package/lib/public/modules/app-panels.js +36 -83
  33. package/lib/public/modules/app-projects.js +3 -1
  34. package/lib/public/modules/app-rendering.js +19 -4
  35. package/lib/public/modules/background-tasks-ui.js +55 -0
  36. package/lib/public/modules/mate-sidebar.js +11 -3
  37. package/lib/public/modules/model-picker.js +263 -0
  38. package/lib/public/modules/notifications.js +7 -1
  39. package/lib/public/modules/pane-bridge.js +11 -0
  40. package/lib/public/modules/pane-links.js +23 -0
  41. package/lib/public/modules/project-switcher.js +7 -6
  42. package/lib/public/modules/session-actions.js +294 -0
  43. package/lib/public/modules/sidebar-mates.js +1 -1
  44. package/lib/public/modules/sidebar-mobile.js +2 -1
  45. package/lib/public/modules/sidebar-projects.js +34 -43
  46. package/lib/public/modules/sidebar-sessions.js +20 -6
  47. package/lib/public/modules/split-pair-ui.js +16 -77
  48. package/lib/public/modules/split-view.js +3 -0
  49. package/lib/public/modules/tools.js +6 -1
  50. package/lib/public/modules/vendor-priority.js +14 -0
  51. package/lib/public/modules/vendor-selection.js +20 -0
  52. package/lib/public/modules/worker-proposal.js +5 -1
  53. package/lib/public/modules/worktree-location.js +17 -0
  54. package/lib/public/qwen-avatar.svg +4 -0
  55. package/lib/public/style.css +4 -2
  56. package/lib/sdk-bridge.js +99 -56
  57. package/lib/sdk-message-processor.js +26 -4
  58. package/lib/session-handoff-context.js +110 -0
  59. package/lib/session-notes-mcp-server.js +8 -1
  60. package/lib/sessions.js +4 -0
  61. package/lib/worktree.js +9 -4
  62. package/lib/ws-schema.js +9 -1
  63. package/lib/yoke/acp-agent-profiles.js +64 -14
  64. package/lib/yoke/adapters/antigravity.js +417 -0
  65. package/lib/yoke/adapters/claude.js +34 -0
  66. package/lib/yoke/adapters/codex.js +101 -5
  67. package/lib/yoke/adapters/copilot.js +7 -0
  68. package/lib/yoke/adapters/grok.js +7 -0
  69. package/lib/yoke/adapters/junie.js +7 -0
  70. package/lib/yoke/adapters/kimi.js +7 -0
  71. package/lib/yoke/adapters/kiro.js +2 -2
  72. package/lib/yoke/adapters/qwen.js +7 -0
  73. package/lib/yoke/codex-app-server.js +25 -8
  74. package/lib/yoke/index.js +67 -28
  75. package/lib/yoke/instructions.js +3 -0
  76. package/lib/yoke/interface.js +12 -0
  77. package/lib/yoke/vendor-registry.js +61 -6
  78. package/package.json +1 -1
  79. package/lib/public/gemini-avatar.svg +0 -11
  80. package/lib/yoke/adapters/gemini.js +0 -7
@@ -1,7 +1,7 @@
1
1
  import { store } from './store.js';
2
2
  import { getWs } from './ws-ref.js';
3
- import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER } from './app-rendering.js';
4
- import { effortLevelsFor, effortDisplayName } from './app-panels.js';
3
+ import { VENDOR_AVATARS } from './app-rendering.js';
4
+ import { buildAgentVendorSelect, fillAgentModels, buildAgentEffortSelect, fillAgentEffort } from './agent-config-selects.js';
5
5
  import { showToast } from './utils.js';
6
6
 
7
7
  var pairDialog = null;
@@ -9,77 +9,14 @@ var pairDialog = null;
9
9
  // that session becomes the Driver and the dialog only configures the Worker.
10
10
  var pendingDriver = null;
11
11
 
12
- function optionValue(entry) {
13
- if (typeof entry === "string") return entry;
14
- return entry && (entry.value || entry.id) || "";
15
- }
16
-
17
- function optionLabel(entry) {
18
- if (typeof entry === "string") return entry;
19
- return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
20
- }
21
-
22
- function fillModels(select, vendor, options) {
23
- var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
24
- select.innerHTML = "";
25
- var automatic = document.createElement("option");
26
- automatic.value = "";
27
- automatic.textContent = "Automatic";
28
- select.appendChild(automatic);
29
- for (var i = 0; i < models.length; i++) {
30
- var option = document.createElement("option");
31
- option.value = optionValue(models[i]);
32
- option.textContent = optionLabel(models[i]);
33
- select.appendChild(option);
34
- }
35
- }
36
-
37
12
  function closePairDialog() {
38
13
  if (pairDialog) pairDialog.remove();
39
14
  pairDialog = null;
40
15
  }
41
16
 
42
- function buildVendorSelect(installed, preferred) {
43
- var select = document.createElement("select");
44
- select.className = "wt-modal-input";
45
- for (var i = 0; i < VENDOR_ORDER.length; i++) {
46
- if (installed.indexOf(VENDOR_ORDER[i]) === -1) continue;
47
- var option = document.createElement("option");
48
- option.value = VENDOR_ORDER[i];
49
- option.textContent = VENDOR_NAMES[VENDOR_ORDER[i]] || VENDOR_ORDER[i];
50
- select.appendChild(option);
51
- }
52
- if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
53
- return select;
54
- }
55
-
56
17
  // Effort levels are vendor- and model-specific (codex: minimal..xhigh,
57
18
  // claude/kiro: low..max, plus per-model supportedEffortLevels overrides), so
58
19
  // options are rebuilt whenever the role's vendor or model changes.
59
- function fillEffortOptions(select, vendor, options, modelValue) {
60
- var previous = select.value;
61
- var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
62
- var levels = effortLevelsFor(vendor, models, modelValue);
63
- select.innerHTML = "";
64
- var automatic = document.createElement("option");
65
- automatic.value = "";
66
- automatic.textContent = "Default";
67
- select.appendChild(automatic);
68
- for (var i = 0; i < levels.length; i++) {
69
- var option = document.createElement("option");
70
- option.value = levels[i];
71
- option.textContent = effortDisplayName(levels[i]);
72
- select.appendChild(option);
73
- }
74
- select.value = levels.indexOf(previous) !== -1 ? previous : "";
75
- }
76
-
77
- function buildEffortSelect() {
78
- var select = document.createElement("select");
79
- select.className = "wt-modal-input";
80
- return select;
81
- }
82
-
83
20
  function fieldLabel(text, control) {
84
21
  var label = document.createElement("label");
85
22
  label.className = "wt-modal-label";
@@ -154,25 +91,25 @@ export function showPairDialog(options) {
154
91
  : "One Driver plans and directs a visible Worker session.";
155
92
  modal.appendChild(desc);
156
93
 
157
- var driverVendor = buildVendorSelect(installed, installed.indexOf("claude") !== -1 ? "claude" : installed[0]);
94
+ var driverVendor = buildAgentVendorSelect(installed, installed.indexOf("claude") !== -1 ? "claude" : installed[0]);
158
95
  var workerDefault = options.lastVendor && options.lastVendor !== driverVendor.value ? options.lastVendor : (installed.indexOf("codex") !== -1 ? "codex" : installed[0]);
159
- var workerVendor = buildVendorSelect(installed, workerDefault);
96
+ var workerVendor = buildAgentVendorSelect(installed, workerDefault);
160
97
  var driverModel = document.createElement("select");
161
98
  driverModel.className = "wt-modal-input";
162
99
  var workerModel = document.createElement("select");
163
100
  workerModel.className = "wt-modal-input";
164
- var driverEffort = buildEffortSelect();
165
- var workerEffort = buildEffortSelect();
166
- function refreshDriverEffort() { fillEffortOptions(driverEffort, driverVendor.value, options, driverModel.value); }
167
- function refreshWorkerEffort() { fillEffortOptions(workerEffort, workerVendor.value, options, workerModel.value); }
168
- fillModels(driverModel, driverVendor.value, options);
169
- fillModels(workerModel, workerVendor.value, options);
101
+ var driverEffort = buildAgentEffortSelect();
102
+ var workerEffort = buildAgentEffortSelect();
103
+ function refreshDriverEffort() { fillAgentEffort(driverEffort, driverVendor.value, options, driverModel.value); }
104
+ function refreshWorkerEffort() { fillAgentEffort(workerEffort, workerVendor.value, options, workerModel.value); }
105
+ fillAgentModels(driverModel, driverVendor.value, options);
106
+ fillAgentModels(workerModel, workerVendor.value, options);
170
107
  refreshDriverEffort();
171
108
  refreshWorkerEffort();
172
109
  workerEffort.value = "medium";
173
110
  if (workerEffort.value !== "medium") workerEffort.value = "";
174
- driverVendor.addEventListener("change", function () { fillModels(driverModel, driverVendor.value, options); refreshDriverEffort(); });
175
- workerVendor.addEventListener("change", function () { fillModels(workerModel, workerVendor.value, options); refreshWorkerEffort(); });
111
+ driverVendor.addEventListener("change", function () { fillAgentModels(driverModel, driverVendor.value, options); refreshDriverEffort(); });
112
+ workerVendor.addEventListener("change", function () { fillAgentModels(workerModel, workerVendor.value, options); refreshWorkerEffort(); });
176
113
  driverModel.addEventListener("change", refreshDriverEffort);
177
114
  workerModel.addEventListener("change", refreshWorkerEffort);
178
115
 
@@ -318,7 +255,8 @@ export function syncPairChrome(host, split) {
318
255
  setBtn.title = "Make this session the Driver; the other pane becomes its Worker";
319
256
  setBtn.addEventListener("click", function () { sendSetPair(group.id, paneSessionId); });
320
257
  var titleEl = header.querySelector(".split-pane-title");
321
- header.insertBefore(setBtn, titleEl ? titleEl.nextSibling : null);
258
+ var accessEl = header.querySelector(".split-pane-full-access");
259
+ header.insertBefore(setBtn, accessEl ? accessEl.nextSibling : (titleEl ? titleEl.nextSibling : null));
322
260
  })(split.panes[ai].sessionId, headers[ai]);
323
261
  }
324
262
  return;
@@ -338,7 +276,8 @@ export function syncPairChrome(host, split) {
338
276
  sendSetPair(group.id, isDriver ? null : sessionId);
339
277
  });
340
278
  var title = header.querySelector(".split-pane-title");
341
- header.insertBefore(badge, title ? title.nextSibling : null);
279
+ var access = header.querySelector(".split-pane-full-access");
280
+ header.insertBefore(badge, access ? access.nextSibling : (title ? title.nextSibling : null));
342
281
  })(split.panes[pi].sessionId, headers[pi]);
343
282
  }
344
283
  var active = (store.get('splitDelegations') || {})[group.id];
@@ -231,6 +231,9 @@ function createPane(pane, index) {
231
231
  }, "Skip Permissions", true);
232
232
  });
233
233
  header.appendChild(fullAccess);
234
+ // Permission state belongs to the session identity on the left. Keep it
235
+ // immediately after the title instead of grouping it with usage and close.
236
+ header.insertBefore(fullAccess, ctxChip);
234
237
 
235
238
  var close = document.createElement("button");
236
239
  close.type = "button";
@@ -817,8 +817,13 @@ function resolvePermissionIdentity(mateId, vendor) {
817
817
  var vendorAvatars = {
818
818
  claude: "/claude-code-avatar.png",
819
819
  codex: "/codex-avatar.png",
820
- gemini: "/gemini-avatar.svg",
820
+ antigravity: "/antigravity-avatar.png",
821
821
  opencode: "/opencode-avatar.svg",
822
+ kimi: "/kimi-avatar.svg",
823
+ grok: "/grok-avatar.svg",
824
+ copilot: "/copilot-avatar.svg",
825
+ qwen: "/qwen-avatar.svg",
826
+ junie: "/junie-avatar.svg",
822
827
  kiro: "/kiro-avatar.svg",
823
828
  };
824
829
  var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
@@ -0,0 +1,14 @@
1
+ export var VENDOR_ORDER = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
2
+ export var EXPERIMENTAL_VENDORS = ["grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode"];
3
+
4
+ export function isExperimentalVendor(vendor) {
5
+ return EXPERIMENTAL_VENDORS.indexOf(vendor) !== -1;
6
+ }
7
+
8
+ export function firstInstalledVendor(installedVendors) {
9
+ var installed = Array.isArray(installedVendors) ? installedVendors : [];
10
+ for (var i = 0; i < VENDOR_ORDER.length; i++) {
11
+ if (installed.indexOf(VENDOR_ORDER[i]) !== -1) return VENDOR_ORDER[i];
12
+ }
13
+ return "";
14
+ }
@@ -0,0 +1,20 @@
1
+ import { store } from './store.js';
2
+ import { firstInstalledVendor } from './vendor-priority.js';
3
+
4
+ export function selectDefaultVendorForBlankSession() {
5
+ var state = store.snap();
6
+ if (!state.activeSessionId || state.sessionHasHistory || state.sessionVendorBound || state.dmMode) return "";
7
+ var vendor = firstInstalledVendor(state.installedVendors);
8
+ if (!vendor) return "";
9
+ if (vendor === state.currentVendor) {
10
+ if (state.vendorSelectionLocked) store.set({ vendorSelectionLocked: false });
11
+ return vendor;
12
+ }
13
+ store.set({
14
+ currentVendor: vendor,
15
+ currentModel: "",
16
+ currentModels: [],
17
+ vendorSelectionLocked: false,
18
+ });
19
+ return vendor;
20
+ }
@@ -103,8 +103,10 @@ function statusLabel(status) {
103
103
  function applyState(card, msg) {
104
104
  var status = msg.status || "pending";
105
105
  card.dataset.status = status;
106
+ if (msg.autoApproved) card.dataset.autoApproved = "true";
107
+ var autoApproved = card.dataset.autoApproved === "true";
106
108
  var badge = card.querySelector(".worker-proposal-status");
107
- if (badge) badge.textContent = statusLabel(status);
109
+ if (badge) badge.textContent = statusLabel(status) + (autoApproved ? " · auto-approved" : "");
108
110
  var error = card.querySelector(".worker-proposal-error");
109
111
  if (error) {
110
112
  error.textContent = msg.error || "";
@@ -116,6 +118,8 @@ function applyState(card, msg) {
116
118
  preview.classList.remove("hidden");
117
119
  }
118
120
  setControlsDisabled(card, status !== "pending");
121
+ var actions = card.querySelector(".worker-proposal-actions");
122
+ if (actions) actions.classList.toggle("hidden", autoApproved);
119
123
  }
120
124
 
121
125
  function sendDecision(card, accepted) {
@@ -0,0 +1,17 @@
1
+ export function isExternalWorktree(project) {
2
+ return !!(project && project.isWorktree && project.worktreeExternal === true);
3
+ }
4
+
5
+ export function externalWorktreeTooltip(name) {
6
+ return name + " — outside the main project folder";
7
+ }
8
+
9
+ export function appendExternalWorktreeBadge(container, className) {
10
+ var badge = document.createElement("span");
11
+ badge.className = className;
12
+ badge.setAttribute("role", "img");
13
+ badge.setAttribute("aria-label", "Outside the main project folder");
14
+ badge.innerHTML = '<i data-lucide="external-link"></i>';
15
+ container.appendChild(badge);
16
+ return badge;
17
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Qwen Code">
2
+ <rect width="64" height="64" rx="14" fill="#6b4eff"/>
3
+ <path d="M32 12a20 20 0 1 0 12 36l7 5-3-11a20 20 0 0 0-16-30zm0 9a11 11 0 1 1-7.8 18.8A11 11 0 0 1 32 21z" fill="#fff"/>
4
+ </svg>
@@ -1,12 +1,13 @@
1
1
  @import url("css/base.css");
2
2
  @import url("css/icon-strip.css");
3
- @import url("css/title-bar.css");
3
+ @import url("css/title-bar.css?v=20260824");
4
4
  @import url("css/pane.css");
5
5
  @import url("css/worker-proposal.css");
6
+ @import url("css/session-actions.css");
6
7
  @import url("css/sidebar.css");
7
8
  @import url("css/overlays.css");
8
9
  @import url("css/menus.css");
9
- @import url("css/messages.css?v=20260407");
10
+ @import url("css/messages.css?v=20260824");
10
11
  @import url("css/rewind.css");
11
12
  @import url("css/input.css");
12
13
  @import url("css/filebrowser.css");
@@ -35,3 +36,4 @@
35
36
  @import url("css/debate.css");
36
37
  @import url("css/notifications-center.css");
37
38
  @import url("css/tui-attention.css");
39
+ @import url("css/pwa-mobile.css?v=20260824f");
package/lib/sdk-bridge.js CHANGED
@@ -110,6 +110,7 @@ function createSDKBridge(opts) {
110
110
  var adapters = opts.adapters || {};
111
111
  var mateDisplayName = opts.mateDisplayName || "";
112
112
  var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
113
+ var osUsers = !!opts.osUsers;
113
114
  var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
114
115
  // mcpServers may be either a static object or a getter function. The
115
116
  // getter form lets callers gate individual servers at call time (e.g.
@@ -244,17 +245,24 @@ function createSDKBridge(opts) {
244
245
  return null;
245
246
  }
246
247
 
247
- function sendModelInfoForVendor(vendor, model) {
248
+ function defaultModelForVendor(vendor) {
249
+ return (sm.defaultModelByVendor && sm.defaultModelByVendor[vendor]) || "";
250
+ }
251
+
252
+ function sendModelInfoForVendor(session, vendor, model) {
248
253
  var resolvedVendor = vendor || (adapter && adapter.vendor) || "claude";
249
- send({
254
+ var message = {
250
255
  type: "model_info",
251
256
  model: model || "",
252
257
  models: getModelsForVendor(resolvedVendor),
253
258
  vendor: resolvedVendor,
259
+ sessionId: session ? session.localId : null,
254
260
  capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[resolvedVendor]) || {},
255
261
  availableVendors: sm.availableVendors || [],
256
262
  installedVendors: sm.installedVendors || [],
257
- });
263
+ };
264
+ if (session) sendToSession(session, message);
265
+ else send(message);
258
266
  }
259
267
 
260
268
  function rememberAdapterReady(vendor, result) {
@@ -323,6 +331,14 @@ function createSDKBridge(opts) {
323
331
  var mergeSkills = skills.mergeSkills;
324
332
 
325
333
  // --- Message processing (extracted to sdk-message-processor.js) ---
334
+ function getSessionLinuxUser(session) {
335
+ if (!osUsers || !session) return null;
336
+ if (session.lastLinuxUser) return session.lastLinuxUser;
337
+ if (!session.ownerId) return null;
338
+ var owner = usersModule.findUserById(session.ownerId);
339
+ return owner && owner.linuxUser ? owner.linuxUser : null;
340
+ }
341
+
326
342
  // Auto-generate a session title via YOKE adapter.generateTitle().
327
343
  // Triggered by sdk-message-processor after AUTO_TITLE_TURN_THRESHOLD turns.
328
344
  function autoGenerateTitle(session) {
@@ -345,7 +361,10 @@ function createSDKBridge(opts) {
345
361
  }
346
362
  console.log("[auto-title] Calling adapter.generateTitle with " + userMessages.length + " messages for session " + session.localId);
347
363
 
348
- sessionAdapter.generateTitle(userMessages, { cwd: cwd }).then(function(title) {
364
+ sessionAdapter.generateTitle(userMessages, {
365
+ cwd: cwd,
366
+ linuxUser: getSessionLinuxUser(session) || undefined,
367
+ }).then(function(title) {
349
368
  if (!title || title.length < 2) return;
350
369
  title = title.substring(0, 100);
351
370
  if (!session.titleManuallySet) {
@@ -837,19 +856,20 @@ function createSDKBridge(opts) {
837
856
  case "model_changed":
838
857
  session.model = metaData.model;
839
858
  sm.saveSessionFile(session);
840
- sm.currentModel = metaData.model;
841
- sendModelInfoForVendor(session.vendor || (adapter && adapter.vendor) || "claude", metaData.model);
842
- sendToSession(session, { type: "config_state", model: session.model || sm.currentModel, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
859
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
860
+ sm.defaultModelByVendor[session.vendor || "claude"] = metaData.model;
861
+ sendModelInfoForVendor(session, session.vendor || (adapter && adapter.vendor) || "claude", metaData.model);
862
+ 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", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
843
863
  break;
844
864
  case "effort_changed":
845
865
  session.effort = metaData.effort;
846
866
  sm.saveSessionFile(session);
847
867
  sm.currentEffort = metaData.effort;
848
- sendToSession(session, { type: "config_state", model: session.model || sm.currentModel || "", mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort, betas: sm.currentBetas || [] });
868
+ 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", effort: session.effort, betas: sm.currentBetas || [] });
849
869
  break;
850
870
  case "permission_mode_changed":
851
871
  sm.currentPermissionMode = metaData.mode;
852
- sendToSession(session, { type: "config_state", model: session.model || sm.currentModel || "", mode: session.permissionMode || sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
872
+ sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
853
873
  break;
854
874
  case "worker_error":
855
875
  send({ type: "error", text: metaData.error });
@@ -866,6 +886,8 @@ function createSDKBridge(opts) {
866
886
  var stillOwnsRuntime = session.queryInstance === myQueryInstance;
867
887
  if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
868
888
  session.isProcessing = false;
889
+ session._awaitingTurnResult = false;
890
+ session._queuedTurnCount = 0;
869
891
  onProcessingChanged();
870
892
  send({ type: "status", processing: false });
871
893
  sendAndRecord(session, { type: "thinking_stop" });
@@ -880,6 +902,8 @@ function createSDKBridge(opts) {
880
902
  // iterator without a result event. Treat any result-less stream end as
881
903
  // a failed turn so the UI cannot remain on processing forever.
882
904
  session.isProcessing = false;
905
+ session._awaitingTurnResult = false;
906
+ session._queuedTurnCount = 0;
883
907
  onProcessingChanged();
884
908
  if (!session._lastAdapterError) {
885
909
  sendAndRecord(session, {
@@ -894,6 +918,8 @@ function createSDKBridge(opts) {
894
918
  } catch (err) {
895
919
  if (session.isProcessing && session.queryInstance === myQueryInstance) {
896
920
  session.isProcessing = false;
921
+ session._awaitingTurnResult = false;
922
+ session._queuedTurnCount = 0;
897
923
  onProcessingChanged();
898
924
  if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
899
925
  if (!session.destroying) {
@@ -1131,7 +1157,9 @@ function createSDKBridge(opts) {
1131
1157
  async function rollbackConversation(session, numTurns) {
1132
1158
  var sessionAdapter = getAdapterForSession(session);
1133
1159
  if (sessionAdapter && typeof sessionAdapter.rollbackThread === "function") {
1134
- await sessionAdapter.rollbackThread(session.cliSessionId, numTurns);
1160
+ await sessionAdapter.rollbackThread(session.cliSessionId, numTurns, {
1161
+ linuxUser: getSessionLinuxUser(session) || undefined,
1162
+ });
1135
1163
  }
1136
1164
  // Claude: conversation rollback is handled by rewindFiles + local history trim
1137
1165
  }
@@ -1143,7 +1171,11 @@ function createSDKBridge(opts) {
1143
1171
 
1144
1172
  async function forkSessionUnified(session, uuid) {
1145
1173
  var sessionAdapter = getAdapterForSession(session);
1146
- var result = await sessionAdapter.forkSession(session.cliSessionId, { upToMessageId: uuid, dir: cwd });
1174
+ var result = await sessionAdapter.forkSession(session.cliSessionId, {
1175
+ upToMessageId: uuid,
1176
+ dir: cwd,
1177
+ linuxUser: getSessionLinuxUser(session) || undefined,
1178
+ });
1147
1179
  if (!result || !result.sessionId) throw new Error("Fork returned no session id");
1148
1180
 
1149
1181
  // Adapters with rollbackThread (e.g. Codex) use local history copy
@@ -1208,10 +1240,10 @@ function createSDKBridge(opts) {
1208
1240
  sm.modelsByVendor[vendor] = await vendorAdapter.supportedModels();
1209
1241
  }
1210
1242
  var vendorModels = getModelsForVendor(vendor);
1211
- var modelForVendor = resolveModelInList(vendorModels, sm.currentModel)
1243
+ var modelForVendor = resolveModelInList(vendorModels, defaultModelForVendor(vendor))
1212
1244
  || modelEntryValue(vendorModels[0])
1213
1245
  || "";
1214
- sendModelInfoForVendor(vendor, modelForVendor);
1246
+ sendModelInfoForVendor(session, vendor, modelForVendor);
1215
1247
  }
1216
1248
  return vendorAdapter;
1217
1249
  }
@@ -1414,14 +1446,9 @@ function createSDKBridge(opts) {
1414
1446
  }
1415
1447
  }
1416
1448
 
1417
- // Pick a model that belongs to the session's vendor. sm.currentModel is
1418
- // shared project-wide, so a Codex session that last set it to
1419
- // "gpt-5.4-mini" would otherwise leak into a Claude session in the same
1420
- // project (or in another session that switches vendor to claude) and
1421
- // Claude would reject the unknown model. We validate against the
1422
- // session vendor's model list regardless of which vendor happens to be
1423
- // the project's default adapter.
1424
- var queryModel = (ls.model && ls.model !== "default" ? ls.model : null) || session.model || sm.currentModel || undefined;
1449
+ // Pick a model from the session or its vendor-specific default, then
1450
+ // validate it against the session vendor's known model list.
1451
+ var queryModel = (ls.model && ls.model !== "default" ? ls.model : null) || session.model || defaultModelForVendor(session.vendor || "claude") || undefined;
1425
1452
  var sessionVendor = session.vendor || (adapter && adapter.vendor) || null;
1426
1453
  if (sessionVendor) {
1427
1454
  var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
@@ -1437,8 +1464,7 @@ function createSDKBridge(opts) {
1437
1464
  }
1438
1465
 
1439
1466
  // Bind the resolved model to the session so it survives a daemon
1440
- // restart. Without this, a session started under sm.currentModel (an
1441
- // in-memory value) came back after restart on the adapter default.
1467
+ // restart. This preserves a session-specific selection across restarts.
1442
1468
  if (queryModel && session.model !== queryModel) {
1443
1469
  session.model = queryModel;
1444
1470
  sm.saveSessionFile(session);
@@ -1471,6 +1497,7 @@ function createSDKBridge(opts) {
1471
1497
 
1472
1498
  var queryOpts = {
1473
1499
  cwd: cwd,
1500
+ linuxUser: linuxUser || undefined,
1474
1501
  // appendSystemPrompt, NOT systemPrompt: a plain-string systemPrompt
1475
1502
  // REPLACES the entire Claude Code system prompt (sdk.d.ts:2096), which
1476
1503
  // would strip the driver session down to the two-sentence pair preset.
@@ -1528,6 +1555,10 @@ function createSDKBridge(opts) {
1528
1555
  ACP: {
1529
1556
  mcpServers: getAcpMcpServers(session),
1530
1557
  },
1558
+ ANTIGRAVITY: {
1559
+ dangerouslySkipPermissions: dangerouslySkipPermissions
1560
+ || session.permissionMode === "bypassPermissions",
1561
+ },
1531
1562
  KIRO: {
1532
1563
  engine: kiroConfig.engine,
1533
1564
  mode: kiroConfig.mode,
@@ -1561,6 +1592,8 @@ function createSDKBridge(opts) {
1561
1592
  console.error("[sdk-bridge] cliSessionId:", session.cliSessionId, "resume:", !!session.cliSessionId);
1562
1593
  console.error("[sdk-bridge] Stack:", e.stack || "(no stack)");
1563
1594
  session.isProcessing = false;
1595
+ session._awaitingTurnResult = false;
1596
+ session._queuedTurnCount = 0;
1564
1597
  onProcessingChanged();
1565
1598
  session.queryInstance = null;
1566
1599
  session.messageQueue = null;
@@ -1602,6 +1635,8 @@ function createSDKBridge(opts) {
1602
1635
  session.messageQueue = null;
1603
1636
  session.abortController = null;
1604
1637
  session.isProcessing = false;
1638
+ session._awaitingTurnResult = false;
1639
+ session._queuedTurnCount = 0;
1605
1640
  onProcessingChanged();
1606
1641
  sendAndRecord(session, { type: "error", text: "The agent connection closed before it received your message. Please send it again." });
1607
1642
  sendAndRecord(session, { type: "done", code: 1 });
@@ -1790,16 +1825,20 @@ function createSDKBridge(opts) {
1790
1825
  } catch (e) {}
1791
1826
  if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
1792
1827
 
1793
- var acpVendorKeys = ["gemini", "opencode"];
1828
+ var subprocessVendorKeys = ["antigravity", "opencode", "kimi", "grok", "copilot", "qwen", "junie"];
1794
1829
  var acpProfiles = require("./yoke/acp-agent-profiles");
1795
- for (var acpVendorIndex = 0; acpVendorIndex < acpVendorKeys.length; acpVendorIndex++) {
1796
- var acpVendor = acpVendorKeys[acpVendorIndex];
1797
- var acpInfo = yoke.getVendorInfo(acpVendor);
1798
- if (!linuxUser || acpInfo.osUserIsolation) {
1799
- var acpProfile = acpProfiles.getAcpAgentProfile(acpVendor);
1800
- if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(acpVendor);
1830
+ for (var subprocessVendorIndex = 0; subprocessVendorIndex < subprocessVendorKeys.length; subprocessVendorIndex++) {
1831
+ var subprocessVendor = subprocessVendorKeys[subprocessVendorIndex];
1832
+ var subprocessInfo = yoke.getVendorInfo(subprocessVendor);
1833
+ if (!linuxUser || subprocessInfo.osUserIsolation) {
1834
+ if (subprocessVendor === "antigravity") {
1835
+ if (tryLookup(subprocessInfo.binaryName)) result.push(subprocessVendor);
1836
+ } else {
1837
+ var acpProfile = acpProfiles.getAcpAgentProfile(subprocessVendor);
1838
+ if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(subprocessVendor);
1839
+ }
1801
1840
  } else {
1802
- console.log("[sdk-bridge] " + acpInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1841
+ console.log("[sdk-bridge] " + subprocessInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1803
1842
  }
1804
1843
  }
1805
1844
 
@@ -1832,6 +1871,13 @@ function createSDKBridge(opts) {
1832
1871
  var defaultVendor = adapter ? adapter.vendor : "claude";
1833
1872
  sm.defaultVendor = defaultVendor;
1834
1873
 
1874
+ // Installation detection is independent of adapter initialization. Publish
1875
+ // it before warmup so a new project does not render the initial empty state
1876
+ // as if every CLI were missing while the default adapter starts up.
1877
+ sm.installedVendors = detectInstalledVendors(linuxUser);
1878
+ sm.availableVendors = getAvailableVendors(linuxUser);
1879
+ sendModelInfoForVendor(null, defaultVendor, defaultModelForVendor(defaultVendor));
1880
+
1835
1881
  // Initialize default adapter first (provides skills, slash commands, etc.)
1836
1882
  if (adapter) {
1837
1883
  try {
@@ -1862,7 +1908,8 @@ function createSDKBridge(opts) {
1862
1908
  send({ type: "slash_commands", commands: combined, vendor: defaultVendor });
1863
1909
  }
1864
1910
  if (result.defaultModel) {
1865
- sm.currentModel = sm.currentModel || sm._savedDefaultModel || result.defaultModel;
1911
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
1912
+ sm.defaultModelByVendor[defaultVendor] = sm.defaultModelByVendor[defaultVendor] || result.defaultModel;
1866
1913
  }
1867
1914
  sm.availableModels = result.models || [];
1868
1915
  // Store per-vendor models and capabilities
@@ -1883,19 +1930,8 @@ function createSDKBridge(opts) {
1883
1930
  // actually issues a query with it.
1884
1931
  sm.modelsByVendor = sm.modelsByVendor || {};
1885
1932
 
1886
- // Detect installed vendors per-user (binary existence check)
1887
- sm.installedVendors = detectInstalledVendors(linuxUser);
1888
- sm.availableVendors = getAvailableVendors(linuxUser);
1889
-
1890
- // Send initial state to client
1891
- send({
1892
- type: "model_info",
1893
- model: sm.currentModel || "",
1894
- models: getModelsForVendor(defaultVendor),
1895
- vendor: defaultVendor,
1896
- availableVendors: sm.availableVendors,
1897
- installedVendors: sm.installedVendors,
1898
- });
1933
+ // Send the fully initialized state to the client.
1934
+ sendModelInfoForVendor(null, defaultVendor, defaultModelForVendor(defaultVendor));
1899
1935
  }
1900
1936
 
1901
1937
  async function setModel(session, model) {
@@ -1907,22 +1943,28 @@ function createSDKBridge(opts) {
1907
1943
  // No active query — just store the model for next startQuery
1908
1944
  session.model = model;
1909
1945
  sm.saveSessionFile(session);
1910
- sm.currentModel = model;
1911
- // Don't send vendor here: session vendor not yet bound, let client keep its selection
1912
- sendModelInfoForVendor(null, model);
1913
- sendToSession(session, { type: "config_state", model: sm.currentModel, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1914
- return;
1946
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
1947
+ // Confirm against the session vendor so a non-default vendor does not
1948
+ // receive model metadata labeled as the project's default adapter.
1949
+ var pendingVendor = session.vendor || (adapter && adapter.vendor) || "claude";
1950
+ sm.defaultModelByVendor[pendingVendor] = model;
1951
+ sendModelInfoForVendor(session, pendingVendor, model);
1952
+ sendToSession(session, { type: "config_state", model: model, vendor: pendingVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1953
+ return { ok: true, model: model };
1915
1954
  }
1916
1955
  try {
1917
1956
  await session.queryInstance.setModel(model);
1918
1957
  session.model = model;
1919
1958
  sm.saveSessionFile(session);
1920
- sm.currentModel = model;
1959
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
1921
1960
  var sessionVendor = session.vendor || (adapter && adapter.vendor) || "claude";
1922
- sendModelInfoForVendor(sessionVendor, model);
1923
- sendToSession(session, { type: "config_state", model: sm.currentModel, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1961
+ sm.defaultModelByVendor[sessionVendor] = model;
1962
+ sendModelInfoForVendor(session, sessionVendor, model);
1963
+ sendToSession(session, { type: "config_state", model: model, vendor: sessionVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1964
+ return { ok: true, model: model };
1924
1965
  } catch (e) {
1925
1966
  send({ type: "error", text: "Failed to switch model: " + (e.message || e) });
1967
+ return { ok: false, model: session.model || "", error: e.message || String(e) };
1926
1968
  }
1927
1969
  }
1928
1970
 
@@ -1951,14 +1993,14 @@ function createSDKBridge(opts) {
1951
1993
  if (!session.queryInstance) {
1952
1994
  // No active query — just store the mode for next startQuery
1953
1995
  sm.currentPermissionMode = mode;
1954
- sendToSession(session, { type: "config_state", model: session.model || sm.currentModel || "", mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1996
+ sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1955
1997
  return;
1956
1998
  }
1957
1999
  try {
1958
2000
  // Route through QueryHandle (works for both in-process and worker paths)
1959
2001
  await session.queryInstance.setPermissionMode(mode);
1960
2002
  sm.currentPermissionMode = mode;
1961
- sendToSession(session, { type: "config_state", model: session.model || sm.currentModel || "", mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
2003
+ sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1962
2004
  } catch (e) {
1963
2005
  sendToSession(session, { type: "error", text: "Failed to set permission mode: " + (e.message || e) });
1964
2006
  }
@@ -2009,7 +2051,7 @@ function createSDKBridge(opts) {
2009
2051
  // Creates a mention session that can be reused across multiple mentions
2010
2052
  // within a conversation flow (session continuity).
2011
2053
  async function createMentionSession(opts) {
2012
- // opts: { vendor, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
2054
+ // opts: { vendor, linuxUser, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
2013
2055
  var abortController = new AbortController();
2014
2056
 
2015
2057
  // Current response callbacks (swapped on each pushMessage)
@@ -2029,6 +2071,7 @@ function createSDKBridge(opts) {
2029
2071
  try {
2030
2072
  handle = await mentionAdapter.createQuery({
2031
2073
  cwd: cwd,
2074
+ linuxUser: opts.linuxUser || undefined,
2032
2075
  systemPrompt: opts.claudeMd,
2033
2076
  model: opts.model || undefined,
2034
2077
  toolServers: opts.includeMcpServers ? (mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined) : undefined,