clay-server 2.46.0 → 2.47.0-beta.2
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/bin/cli.js +10 -18
- package/lib/daemon.js +49 -8
- package/lib/kiro-defaults.js +22 -0
- package/lib/migrate-single-user.js +286 -0
- package/lib/project-connection.js +1 -0
- package/lib/project-notifications.js +1 -1
- package/lib/project-sessions.js +13 -2
- package/lib/project.js +31 -7
- package/lib/public/app.js +6 -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 +11 -10
- package/lib/public/modules/app-panels.js +5 -1
- package/lib/public/modules/app-rendering.js +12 -0
- package/lib/public/modules/input.js +5 -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/sdk-bridge.js +50 -5
- package/lib/sdk-message-processor.js +4 -2
- package/lib/server-auth.js +14 -5
- package/lib/server.js +4 -0
- package/lib/users-preferences.js +3 -1
- package/lib/users.js +14 -0
- package/lib/yoke/adapters/codex.js +7 -4
- package/lib/yoke/adapters/kiro.js +1156 -0
- package/lib/yoke/index.js +56 -4
- package/lib/yoke/kiro-acp-server.js +328 -0
- package/package.json +2 -1
package/lib/yoke/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var iface = require("./interface");
|
|
|
5
5
|
var instructions = require("./instructions");
|
|
6
6
|
var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
|
|
7
7
|
var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
|
|
8
|
+
var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Wrap adapter.createQuery to inject cross-vendor project instructions.
|
|
@@ -50,6 +51,8 @@ function createAdapter(opts) {
|
|
|
50
51
|
adapter = createClaudeAdapter(opts);
|
|
51
52
|
} else if (vendor === "codex") {
|
|
52
53
|
adapter = createCodexAdapter(opts);
|
|
54
|
+
} else if (vendor === "kiro") {
|
|
55
|
+
adapter = createKiroAdapter(opts);
|
|
53
56
|
} else {
|
|
54
57
|
throw new Error("[YOKE] Unknown adapter vendor: " + vendor);
|
|
55
58
|
}
|
|
@@ -73,7 +76,7 @@ function logAuthCheck(auth) {
|
|
|
73
76
|
if (_lastAuthLogKey === key && now - _lastAuthLogAt < 30000) return;
|
|
74
77
|
_lastAuthLogKey = key;
|
|
75
78
|
_lastAuthLogAt = now;
|
|
76
|
-
console.log("[yoke] Auth check: claude=" + auth.claude + " codex=" + auth.codex);
|
|
79
|
+
console.log("[yoke] Auth check: claude=" + auth.claude + " codex=" + auth.codex + " kiro=" + auth.kiro);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
function checkAuth() {
|
|
@@ -181,7 +184,30 @@ function checkAuth() {
|
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
function resolveKiroBinary() {
|
|
188
|
+
var fs = require("fs");
|
|
189
|
+
try {
|
|
190
|
+
var findKiroPath = require("./kiro-acp-server").findKiroPath;
|
|
191
|
+
var kiroBin = findKiroPath();
|
|
192
|
+
if (kiroBin && fs.existsSync(kiroBin)) return kiroBin;
|
|
193
|
+
} catch (e) {}
|
|
194
|
+
return lookupBinary("kiro-cli");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function checkKiro() {
|
|
198
|
+
try {
|
|
199
|
+
var kiroBin = resolveKiroBinary();
|
|
200
|
+
if (!kiroBin) return false;
|
|
201
|
+
// `kiro-cli whoami` exits 0 and prints account details when logged in,
|
|
202
|
+
// and exits non-zero otherwise.
|
|
203
|
+
execFileSync(kiroBin, ["whoami"], { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
204
|
+
return true;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_authCache = { claude: checkClaude(), codex: checkCodex(), kiro: checkKiro() };
|
|
185
211
|
logAuthCheck(_authCache);
|
|
186
212
|
return _authCache;
|
|
187
213
|
}
|
|
@@ -203,12 +229,17 @@ function checkInstalled() {
|
|
|
203
229
|
|
|
204
230
|
var fs = require("fs");
|
|
205
231
|
var execFileSync = require("child_process").execFileSync;
|
|
206
|
-
var result = { claude: false, codex: false };
|
|
232
|
+
var result = { claude: false, codex: false, kiro: false };
|
|
207
233
|
try {
|
|
208
234
|
if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
209
235
|
else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
210
236
|
result.claude = true;
|
|
211
237
|
} catch (e) {}
|
|
238
|
+
try {
|
|
239
|
+
var findKiroPath = require("./kiro-acp-server").findKiroPath;
|
|
240
|
+
var kiroBin = findKiroPath();
|
|
241
|
+
if (kiroBin && fs.existsSync(kiroBin)) result.kiro = true;
|
|
242
|
+
} catch (e) {}
|
|
212
243
|
try {
|
|
213
244
|
var codexBin = null;
|
|
214
245
|
try {
|
|
@@ -250,13 +281,17 @@ function createAdapters(opts) {
|
|
|
250
281
|
// that `claude auth status` does not always detect. Runtime auth failures are
|
|
251
282
|
// handled downstream via query-level error detection.
|
|
252
283
|
var installed = checkInstalled();
|
|
253
|
-
var auth = { claude: false, codex: false };
|
|
284
|
+
var auth = { claude: false, codex: false, kiro: false };
|
|
254
285
|
var adapters = {};
|
|
255
286
|
|
|
256
287
|
if (installed.claude) {
|
|
257
288
|
try {
|
|
258
289
|
if (!_sharedClaudeAdapter) {
|
|
259
290
|
_sharedClaudeAdapter = createAdapter({ vendor: "claude", cwd: opts.cwd });
|
|
291
|
+
// This adapter instance is reused by every project. Mark it so
|
|
292
|
+
// per-project teardown never shuts it down on behalf of one project
|
|
293
|
+
// (see destroy() in lib/project.js).
|
|
294
|
+
_sharedClaudeAdapter.shared = true;
|
|
260
295
|
}
|
|
261
296
|
adapters.claude = _sharedClaudeAdapter;
|
|
262
297
|
auth.claude = true;
|
|
@@ -276,6 +311,18 @@ function createAdapters(opts) {
|
|
|
276
311
|
}
|
|
277
312
|
}
|
|
278
313
|
|
|
314
|
+
if (installed.kiro && !opts.osUsers) {
|
|
315
|
+
try {
|
|
316
|
+
adapters.kiro = createAdapter({ vendor: "kiro", cwd: opts.cwd, slug: opts.slug });
|
|
317
|
+
auth.kiro = true;
|
|
318
|
+
console.log("[yoke] Adapter created: kiro");
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.error("[yoke] Failed to create adapter for kiro:", e.message);
|
|
321
|
+
}
|
|
322
|
+
} else if (installed.kiro && opts.osUsers) {
|
|
323
|
+
console.log("[yoke] Kiro adapter disabled: OS-user isolation requires per-user ACP spawning");
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
return { adapters: adapters, auth: auth };
|
|
280
327
|
}
|
|
281
328
|
|
|
@@ -287,6 +334,11 @@ function createAdapters(opts) {
|
|
|
287
334
|
async function lazyCreateAdapter(adapters, vendor, opts) {
|
|
288
335
|
opts = opts || {};
|
|
289
336
|
|
|
337
|
+
if (vendor === "kiro" && (opts.osUsers || opts.linuxUser)) {
|
|
338
|
+
console.log("[yoke] Refusing lazy Kiro adapter creation for OS-isolated user " + (opts.linuxUser || "unknown"));
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
290
342
|
// Force re-check since user may have logged in after server start
|
|
291
343
|
invalidateAuthCache();
|
|
292
344
|
var installed = checkInstalled();
|
|
@@ -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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.47.0-beta.2",
|
|
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"
|