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.
- package/lib/codex-defaults.js +12 -2
- package/lib/daemon.js +38 -8
- package/lib/kiro-defaults.js +22 -0
- package/lib/project-connection.js +3 -1
- package/lib/project-notifications.js +3 -1
- package/lib/project-sessions.js +28 -9
- package/lib/project.js +42 -10
- package/lib/public/app.js +8 -0
- package/lib/public/css/mobile-nav.css +53 -9
- package/lib/public/css/sidebar.css +24 -1
- package/lib/public/css/tui-attention.css +4 -63
- package/lib/public/index.html +4 -0
- package/lib/public/kiro-avatar.svg +14 -0
- package/lib/public/modules/app-messages.js +14 -12
- package/lib/public/modules/app-notifications.js +12 -4
- package/lib/public/modules/app-panels.js +10 -4
- package/lib/public/modules/app-rate-limit.js +41 -13
- package/lib/public/modules/app-rendering.js +14 -0
- package/lib/public/modules/input.js +4 -3
- package/lib/public/modules/mate-sidebar.js +3 -3
- package/lib/public/modules/session-tui-view.js +4 -40
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +56 -23
- package/lib/public/modules/sidebar-sessions.js +126 -63
- package/lib/public/modules/tools.js +1 -1
- package/lib/public/modules/tui-grab.js +12 -3
- package/lib/sdk-bridge.js +90 -21
- package/lib/sdk-message-processor.js +4 -4
- package/lib/server.js +4 -0
- package/lib/users-preferences.js +3 -1
- package/lib/yoke/adapters/claude.js +25 -0
- package/lib/yoke/adapters/codex.js +34 -21
- package/lib/yoke/adapters/kiro.js +1170 -0
- package/lib/yoke/index.js +70 -6
- package/lib/yoke/kiro-acp-server.js +328 -0
- package/lib/yoke/vendor-registry.js +55 -0
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
52
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.233",
|
|
52
53
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
53
|
-
"@openai/codex": "^0.
|
|
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",
|