clay-server 2.47.0-beta.1 → 2.47.0-beta.3

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 (37) hide show
  1. package/lib/codex-defaults.js +12 -2
  2. package/lib/daemon.js +38 -8
  3. package/lib/kiro-defaults.js +22 -0
  4. package/lib/project-connection.js +3 -1
  5. package/lib/project-notifications.js +3 -1
  6. package/lib/project-sessions.js +28 -9
  7. package/lib/project.js +42 -10
  8. package/lib/public/app.js +8 -0
  9. package/lib/public/css/mobile-nav.css +53 -9
  10. package/lib/public/css/sidebar.css +24 -1
  11. package/lib/public/css/tui-attention.css +4 -63
  12. package/lib/public/index.html +4 -0
  13. package/lib/public/kiro-avatar.svg +14 -0
  14. package/lib/public/modules/app-messages.js +14 -12
  15. package/lib/public/modules/app-notifications.js +12 -4
  16. package/lib/public/modules/app-panels.js +10 -4
  17. package/lib/public/modules/app-rate-limit.js +41 -13
  18. package/lib/public/modules/app-rendering.js +14 -0
  19. package/lib/public/modules/input.js +4 -3
  20. package/lib/public/modules/mate-sidebar.js +3 -3
  21. package/lib/public/modules/session-tui-view.js +4 -40
  22. package/lib/public/modules/sidebar-mates.js +1 -1
  23. package/lib/public/modules/sidebar-mobile.js +56 -23
  24. package/lib/public/modules/sidebar-sessions.js +126 -63
  25. package/lib/public/modules/tools.js +1 -1
  26. package/lib/public/modules/tui-grab.js +12 -3
  27. package/lib/sdk-bridge.js +90 -21
  28. package/lib/sdk-message-processor.js +4 -4
  29. package/lib/server.js +4 -0
  30. package/lib/users-preferences.js +3 -1
  31. package/lib/yoke/adapters/claude.js +25 -0
  32. package/lib/yoke/adapters/codex.js +34 -21
  33. package/lib/yoke/adapters/kiro.js +1170 -0
  34. package/lib/yoke/index.js +70 -6
  35. package/lib/yoke/kiro-acp-server.js +328 -0
  36. package/lib/yoke/vendor-registry.js +55 -0
  37. package/package.json +4 -3
package/lib/yoke/index.js CHANGED
@@ -3,8 +3,10 @@
3
3
 
4
4
  var iface = require("./interface");
5
5
  var instructions = require("./instructions");
6
+ var vendorRegistry = require("./vendor-registry");
6
7
  var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
7
8
  var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
9
+ var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
8
10
 
9
11
  /**
10
12
  * Wrap adapter.createQuery to inject cross-vendor project instructions.
@@ -44,12 +46,16 @@ function wrapCreateQuery(adapter, defaultCwd) {
44
46
  * @returns {Adapter}
45
47
  */
46
48
  function createAdapter(opts) {
49
+ // Adding a vendor here also requires a registry entry and an update to the
50
+ // completeness list in test/yoke-vendor-registry.test.js.
47
51
  var vendor = (opts && opts.vendor) || "claude";
48
52
  var adapter;
49
53
  if (vendor === "claude") {
50
54
  adapter = createClaudeAdapter(opts);
51
55
  } else if (vendor === "codex") {
52
56
  adapter = createCodexAdapter(opts);
57
+ } else if (vendor === "kiro") {
58
+ adapter = createKiroAdapter(opts);
53
59
  } else {
54
60
  throw new Error("[YOKE] Unknown adapter vendor: " + vendor);
55
61
  }
@@ -73,7 +79,7 @@ function logAuthCheck(auth) {
73
79
  if (_lastAuthLogKey === key && now - _lastAuthLogAt < 30000) return;
74
80
  _lastAuthLogKey = key;
75
81
  _lastAuthLogAt = now;
76
- console.log("[yoke] Auth check: claude=" + auth.claude + " codex=" + auth.codex);
82
+ console.log("[yoke] Auth check: claude=" + auth.claude + " codex=" + auth.codex + " kiro=" + auth.kiro);
77
83
  }
78
84
 
79
85
  function checkAuth() {
@@ -181,7 +187,30 @@ function checkAuth() {
181
187
  }
182
188
  }
183
189
 
184
- _authCache = { claude: checkClaude(), codex: checkCodex() };
190
+ function resolveKiroBinary() {
191
+ var fs = require("fs");
192
+ try {
193
+ var findKiroPath = require("./kiro-acp-server").findKiroPath;
194
+ var kiroBin = findKiroPath();
195
+ if (kiroBin && fs.existsSync(kiroBin)) return kiroBin;
196
+ } catch (e) {}
197
+ return lookupBinary("kiro-cli");
198
+ }
199
+
200
+ function checkKiro() {
201
+ try {
202
+ var kiroBin = resolveKiroBinary();
203
+ if (!kiroBin) return false;
204
+ // `kiro-cli whoami` exits 0 and prints account details when logged in,
205
+ // and exits non-zero otherwise.
206
+ execFileSync(kiroBin, ["whoami"], { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
207
+ return true;
208
+ } catch (e) {
209
+ return false;
210
+ }
211
+ }
212
+
213
+ _authCache = { claude: checkClaude(), codex: checkCodex(), kiro: checkKiro() };
185
214
  logAuthCheck(_authCache);
186
215
  return _authCache;
187
216
  }
@@ -203,12 +232,17 @@ function checkInstalled() {
203
232
 
204
233
  var fs = require("fs");
205
234
  var execFileSync = require("child_process").execFileSync;
206
- var result = { claude: false, codex: false };
235
+ var result = { claude: false, codex: false, kiro: false };
207
236
  try {
208
237
  if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
209
238
  else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
210
239
  result.claude = true;
211
240
  } catch (e) {}
241
+ try {
242
+ var findKiroPath = require("./kiro-acp-server").findKiroPath;
243
+ var kiroBin = findKiroPath();
244
+ if (kiroBin && fs.existsSync(kiroBin)) result.kiro = true;
245
+ } catch (e) {}
212
246
  try {
213
247
  var codexBin = null;
214
248
  try {
@@ -250,13 +284,22 @@ function createAdapters(opts) {
250
284
  // that `claude auth status` does not always detect. Runtime auth failures are
251
285
  // handled downstream via query-level error detection.
252
286
  var installed = checkInstalled();
253
- var auth = { claude: false, codex: false };
287
+ var auth = { claude: false, codex: false, kiro: false };
254
288
  var adapters = {};
255
289
 
256
- if (installed.claude) {
290
+ function supportsConfiguredIsolation(vendor) {
291
+ var info = vendorRegistry.getVendorInfo(vendor);
292
+ return !opts.osUsers || !info || info.osUserIsolation;
293
+ }
294
+
295
+ if (installed.claude && supportsConfiguredIsolation("claude")) {
257
296
  try {
258
297
  if (!_sharedClaudeAdapter) {
259
298
  _sharedClaudeAdapter = createAdapter({ vendor: "claude", cwd: opts.cwd });
299
+ // This adapter instance is reused by every project. Mark it so
300
+ // per-project teardown never shuts it down on behalf of one project
301
+ // (see destroy() in lib/project.js).
302
+ _sharedClaudeAdapter.shared = true;
260
303
  }
261
304
  adapters.claude = _sharedClaudeAdapter;
262
305
  auth.claude = true;
@@ -266,7 +309,7 @@ function createAdapters(opts) {
266
309
  }
267
310
  }
268
311
 
269
- if (installed.codex) {
312
+ if (installed.codex && supportsConfiguredIsolation("codex")) {
270
313
  try {
271
314
  adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
272
315
  auth.codex = true;
@@ -276,6 +319,19 @@ function createAdapters(opts) {
276
319
  }
277
320
  }
278
321
 
322
+ var kiroInfo = vendorRegistry.getVendorInfo("kiro");
323
+ if (installed.kiro && supportsConfiguredIsolation("kiro")) {
324
+ try {
325
+ adapters.kiro = createAdapter({ vendor: "kiro", cwd: opts.cwd, slug: opts.slug });
326
+ auth.kiro = true;
327
+ console.log("[yoke] Adapter created: kiro");
328
+ } catch (e) {
329
+ console.error("[yoke] Failed to create adapter for kiro:", e.message);
330
+ }
331
+ } else if (installed.kiro && opts.osUsers) {
332
+ console.log("[yoke] " + kiroInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
333
+ }
334
+
279
335
  return { adapters: adapters, auth: auth };
280
336
  }
281
337
 
@@ -287,6 +343,12 @@ function createAdapters(opts) {
287
343
  async function lazyCreateAdapter(adapters, vendor, opts) {
288
344
  opts = opts || {};
289
345
 
346
+ var vendorInfo = vendorRegistry.getVendorInfo(vendor);
347
+ if (vendorInfo && !vendorInfo.osUserIsolation && (opts.osUsers || opts.linuxUser)) {
348
+ console.log("[yoke] Refusing lazy " + vendorInfo.displayName + " adapter creation for OS-isolated user " + (opts.linuxUser || "unknown"));
349
+ return null;
350
+ }
351
+
290
352
  // Force re-check since user may have logged in after server start
291
353
  invalidateAuthCache();
292
354
  var installed = checkInstalled();
@@ -313,6 +375,8 @@ module.exports = {
313
375
  checkAuth: checkAuth,
314
376
  checkInstalled: checkInstalled,
315
377
  invalidateAuthCache: invalidateAuthCache,
378
+ VENDOR_REGISTRY: vendorRegistry.VENDOR_REGISTRY,
379
+ getVendorInfo: vendorRegistry.getVendorInfo,
316
380
  TOOL_POLICIES: iface.TOOL_POLICIES,
317
381
  validateAdapter: iface.validateAdapter,
318
382
  validateQueryHandle: iface.validateQueryHandle,
@@ -0,0 +1,328 @@
1
+ // Kiro ACP Server Protocol Client
2
+ // --------------------------------
3
+ // Manages a `kiro-cli acp` child process with bidirectional JSON-RPC 2.0
4
+ // communication over stdin/stdout. Kiro CLI implements the Agent Client
5
+ // Protocol (ACP), the same standardized protocol used by editors like Zed.
6
+ //
7
+ // This is structurally the same transport as codex-app-server.js: line-delimited
8
+ // JSON-RPC where the child both answers our requests and initiates its own
9
+ // (session/update notifications and session/request_permission requests).
10
+
11
+ var { spawn } = require("child_process");
12
+ var readline = require("readline");
13
+ var path = require("path");
14
+ var fs = require("fs");
15
+
16
+ // --- Find the kiro-cli binary path ---
17
+ // Kiro CLI installs to ~/.local/bin on Linux/macOS. We also honor a
18
+ // KIRO_CLI_PATH override and fall back to a plain PATH lookup.
19
+ function findKiroPath() {
20
+ if (process.env.KIRO_CLI_PATH && fs.existsSync(process.env.KIRO_CLI_PATH)) {
21
+ return process.env.KIRO_CLI_PATH;
22
+ }
23
+
24
+ var binName = process.platform === "win32" ? "kiro-cli.exe" : "kiro-cli";
25
+ var REAL_HOME;
26
+ try { REAL_HOME = require("../config").REAL_HOME; } catch (e) { REAL_HOME = require("os").homedir(); }
27
+
28
+ var candidates = [
29
+ path.join(REAL_HOME || "", ".local", "bin", binName),
30
+ path.join(REAL_HOME || "", "bin", binName),
31
+ "/usr/local/bin/" + binName,
32
+ "/opt/homebrew/bin/" + binName,
33
+ ];
34
+ for (var i = 0; i < candidates.length; i++) {
35
+ if (candidates[i] && fs.existsSync(candidates[i])) return candidates[i];
36
+ }
37
+
38
+ // Fall back to a PATH lookup.
39
+ try {
40
+ var execFileSync = require("child_process").execFileSync;
41
+ var out = process.platform === "win32"
42
+ ? execFileSync("where", [binName], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
43
+ : execFileSync("which", ["kiro-cli"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
44
+ var resolved = out.trim().split(/\r?\n/)[0];
45
+ if (resolved) return resolved;
46
+ } catch (e) {}
47
+
48
+ throw new Error("Could not find kiro-cli binary (looked in ~/.local/bin, PATH, KIRO_CLI_PATH)");
49
+ }
50
+
51
+ // --- KiroAcpServer ---
52
+
53
+ function KiroAcpServer(executablePath, opts) {
54
+ this.proc = null;
55
+ this.rl = null;
56
+ this.nextId = 1;
57
+ this.pendingRequests = {}; // id -> { resolve, reject, timer }
58
+ this.requestHandlers = {}; // method -> async function(params, message)
59
+ // One ACP process is shared by every session in a project, so server-initiated
60
+ // events must be routed by params.sessionId rather than handed to a single
61
+ // handler. Each entry is { sessionId, fn }; sessionId starts null and is
62
+ // filled in once session/new or session/load resolves.
63
+ this.handlers = [];
64
+ this.executablePath = executablePath || findKiroPath();
65
+ this.opts = opts || {};
66
+ this.started = false;
67
+ this._stderrBuf = "";
68
+ this._authSignalSent = false;
69
+ }
70
+
71
+ KiroAcpServer.prototype.start = function() {
72
+ var self = this;
73
+
74
+ return new Promise(function(resolve, reject) {
75
+ try {
76
+ var args = ["acp"];
77
+ // Kiro auto-approves nothing by default; Clay drives approvals through
78
+ // session/request_permission, so we do not pass --trust-all-tools here.
79
+ if (self.opts.extraArgs && self.opts.extraArgs.length) {
80
+ args = args.concat(self.opts.extraArgs);
81
+ }
82
+ var env = Object.assign({}, process.env, self.opts.env || {});
83
+
84
+ console.log("[kiro-acp-server] Spawning:", self.executablePath, args.join(" "));
85
+
86
+ self.proc = spawn(self.executablePath, args, {
87
+ stdio: ["pipe", "pipe", "pipe"],
88
+ env: env,
89
+ cwd: self.opts.cwd || process.cwd(),
90
+ });
91
+
92
+ self.proc.on("error", function(err) {
93
+ console.error("[kiro-acp-server] Process error:", err.message);
94
+ if (!self.started) reject(err);
95
+ self._rejectAllPending(err);
96
+ });
97
+
98
+ self.proc.on("exit", function(code, signal) {
99
+ console.log("[kiro-acp-server] Process exited: code=" + code + " signal=" + signal);
100
+ self.started = false;
101
+ self._rejectAllPending(new Error("Process exited: code=" + code));
102
+ });
103
+
104
+ // Collect stderr for debugging + auth-error detection.
105
+ self.proc.stderr.on("data", function(chunk) {
106
+ var text = chunk.toString();
107
+ self._stderrBuf += text;
108
+ var lines = self._stderrBuf.split("\n");
109
+ while (lines.length > 1) {
110
+ var line = lines.shift();
111
+ if (line.trim()) console.log("[kiro-acp-server stderr]", line);
112
+ self._maybeSignalAuthError(line);
113
+ }
114
+ self._stderrBuf = lines[0] || "";
115
+ });
116
+
117
+ // Line-based JSON-RPC reading from stdout.
118
+ self.rl = readline.createInterface({ input: self.proc.stdout, crlfDelay: Infinity });
119
+ self.rl.on("line", function(line) {
120
+ if (!line.trim()) return;
121
+ try {
122
+ var msg = JSON.parse(line);
123
+ self._handleMessage(msg);
124
+ } catch (e) {
125
+ console.error("[kiro-acp-server] Failed to parse line:", line.substring(0, 200));
126
+ }
127
+ });
128
+ self.rl.on("close", function() {
129
+ console.log("[kiro-acp-server] stdout closed");
130
+ });
131
+
132
+ self.started = true;
133
+ resolve();
134
+ } catch (e) {
135
+ reject(e);
136
+ }
137
+ });
138
+ };
139
+
140
+ KiroAcpServer.prototype._handleMessage = function(msg) {
141
+ // Response to a request we sent.
142
+ if (msg.id !== undefined && msg.id !== null && (msg.result !== undefined || msg.error !== undefined)) {
143
+ var pending = this.pendingRequests[msg.id];
144
+ if (pending) {
145
+ delete this.pendingRequests[msg.id];
146
+ if (pending.timer) clearTimeout(pending.timer);
147
+ if (msg.error) {
148
+ var e = new Error(msg.error.message || JSON.stringify(msg.error));
149
+ e.rpcError = msg.error;
150
+ pending.reject(e);
151
+ } else {
152
+ pending.resolve(msg.result);
153
+ }
154
+ }
155
+ return;
156
+ }
157
+
158
+ // Server-initiated request (has id + method) or notification (has method, no id).
159
+ if (msg.method) {
160
+ var isRequest = msg.id !== undefined && msg.id !== null;
161
+ var directHandler = isRequest && this.requestHandlers[msg.method];
162
+ if (directHandler) {
163
+ var self = this;
164
+ Promise.resolve().then(function() {
165
+ return directHandler(msg.params || {}, msg);
166
+ }).then(function(result) {
167
+ self.respond(msg.id, result);
168
+ }).catch(function(err) {
169
+ console.error("[kiro-acp-server] Request handler failed for " + msg.method + ":", err && err.message ? err.message : err);
170
+ self.respondError(msg.id, -32002, err && err.message ? err.message : "Request handler failed");
171
+ });
172
+ return;
173
+ }
174
+
175
+ var sessionId = msg.params && msg.params.sessionId;
176
+ var targets;
177
+ if (sessionId) {
178
+ targets = this.handlers.filter(function(h) { return h.sessionId === sessionId; });
179
+ } else {
180
+ // Process-wide events (auth failures, transport errors) have no session,
181
+ // so every active session should see them.
182
+ targets = this.handlers.slice();
183
+ }
184
+
185
+ if (!targets.length) {
186
+ // A request carries an id and MUST be answered, otherwise kiro-cli blocks
187
+ // on it until session/prompt times out. Never drop one silently.
188
+ if (msg.id !== undefined && msg.id !== null) {
189
+ console.warn("[kiro-acp-server] No handler for request " + msg.method + " (session=" + (sessionId || "none") + "), rejecting");
190
+ this.respondError(msg.id, -32001, "No active handler for session " + (sessionId || "none"));
191
+ } else {
192
+ console.log("[kiro-acp-server] Unhandled event:", msg.method);
193
+ }
194
+ return;
195
+ }
196
+
197
+ // A request must be answered exactly once, so only the first matching
198
+ // handler gets it. Notifications fan out to all matches.
199
+ if (msg.id !== undefined && msg.id !== null) {
200
+ try {
201
+ targets[0].fn(msg);
202
+ } catch (e) {
203
+ console.error("[kiro-acp-server] Handler threw for " + msg.method + ":", e && e.message ? e.message : e);
204
+ this.respondError(msg.id, -32000, "Handler error");
205
+ }
206
+ return;
207
+ }
208
+ targets.forEach(function(h) {
209
+ try { h.fn(msg); } catch (e) {
210
+ console.error("[kiro-acp-server] Handler threw for " + msg.method + ":", e && e.message ? e.message : e);
211
+ }
212
+ });
213
+ }
214
+ };
215
+
216
+ // Register a per-query handler. Returns the entry so the caller can set
217
+ // entry.sessionId once known and pass it back to removeHandler on teardown.
218
+ KiroAcpServer.prototype.addHandler = function(fn) {
219
+ var entry = { sessionId: null, fn: fn };
220
+ this.handlers.push(entry);
221
+ return entry;
222
+ };
223
+
224
+ KiroAcpServer.prototype.removeHandler = function(entry) {
225
+ var idx = this.handlers.indexOf(entry);
226
+ if (idx !== -1) this.handlers.splice(idx, 1);
227
+ };
228
+
229
+ KiroAcpServer.prototype.addRequestHandler = function(method, fn) {
230
+ this.requestHandlers[method] = fn;
231
+ };
232
+
233
+ // Detect "not logged in" signals on stderr. Kiro surfaces auth failures as
234
+ // 401/expired-token/"kiro-cli login" hints. Deduped so a burst collapses.
235
+ KiroAcpServer.prototype._maybeSignalAuthError = function(line) {
236
+ if (!this.handlers.length || !line || this._authSignalSent) return;
237
+ var isAuth = /not logged in|expired token|token has expired|please (?:sign in|log ?in) again|reauthenticate|kiro-cli login|no valid credentials|forbidden/i.test(line)
238
+ || (/\b401\b/.test(line) && /unauthorized|credential|token/i.test(line));
239
+ if (!isAuth) return;
240
+ this._authSignalSent = true;
241
+ var self = this;
242
+ var dedupeTimer = setTimeout(function() { self._authSignalSent = false; }, 15000);
243
+ // Don't hold the event loop open just to reset a dedupe flag.
244
+ if (dedupeTimer && typeof dedupeTimer.unref === "function") dedupeTimer.unref();
245
+ // No sessionId: _handleMessage fans this out to every active session.
246
+ this._handleMessage({ method: "_kiro/error", params: { error: { kiroErrorInfo: "unauthorized", message: line } } });
247
+ };
248
+
249
+ // Send a JSON-RPC request (expects a response).
250
+ KiroAcpServer.prototype.send = function(method, params, timeoutMs) {
251
+ var self = this;
252
+ var id = this.nextId++;
253
+ timeoutMs = timeoutMs || 30000;
254
+
255
+ return new Promise(function(resolve, reject) {
256
+ if (!self.proc || !self.started) {
257
+ return reject(new Error("ACP server not started"));
258
+ }
259
+ var timer = setTimeout(function() {
260
+ delete self.pendingRequests[id];
261
+ reject(new Error("Request timeout: " + method + " (id=" + id + ")"));
262
+ }, timeoutMs);
263
+ self.pendingRequests[id] = { resolve: resolve, reject: reject, timer: timer };
264
+
265
+ var msg = { jsonrpc: "2.0", id: id, method: method };
266
+ if (params !== undefined) msg.params = params;
267
+ self._write(msg);
268
+ });
269
+ };
270
+
271
+ // Send a JSON-RPC notification (no response expected).
272
+ KiroAcpServer.prototype.notify = function(method, params) {
273
+ if (!this.proc || !this.started) return;
274
+ var msg = { jsonrpc: "2.0", method: method };
275
+ if (params !== undefined) msg.params = params;
276
+ this._write(msg);
277
+ };
278
+
279
+ // Respond to a server-initiated request.
280
+ KiroAcpServer.prototype.respond = function(id, result) {
281
+ if (!this.proc || !this.started) return;
282
+ this._write({ jsonrpc: "2.0", id: id, result: result });
283
+ };
284
+
285
+ // Respond with an error to a server-initiated request.
286
+ KiroAcpServer.prototype.respondError = function(id, code, message) {
287
+ if (!this.proc || !this.started) return;
288
+ this._write({ jsonrpc: "2.0", id: id, error: { code: code || -1, message: message || "Error" } });
289
+ };
290
+
291
+ KiroAcpServer.prototype._write = function(msg) {
292
+ if (!this.proc || !this.proc.stdin || this.proc.stdin.destroyed) return;
293
+ try {
294
+ this.proc.stdin.write(JSON.stringify(msg) + "\n");
295
+ } catch (e) {
296
+ console.error("[kiro-acp-server] Write error:", e.message);
297
+ }
298
+ };
299
+
300
+ KiroAcpServer.prototype._rejectAllPending = function(err) {
301
+ var ids = Object.keys(this.pendingRequests);
302
+ for (var i = 0; i < ids.length; i++) {
303
+ var pending = this.pendingRequests[ids[i]];
304
+ if (pending.timer) clearTimeout(pending.timer);
305
+ pending.reject(err);
306
+ }
307
+ this.pendingRequests = {};
308
+ };
309
+
310
+ KiroAcpServer.prototype.stop = function() {
311
+ this.started = false;
312
+ this._rejectAllPending(new Error("Stopped"));
313
+
314
+ if (this.rl) {
315
+ this.rl.close();
316
+ this.rl = null;
317
+ }
318
+ if (this.proc) {
319
+ try { this.proc.stdin.end(); } catch (e) {}
320
+ try { this.proc.kill("SIGTERM"); } catch (e) {}
321
+ this.proc = null;
322
+ }
323
+ };
324
+
325
+ module.exports = {
326
+ KiroAcpServer: KiroAcpServer,
327
+ findKiroPath: findKiroPath,
328
+ };
@@ -0,0 +1,55 @@
1
+ // Static, init-free facts about each vendor YOKE supports. Host code that
2
+ // needs metadata before adapter initialization should read it from here.
3
+ // Do not import adapters into this module.
4
+
5
+ var VENDOR_REGISTRY = {
6
+ claude: {
7
+ displayName: "Claude Code",
8
+ loginCommand: "claude login",
9
+ binaryName: "claude",
10
+ avatar: "/claude-code-avatar.png",
11
+ sessionModes: ["gui", "tui"],
12
+ osUserIsolation: true,
13
+ usageDashboard: {
14
+ icon: "/claude-code-avatar.png",
15
+ alt: "Claude Code",
16
+ href: "https://claude.ai/settings/usage",
17
+ title: "Check usage on claude.ai",
18
+ },
19
+ rateLimitTracking: true,
20
+ },
21
+ codex: {
22
+ displayName: "Codex",
23
+ loginCommand: "codex login --device-auth",
24
+ binaryName: "codex",
25
+ avatar: "/codex-avatar.png",
26
+ sessionModes: ["gui"],
27
+ osUserIsolation: true,
28
+ usageDashboard: {
29
+ icon: "/codex-avatar.png",
30
+ alt: "Codex",
31
+ href: "https://chatgpt.com/admin/usage",
32
+ title: "Check usage on ChatGPT",
33
+ },
34
+ rateLimitTracking: true,
35
+ },
36
+ kiro: {
37
+ displayName: "Kiro CLI",
38
+ loginCommand: "kiro-cli login",
39
+ binaryName: "kiro-cli",
40
+ avatar: "/kiro-avatar.svg",
41
+ sessionModes: ["gui"],
42
+ osUserIsolation: false,
43
+ usageDashboard: null,
44
+ rateLimitTracking: false,
45
+ },
46
+ };
47
+
48
+ function getVendorInfo(vendor) {
49
+ return VENDOR_REGISTRY[vendor] || null;
50
+ }
51
+
52
+ module.exports = {
53
+ VENDOR_REGISTRY: VENDOR_REGISTRY,
54
+ getVendorInfo: getVendorInfo,
55
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.47.0-beta.1",
3
+ "version": "2.47.0-beta.3",
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",
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "dev": "node bin/cli.js --dev",
11
+ "test": "node --test --test-force-exit test/*.test.js",
11
12
  "prepack": "npm pkg delete bin.clay-dev",
12
13
  "postpack": "npm pkg set bin.clay-dev=./bin/cli.js",
13
14
  "semantic-release": "semantic-release"
@@ -48,9 +49,9 @@
48
49
  "homepage": "https://github.com/chadbyte/clay#readme",
49
50
  "author": "Chad",
50
51
  "dependencies": {
51
- "@anthropic-ai/claude-agent-sdk": "^0.3.198",
52
+ "@anthropic-ai/claude-agent-sdk": "^0.3.233",
52
53
  "@lydell/node-pty": "^1.2.0-beta.3",
53
- "@openai/codex": "^0.142.4",
54
+ "@openai/codex": "^0.147.0",
54
55
  "imapflow": "^1.3.1",
55
56
  "nodemailer": "^6.10.1",
56
57
  "qrcode-terminal": "^0.12.0",