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

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 (82) 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-session-pair.js +14 -7
  10. package/lib/project-session-spawn.js +1 -1
  11. package/lib/project-sessions.js +13 -45
  12. package/lib/project-worker-proposal.js +51 -14
  13. package/lib/project.js +38 -80
  14. package/lib/public/antigravity-avatar.png +0 -0
  15. package/lib/public/app.js +29 -0
  16. package/lib/public/copilot-avatar.svg +5 -0
  17. package/lib/public/css/command-palette.css +22 -2
  18. package/lib/public/css/icon-strip.css +29 -13
  19. package/lib/public/css/input.css +56 -0
  20. package/lib/public/css/mates.css +6 -0
  21. package/lib/public/css/menus.css +38 -25
  22. package/lib/public/css/messages.css +9 -0
  23. package/lib/public/css/pane.css +16 -3
  24. package/lib/public/css/pwa-mobile.css +17 -0
  25. package/lib/public/css/session-actions.css +98 -0
  26. package/lib/public/css/title-bar.css +19 -0
  27. package/lib/public/grok-avatar.svg +4 -0
  28. package/lib/public/index.html +29 -7
  29. package/lib/public/junie-avatar.svg +11 -0
  30. package/lib/public/kimi-avatar.svg +4 -0
  31. package/lib/public/modules/agent-config-selects.js +69 -0
  32. package/lib/public/modules/app-header.js +4 -22
  33. package/lib/public/modules/app-messages.js +54 -11
  34. package/lib/public/modules/app-panels.js +36 -83
  35. package/lib/public/modules/app-projects.js +3 -1
  36. package/lib/public/modules/app-rendering.js +19 -4
  37. package/lib/public/modules/background-tasks-ui.js +55 -0
  38. package/lib/public/modules/mate-sidebar.js +11 -3
  39. package/lib/public/modules/model-picker.js +263 -0
  40. package/lib/public/modules/notifications.js +7 -1
  41. package/lib/public/modules/pane-bridge.js +11 -0
  42. package/lib/public/modules/pane-links.js +23 -0
  43. package/lib/public/modules/project-switcher.js +7 -6
  44. package/lib/public/modules/session-actions.js +294 -0
  45. package/lib/public/modules/sidebar-mates.js +1 -1
  46. package/lib/public/modules/sidebar-mobile.js +2 -1
  47. package/lib/public/modules/sidebar-projects.js +34 -43
  48. package/lib/public/modules/sidebar-sessions.js +20 -6
  49. package/lib/public/modules/split-pair-ui.js +16 -77
  50. package/lib/public/modules/split-view.js +3 -0
  51. package/lib/public/modules/tools.js +6 -1
  52. package/lib/public/modules/vendor-priority.js +14 -0
  53. package/lib/public/modules/vendor-selection.js +20 -0
  54. package/lib/public/modules/worker-proposal.js +6 -1
  55. package/lib/public/modules/worktree-location.js +17 -0
  56. package/lib/public/qwen-avatar.svg +4 -0
  57. package/lib/public/style.css +4 -2
  58. package/lib/sdk-bridge.js +103 -56
  59. package/lib/sdk-message-processor.js +26 -4
  60. package/lib/session-handoff-context.js +110 -0
  61. package/lib/session-notes-mcp-server.js +8 -1
  62. package/lib/sessions.js +4 -0
  63. package/lib/worktree.js +9 -4
  64. package/lib/ws-schema.js +9 -1
  65. package/lib/yoke/acp-agent-profiles.js +64 -14
  66. package/lib/yoke/adapters/antigravity.js +417 -0
  67. package/lib/yoke/adapters/claude.js +34 -0
  68. package/lib/yoke/adapters/codex.js +101 -5
  69. package/lib/yoke/adapters/copilot.js +7 -0
  70. package/lib/yoke/adapters/grok.js +7 -0
  71. package/lib/yoke/adapters/junie.js +7 -0
  72. package/lib/yoke/adapters/kimi.js +7 -0
  73. package/lib/yoke/adapters/kiro.js +2 -2
  74. package/lib/yoke/adapters/qwen.js +7 -0
  75. package/lib/yoke/codex-app-server.js +25 -8
  76. package/lib/yoke/index.js +67 -28
  77. package/lib/yoke/instructions.js +3 -0
  78. package/lib/yoke/interface.js +12 -0
  79. package/lib/yoke/vendor-registry.js +61 -6
  80. package/package.json +1 -1
  81. package/lib/public/gemini-avatar.svg +0 -11
  82. package/lib/yoke/adapters/gemini.js +0 -7
@@ -7,6 +7,7 @@ var path = require("path");
7
7
  var fs = require("fs");
8
8
  var { CodexAppServer } = require("../codex-app-server");
9
9
  var skillDiscovery = require("../skill-discovery");
10
+ var { resolveOsUserInfo } = require("../../os-users");
10
11
 
11
12
  // --- Event flattening ---
12
13
  // Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
@@ -1071,6 +1072,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1071
1072
  await appServer.send("turn/start", {
1072
1073
  threadId: state.threadId,
1073
1074
  input: input,
1075
+ model: state.model,
1074
1076
  }, 60000);
1075
1077
 
1076
1078
  // Wait for turn to complete
@@ -1143,7 +1145,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
1143
1145
  },
1144
1146
 
1145
1147
  setModel: function(model) {
1146
- // Model is set at thread creation. Cannot change mid-thread.
1148
+ state.model = model || "gpt-5.6-terra";
1147
1149
  return Promise.resolve();
1148
1150
  },
1149
1151
 
@@ -1220,6 +1222,13 @@ function createCodexAdapter(opts) {
1220
1222
  var _cwd = (opts && opts.cwd) || process.cwd();
1221
1223
  var _slug = (opts && opts.slug) || "";
1222
1224
  var _defaultInitOpts = Object.assign({}, opts || {});
1225
+ var _runtimeLinuxUser = (opts && opts.runtimeLinuxUser) || null;
1226
+ var _requiresLinuxUser = !!(opts && opts.osUsers) && !_runtimeLinuxUser;
1227
+ var _userRuntimes = Object.create(null);
1228
+ var _resolveOsUserInfo = (opts && opts.resolveOsUserInfo) || resolveOsUserInfo;
1229
+ var _createAppServer = (opts && opts.createAppServer) || function(serverOpts) {
1230
+ return new CodexAppServer(null, serverOpts);
1231
+ };
1223
1232
  // Codex models are a fixed list (the app-server doesn't enumerate them), so
1224
1233
  // model listing must not depend on a successful app-server init — otherwise a
1225
1234
  // slow/failed `initialize` leaves the picker empty and the chip shows the
@@ -1244,6 +1253,45 @@ function createCodexAdapter(opts) {
1244
1253
  var _shuttingDown = false;
1245
1254
  var _activeQueries = [];
1246
1255
 
1256
+ function withoutLinuxUser(callOpts) {
1257
+ var cleaned = Object.assign({}, callOpts || {});
1258
+ delete cleaned.linuxUser;
1259
+ return cleaned;
1260
+ }
1261
+
1262
+ function getUserRuntime(linuxUser) {
1263
+ if (!linuxUser) return null;
1264
+ if (_runtimeLinuxUser) {
1265
+ if (_runtimeLinuxUser !== linuxUser) {
1266
+ throw new Error("Codex runtime user mismatch: expected " + _runtimeLinuxUser + ", received " + linuxUser);
1267
+ }
1268
+ return adapter;
1269
+ }
1270
+ if (!_userRuntimes[linuxUser]) {
1271
+ var runtimeOpts = Object.assign({}, _defaultInitOpts, {
1272
+ runtimeLinuxUser: linuxUser,
1273
+ });
1274
+ delete runtimeOpts.linuxUser;
1275
+ _userRuntimes[linuxUser] = createCodexAdapter(runtimeOpts);
1276
+ }
1277
+ return _userRuntimes[linuxUser];
1278
+ }
1279
+
1280
+ function shutdownUserRuntimes(method, idleMs) {
1281
+ var users = Object.keys(_userRuntimes);
1282
+ if (!users.length) return Promise.resolve([]);
1283
+ return Promise.all(users.map(function(linuxUser) {
1284
+ var runtime = _userRuntimes[linuxUser];
1285
+ var result = method === "shutdownIfIdle"
1286
+ ? runtime.shutdownIfIdle(idleMs)
1287
+ : runtime.shutdown();
1288
+ return Promise.resolve(result).then(function(stopped) {
1289
+ if (method === "shutdown" || stopped) delete _userRuntimes[linuxUser];
1290
+ return stopped;
1291
+ });
1292
+ }));
1293
+ }
1294
+
1247
1295
  function updateLastActiveAt() {
1248
1296
  _lastActiveAt = Date.now();
1249
1297
  }
@@ -1279,7 +1327,7 @@ function createCodexAdapter(opts) {
1279
1327
  fastModeState: null,
1280
1328
  capabilities: {
1281
1329
  effort: true,
1282
- midSessionModelSwitch: false,
1330
+ midSessionModelSwitch: true,
1283
1331
  fork: true,
1284
1332
  rollback: true,
1285
1333
  sessionListing: false,
@@ -1396,6 +1444,16 @@ function createCodexAdapter(opts) {
1396
1444
  vendor: "codex",
1397
1445
 
1398
1446
  init: function(initOpts) {
1447
+ var requestedLinuxUser = initOpts && initOpts.linuxUser;
1448
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1449
+ return getUserRuntime(requestedLinuxUser).init(withoutLinuxUser(initOpts));
1450
+ }
1451
+ if (_requiresLinuxUser) {
1452
+ return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1453
+ }
1454
+ if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
1455
+ return Promise.reject(new Error("Codex runtime user mismatch"));
1456
+ }
1399
1457
  if (_shuttingDown) {
1400
1458
  return Promise.reject(createShutdownError());
1401
1459
  }
@@ -1412,6 +1470,9 @@ function createCodexAdapter(opts) {
1412
1470
 
1413
1471
  _initPromise = (async function() {
1414
1472
  var serverOpts = { cwd: _cwd };
1473
+ if (_runtimeLinuxUser) {
1474
+ serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
1475
+ }
1415
1476
 
1416
1477
  // Extract adapter options
1417
1478
  if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
@@ -1478,7 +1539,7 @@ function createCodexAdapter(opts) {
1478
1539
  }
1479
1540
 
1480
1541
  // Spawn and initialize app-server
1481
- _appServer = new CodexAppServer(null, serverOpts);
1542
+ _appServer = _createAppServer(serverOpts);
1482
1543
  await _appServer.start();
1483
1544
 
1484
1545
  await _appServer.send("initialize", {
@@ -1565,6 +1626,16 @@ function createCodexAdapter(opts) {
1565
1626
  },
1566
1627
 
1567
1628
  createQuery: async function(queryOpts) {
1629
+ var requestedLinuxUser = queryOpts && queryOpts.linuxUser;
1630
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1631
+ return getUserRuntime(requestedLinuxUser).createQuery(withoutLinuxUser(queryOpts));
1632
+ }
1633
+ if (_requiresLinuxUser) {
1634
+ throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
1635
+ }
1636
+ if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
1637
+ throw new Error("Codex runtime user mismatch");
1638
+ }
1568
1639
  if (_shuttingDown) {
1569
1640
  throw createShutdownError();
1570
1641
  }
@@ -1661,6 +1732,13 @@ function createCodexAdapter(opts) {
1661
1732
 
1662
1733
  // --- Title generation ---
1663
1734
  generateTitle: async function(messages, opts) {
1735
+ var requestedLinuxUser = opts && opts.linuxUser;
1736
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1737
+ return getUserRuntime(requestedLinuxUser).generateTitle(messages, withoutLinuxUser(opts));
1738
+ }
1739
+ if (_requiresLinuxUser) {
1740
+ throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
1741
+ }
1664
1742
  var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
1665
1743
  var prompt = "Below is a conversation between a user and an AI assistant. Generate a short, descriptive title (3-8 words) that captures the main topic. Reply with ONLY the title, nothing else.\n\n";
1666
1744
  for (var i = 0; i < messages.length; i++) {
@@ -1708,6 +1786,11 @@ function createCodexAdapter(opts) {
1708
1786
  listSessions: function() { return Promise.resolve([]); },
1709
1787
  renameSession: function() { return Promise.resolve(); },
1710
1788
  forkSession: function(threadId, opts) {
1789
+ var requestedLinuxUser = opts && opts.linuxUser;
1790
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1791
+ return getUserRuntime(requestedLinuxUser).forkSession(threadId, withoutLinuxUser(opts));
1792
+ }
1793
+ if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1711
1794
  if (!_appServer || !_appServer.started) return Promise.resolve(null);
1712
1795
  return _appServer.send("thread/fork", { threadId: threadId }, 30000).then(function(result) {
1713
1796
  var newThreadId = (result && result.thread) ? result.thread.id : null;
@@ -1715,17 +1798,30 @@ function createCodexAdapter(opts) {
1715
1798
  return { sessionId: newThreadId };
1716
1799
  });
1717
1800
  },
1718
- rollbackThread: function(threadId, numTurns) {
1801
+ rollbackThread: function(threadId, numTurns, opts) {
1802
+ var requestedLinuxUser = opts && opts.linuxUser;
1803
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1804
+ return getUserRuntime(requestedLinuxUser).rollbackThread(threadId, numTurns, withoutLinuxUser(opts));
1805
+ }
1806
+ if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1719
1807
  if (!_appServer || !_appServer.started) return Promise.resolve(null);
1720
1808
  return _appServer.send("thread/rollback", { threadId: threadId, numTurns: numTurns }, 30000);
1721
1809
  },
1722
1810
 
1723
1811
  // Shutdown the app-server process
1724
1812
  shutdown: function() {
1725
- return beginShutdown(true);
1813
+ return Promise.all([
1814
+ beginShutdown(true),
1815
+ shutdownUserRuntimes("shutdown"),
1816
+ ]).then(function() { return true; });
1726
1817
  },
1727
1818
 
1728
1819
  shutdownIfIdle: function(idleMs) {
1820
+ if (!_runtimeLinuxUser && Object.keys(_userRuntimes).length > 0) {
1821
+ return shutdownUserRuntimes("shutdownIfIdle", idleMs).then(function(results) {
1822
+ return results.some(function(stopped) { return !!stopped; });
1823
+ });
1824
+ }
1729
1825
  if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
1730
1826
  if (_initPromise) return Promise.resolve(false);
1731
1827
  if (!_appServer) return Promise.resolve(false);
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createCopilotAdapter(opts) {
4
+ return createAcpAdapter("copilot", opts);
5
+ }
6
+
7
+ module.exports = { createCopilotAdapter: createCopilotAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createGrokAdapter(opts) {
4
+ return createAcpAdapter("grok", opts);
5
+ }
6
+
7
+ module.exports = { createGrokAdapter: createGrokAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createJunieAdapter(opts) {
4
+ return createAcpAdapter("junie", opts);
5
+ }
6
+
7
+ module.exports = { createJunieAdapter: createJunieAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createKimiAdapter(opts) {
4
+ return createAcpAdapter("kimi", opts);
5
+ }
6
+
7
+ module.exports = { createKimiAdapter: createKimiAdapter };
@@ -762,7 +762,7 @@ function createKiroQueryHandle(acp, queryOpts) {
762
762
 
763
763
  setModel: function(model) {
764
764
  state.model = model;
765
- return setSessionModel(model).catch(function() {});
765
+ return setSessionModel(model);
766
766
  },
767
767
 
768
768
  setEffort: function() { return Promise.resolve(); },
@@ -867,7 +867,7 @@ function createKiroAdapter(opts) {
867
867
  fastModeState: null,
868
868
  capabilities: {
869
869
  effort: false,
870
- midSessionModelSwitch: false,
870
+ midSessionModelSwitch: true,
871
871
  fork: false,
872
872
  rollback: false,
873
873
  sessionListing: false,
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createQwenAdapter(opts) {
4
+ return createAcpAdapter("qwen", opts);
5
+ }
6
+
7
+ module.exports = { createQwenAdapter: createQwenAdapter };
@@ -9,6 +9,8 @@ var readline = require("readline");
9
9
  var path = require("path");
10
10
  var fs = require("fs");
11
11
  var { createRequire } = require("module");
12
+ var { buildUserEnv } = require("../build-user-env");
13
+ var { wrapSpawnAsUser } = require("../os-users");
12
14
 
13
15
  // --- Find the codex binary path ---
14
16
  // Mirrors the logic from @openai/codex-sdk findCodexPath()
@@ -120,14 +122,30 @@ function CodexAppServer(executablePath, opts) {
120
122
  this._stderrBuf = "";
121
123
  }
122
124
 
125
+ function buildSpawnSpec(executablePath, args, opts, wrapFn) {
126
+ opts = opts || {};
127
+ var osUserInfo = opts.osUserInfo || null;
128
+ var env = osUserInfo
129
+ ? Object.assign(buildUserEnv(osUserInfo), opts.env || {})
130
+ : Object.assign({}, process.env, opts.env || {});
131
+ var spawnOptions = {
132
+ stdio: ["pipe", "pipe", "pipe"],
133
+ env: env,
134
+ cwd: opts.cwd || process.cwd(),
135
+ };
136
+ if (osUserInfo) {
137
+ spawnOptions.uid = osUserInfo.uid;
138
+ spawnOptions.gid = osUserInfo.gid;
139
+ }
140
+ return (wrapFn || wrapSpawnAsUser)(executablePath, args, spawnOptions);
141
+ }
142
+
123
143
  CodexAppServer.prototype.start = function() {
124
144
  var self = this;
125
145
 
126
146
  return new Promise(function(resolve, reject) {
127
147
  try {
128
148
  var args = ["app-server"];
129
- var env = Object.assign({}, process.env, self.opts.env || {});
130
-
131
149
  // Pass config overrides via --config key=value flags
132
150
  if (self.opts.config) {
133
151
  var configArgs = serializeConfig(self.opts.config, "");
@@ -136,13 +154,11 @@ CodexAppServer.prototype.start = function() {
136
154
  }
137
155
  }
138
156
 
139
- console.log("[codex-app-server] Spawning:", self.executablePath, args.join(" "));
157
+ var spawnSpec = buildSpawnSpec(self.executablePath, args, self.opts);
158
+ var userSuffix = self.opts.osUserInfo ? " as " + self.opts.osUserInfo.user : "";
159
+ console.log("[codex-app-server] Spawning" + userSuffix + ":", spawnSpec.command, spawnSpec.args.join(" "));
140
160
 
141
- self.proc = spawn(self.executablePath, args, {
142
- stdio: ["pipe", "pipe", "pipe"],
143
- env: env,
144
- cwd: self.opts.cwd || process.cwd(),
145
- });
161
+ self.proc = spawn(spawnSpec.command, spawnSpec.args, spawnSpec.options);
146
162
 
147
163
  self.proc.on("error", function(err) {
148
164
  console.error("[codex-app-server] Process error:", err.message);
@@ -383,4 +399,5 @@ CodexAppServer.prototype.stop = function() {
383
399
  module.exports = {
384
400
  CodexAppServer: CodexAppServer,
385
401
  findCodexPath: findCodexPath,
402
+ buildSpawnSpec: buildSpawnSpec,
386
403
  };
package/lib/yoke/index.js CHANGED
@@ -6,13 +6,18 @@ var instructions = require("./instructions");
6
6
  var vendorRegistry = require("./vendor-registry");
7
7
  var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
8
8
  var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
9
- var createGeminiAdapter = require("./adapters/gemini").createGeminiAdapter;
9
+ var createAntigravityAdapter = require("./adapters/antigravity").createAntigravityAdapter;
10
10
  var createOpenCodeAdapter = require("./adapters/opencode").createOpenCodeAdapter;
11
+ var createKimiAdapter = require("./adapters/kimi").createKimiAdapter;
12
+ var createGrokAdapter = require("./adapters/grok").createGrokAdapter;
13
+ var createCopilotAdapter = require("./adapters/copilot").createCopilotAdapter;
14
+ var createQwenAdapter = require("./adapters/qwen").createQwenAdapter;
15
+ var createJunieAdapter = require("./adapters/junie").createJunieAdapter;
11
16
  var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
12
17
 
13
18
  // Keep the first session in a new project predictable. This order is also
14
19
  // used by the UI when a project has no remembered vendor yet.
15
- var DEFAULT_VENDOR_ORDER = ["claude", "codex", "gemini", "opencode", "kiro"];
20
+ var DEFAULT_VENDOR_ORDER = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
16
21
 
17
22
  function resolveDefaultVendor(availableVendors) {
18
23
  availableVendors = availableVendors || {};
@@ -73,10 +78,20 @@ function createAdapter(opts) {
73
78
  adapter = createClaudeAdapter(opts);
74
79
  } else if (vendor === "codex") {
75
80
  adapter = createCodexAdapter(opts);
76
- } else if (vendor === "gemini") {
77
- adapter = createGeminiAdapter(opts);
81
+ } else if (vendor === "antigravity") {
82
+ adapter = createAntigravityAdapter(opts);
78
83
  } else if (vendor === "opencode") {
79
84
  adapter = createOpenCodeAdapter(opts);
85
+ } else if (vendor === "kimi") {
86
+ adapter = createKimiAdapter(opts);
87
+ } else if (vendor === "grok") {
88
+ adapter = createGrokAdapter(opts);
89
+ } else if (vendor === "copilot") {
90
+ adapter = createCopilotAdapter(opts);
91
+ } else if (vendor === "qwen") {
92
+ adapter = createQwenAdapter(opts);
93
+ } else if (vendor === "junie") {
94
+ adapter = createJunieAdapter(opts);
80
95
  } else if (vendor === "kiro") {
81
96
  adapter = createKiroAdapter(opts);
82
97
  } else {
@@ -102,7 +117,7 @@ function logAuthCheck(auth) {
102
117
  if (_lastAuthLogKey === key && now - _lastAuthLogAt < 30000) return;
103
118
  _lastAuthLogKey = key;
104
119
  _lastAuthLogAt = now;
105
- console.log("[yoke] Auth check: claude=" + auth.claude + " codex=" + auth.codex + " gemini=" + auth.gemini + " opencode=" + auth.opencode + " kiro=" + auth.kiro);
120
+ console.log("[yoke] Auth check: " + Object.keys(auth).map(function(vendor) { return vendor + "=" + auth[vendor]; }).join(" "));
106
121
  }
107
122
 
108
123
  function checkAuth() {
@@ -236,8 +251,13 @@ function checkAuth() {
236
251
  _authCache = {
237
252
  claude: checkClaude(),
238
253
  codex: checkCodex(),
239
- gemini: !!lookupBinary("gemini"),
254
+ antigravity: !!lookupBinary("agy"),
240
255
  opencode: !!lookupBinary("opencode"),
256
+ kimi: !!lookupBinary("kimi"),
257
+ grok: !!lookupBinary("grok"),
258
+ copilot: !!lookupBinary("copilot"),
259
+ qwen: !!lookupBinary("qwen"),
260
+ junie: !!lookupBinary("junie"),
241
261
  kiro: checkKiro(),
242
262
  };
243
263
  logAuthCheck(_authCache);
@@ -261,7 +281,7 @@ function checkInstalled() {
261
281
 
262
282
  var fs = require("fs");
263
283
  var execFileSync = require("child_process").execFileSync;
264
- var result = { claude: false, codex: false, gemini: false, opencode: false, kiro: false };
284
+ var result = { claude: false, codex: false, antigravity: false, opencode: false, kimi: false, grok: false, copilot: false, qwen: false, junie: false, kiro: false };
265
285
  try {
266
286
  if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
267
287
  else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
@@ -290,10 +310,19 @@ function checkInstalled() {
290
310
  }
291
311
  } catch (e) {}
292
312
  try {
293
- var acpProfiles = require("./acp-agent-profiles");
294
- result.gemini = !!acpProfiles.findAcpAgentPath(acpProfiles.getAcpAgentProfile("gemini"));
295
- result.opencode = !!acpProfiles.findAcpAgentPath(acpProfiles.getAcpAgentProfile("opencode"));
313
+ var antigravityBinary = process.platform === "win32"
314
+ ? execFileSync("where", ["agy"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
315
+ : execFileSync("which", ["agy"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
316
+ result.antigravity = !!antigravityBinary.trim();
296
317
  } catch (e) {}
318
+ var acpVendorKeys = ["opencode", "kimi", "grok", "copilot", "qwen", "junie"];
319
+ for (var acpIndex = 0; acpIndex < acpVendorKeys.length; acpIndex++) {
320
+ try {
321
+ var acpProfiles = require("./acp-agent-profiles");
322
+ var acpVendor = acpVendorKeys[acpIndex];
323
+ result[acpVendor] = !!acpProfiles.findAcpAgentPath(acpProfiles.getAcpAgentProfile(acpVendor));
324
+ } catch (e) {}
325
+ }
297
326
  _installedCache = result;
298
327
  return result;
299
328
  }
@@ -317,8 +346,10 @@ function createAdapters(opts) {
317
346
  // Claude Code supports multiple auth modes (OAuth, Bedrock, Vertex, API key)
318
347
  // that `claude auth status` does not always detect. Runtime auth failures are
319
348
  // handled downstream via query-level error detection.
320
- var installed = checkInstalled();
321
- var auth = { claude: false, codex: false, gemini: false, opencode: false, kiro: false };
349
+ // Tests can supply a fixed installation snapshot without probing or spawning
350
+ // vendor binaries; production callers always use the cached host scan.
351
+ var installed = opts._installed || checkInstalled();
352
+ var auth = { claude: false, codex: false, antigravity: false, opencode: false, kimi: false, grok: false, copilot: false, qwen: false, junie: false, kiro: false };
322
353
  var adapters = {};
323
354
 
324
355
  function supportsConfiguredIsolation(vendor) {
@@ -334,10 +365,10 @@ function createAdapters(opts) {
334
365
  // per-project teardown never shuts it down on behalf of one project
335
366
  // (see destroy() in lib/project.js).
336
367
  _sharedClaudeAdapter.shared = true;
368
+ console.log("[yoke] Shared adapter created: claude");
337
369
  }
338
370
  adapters.claude = _sharedClaudeAdapter;
339
371
  auth.claude = true;
340
- console.log("[yoke] Adapter created: claude");
341
372
  } catch (e) {
342
373
  console.error("[yoke] Failed to create adapter for claude:", e.message);
343
374
  }
@@ -345,28 +376,26 @@ function createAdapters(opts) {
345
376
 
346
377
  if (installed.codex && supportsConfiguredIsolation("codex")) {
347
378
  try {
348
- adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
379
+ adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers });
349
380
  auth.codex = true;
350
- console.log("[yoke] Adapter created: codex");
351
381
  } catch (e) {
352
382
  console.error("[yoke] Failed to create adapter for codex:", e.message);
353
383
  }
354
384
  }
355
385
 
356
- var acpVendors = ["gemini", "opencode"];
357
- for (var acpIndex = 0; acpIndex < acpVendors.length; acpIndex++) {
358
- var acpVendor = acpVendors[acpIndex];
359
- var acpInfo = vendorRegistry.getVendorInfo(acpVendor);
360
- if (installed[acpVendor] && supportsConfiguredIsolation(acpVendor)) {
386
+ var subprocessVendors = ["antigravity", "opencode", "kimi", "grok", "copilot", "qwen", "junie"];
387
+ for (var subprocessIndex = 0; subprocessIndex < subprocessVendors.length; subprocessIndex++) {
388
+ var subprocessVendor = subprocessVendors[subprocessIndex];
389
+ var subprocessInfo = vendorRegistry.getVendorInfo(subprocessVendor);
390
+ if (installed[subprocessVendor] && supportsConfiguredIsolation(subprocessVendor)) {
361
391
  try {
362
- adapters[acpVendor] = createAdapter({ vendor: acpVendor, cwd: opts.cwd, slug: opts.slug });
363
- auth[acpVendor] = true;
364
- console.log("[yoke] Adapter created: " + acpVendor);
392
+ adapters[subprocessVendor] = createAdapter({ vendor: subprocessVendor, cwd: opts.cwd, slug: opts.slug });
393
+ auth[subprocessVendor] = true;
365
394
  } catch (e) {
366
- console.error("[yoke] Failed to create adapter for " + acpVendor + ":", e.message);
395
+ console.error("[yoke] Failed to create adapter for " + subprocessVendor + ":", e.message);
367
396
  }
368
- } else if (installed[acpVendor] && opts.osUsers) {
369
- console.log("[yoke] " + acpInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
397
+ } else if (installed[subprocessVendor] && opts.osUsers) {
398
+ console.log("[yoke] " + subprocessInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
370
399
  }
371
400
  }
372
401
 
@@ -375,7 +404,6 @@ function createAdapters(opts) {
375
404
  try {
376
405
  adapters.kiro = createAdapter({ vendor: "kiro", cwd: opts.cwd, slug: opts.slug });
377
406
  auth.kiro = true;
378
- console.log("[yoke] Adapter created: kiro");
379
407
  } catch (e) {
380
408
  console.error("[yoke] Failed to create adapter for kiro:", e.message);
381
409
  }
@@ -383,6 +411,12 @@ function createAdapters(opts) {
383
411
  console.log("[yoke] " + kiroInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
384
412
  }
385
413
 
414
+ var registeredVendors = Object.keys(adapters);
415
+ if (registeredVendors.length > 0) {
416
+ var registrationScope = opts.slug ? " for " + opts.slug : "";
417
+ console.log("[yoke] Adapters registered" + registrationScope + ": " + registeredVendors.join(", "));
418
+ }
419
+
386
420
  return { adapters: adapters, auth: auth };
387
421
  }
388
422
 
@@ -406,7 +440,12 @@ async function lazyCreateAdapter(adapters, vendor, opts) {
406
440
  if (!installed[vendor]) return null;
407
441
 
408
442
  try {
409
- var ad = createAdapter({ vendor: vendor, cwd: opts.cwd, slug: opts.slug });
443
+ var ad = createAdapter({
444
+ vendor: vendor,
445
+ cwd: opts.cwd,
446
+ slug: opts.slug,
447
+ osUsers: !!(opts.osUsers || opts.linuxUser),
448
+ });
410
449
  if (typeof ad.init === "function") {
411
450
  await ad.init(opts || {});
412
451
  }
@@ -18,12 +18,15 @@ var KNOWN_FILES = [
18
18
  { file: ".cursorrules", label: ".cursorrules" },
19
19
  { file: ".github/copilot-instructions.md", label: ".github/copilot-instructions.md" },
20
20
  { file: "COPILOT.md", label: "COPILOT.md" },
21
+ { file: "QWEN.md", label: "QWEN.md" },
21
22
  ];
22
23
 
23
24
  // Files each vendor reads natively (skip these to avoid duplication).
24
25
  var NATIVE_FILES = {
25
26
  claude: ["CLAUDE.md"],
26
27
  codex: ["AGENTS.md"],
28
+ copilot: [".github/copilot-instructions.md", "COPILOT.md"],
29
+ qwen: ["QWEN.md"],
27
30
  };
28
31
 
29
32
  // Scan projectDir for instruction files and return merged text.
@@ -9,6 +9,17 @@
9
9
  // 2. QueryHandle (returned by adapter.createQuery)
10
10
 
11
11
  var TOOL_POLICIES = ["ask", "allow-all"];
12
+ var BACKGROUND_TASK_TYPES = ["shell", "agent", "monitor", "other"];
13
+
14
+ /*
15
+ * Optional adapter event contract:
16
+ * { yokeType: "background_tasks_changed", tasks: [{ task_id, task_type, description }] }
17
+ *
18
+ * Each event replaces the complete live set. An adapter that emits this event
19
+ * must reset it to an empty set when its CLI process restarts. task_type uses
20
+ * BACKGROUND_TASK_TYPES; adapters without a level signal may synthesize one,
21
+ * and adapters that emit nothing simply have no background-task indicator.
22
+ */
12
23
 
13
24
  /**
14
25
  * Validate that an adapter object implements all required methods.
@@ -93,6 +104,7 @@ function validateQueryHandle(handle) {
93
104
 
94
105
  module.exports = {
95
106
  TOOL_POLICIES: TOOL_POLICIES,
107
+ BACKGROUND_TASK_TYPES: BACKGROUND_TASK_TYPES,
96
108
  ADAPTER_METHODS: ADAPTER_METHODS,
97
109
  QUERY_HANDLE_METHODS: QUERY_HANDLE_METHODS,
98
110
  validateAdapter: validateAdapter,
@@ -40,13 +40,13 @@ var VENDOR_REGISTRY = {
40
40
  },
41
41
  rateLimitTracking: true,
42
42
  },
43
- gemini: {
44
- displayName: "Gemini CLI",
45
- loginCommand: "gemini",
46
- binaryName: "gemini",
47
- avatar: "/gemini-avatar.svg",
43
+ antigravity: {
44
+ displayName: "Antigravity CLI",
45
+ loginCommand: "agy",
46
+ binaryName: "agy",
47
+ avatar: "/antigravity-avatar.png",
48
48
  sessionModes: ["gui"],
49
- effortLevels: [],
49
+ effortLevels: ["low", "medium", "high"],
50
50
  osUserIsolation: false,
51
51
  usageDashboard: null,
52
52
  rateLimitTracking: false,
@@ -62,6 +62,61 @@ var VENDOR_REGISTRY = {
62
62
  usageDashboard: null,
63
63
  rateLimitTracking: false,
64
64
  },
65
+ kimi: {
66
+ displayName: "Kimi Code",
67
+ loginCommand: "kimi login",
68
+ binaryName: "kimi",
69
+ avatar: "/kimi-avatar.svg",
70
+ sessionModes: ["gui"],
71
+ effortLevels: [],
72
+ osUserIsolation: false,
73
+ usageDashboard: null,
74
+ rateLimitTracking: false,
75
+ },
76
+ grok: {
77
+ displayName: "Grok Build",
78
+ loginCommand: "grok login --device-auth",
79
+ binaryName: "grok",
80
+ avatar: "/grok-avatar.svg",
81
+ sessionModes: ["gui"],
82
+ effortLevels: [],
83
+ osUserIsolation: false,
84
+ usageDashboard: null,
85
+ rateLimitTracking: false,
86
+ },
87
+ copilot: {
88
+ displayName: "GitHub Copilot CLI",
89
+ loginCommand: "copilot login",
90
+ binaryName: "copilot",
91
+ avatar: "/copilot-avatar.svg",
92
+ sessionModes: ["gui"],
93
+ effortLevels: [],
94
+ osUserIsolation: false,
95
+ usageDashboard: null,
96
+ rateLimitTracking: false,
97
+ },
98
+ qwen: {
99
+ displayName: "Qwen Code",
100
+ loginCommand: "qwen",
101
+ binaryName: "qwen",
102
+ avatar: "/qwen-avatar.svg",
103
+ sessionModes: ["gui"],
104
+ effortLevels: [],
105
+ osUserIsolation: false,
106
+ usageDashboard: null,
107
+ rateLimitTracking: false,
108
+ },
109
+ junie: {
110
+ displayName: "Junie CLI",
111
+ loginCommand: "junie",
112
+ binaryName: "junie",
113
+ avatar: "/junie-avatar.svg",
114
+ sessionModes: ["gui"],
115
+ effortLevels: [],
116
+ osUserIsolation: false,
117
+ usageDashboard: null,
118
+ rateLimitTracking: false,
119
+ },
65
120
  kiro: {
66
121
  displayName: "Kiro CLI",
67
122
  loginCommand: "kiro-cli login",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.4.0-beta.2",
3
+ "version": "3.4.0-beta.21",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",