clay-server 3.3.2-beta.3 → 3.4.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/lib/project-file-watch.js +77 -16
- package/lib/project-shell-command.js +160 -0
- package/lib/project-user-message.js +10 -0
- package/lib/project.js +13 -0
- package/lib/public/app.js +10 -0
- package/lib/public/css/input.css +67 -0
- package/lib/public/css/mates.css +1 -0
- package/lib/public/gemini-avatar.svg +11 -0
- package/lib/public/index.html +13 -0
- package/lib/public/modules/app-messages.js +16 -1
- package/lib/public/modules/app-panels.js +13 -1
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rendering.js +7 -1
- package/lib/public/modules/input.js +58 -28
- package/lib/public/modules/mate-sidebar.js +9 -3
- package/lib/public/modules/shell-command.js +148 -0
- package/lib/public/modules/sidebar-mates.js +7 -1
- package/lib/public/modules/tools.js +7 -1
- package/lib/public/opencode-avatar.svg +4 -0
- package/lib/sdk-bridge.js +18 -2
- package/lib/sdk-message-processor.js +10 -1
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/acp-agent-profiles.js +188 -0
- package/lib/yoke/acp-driver-runtime.js +50 -0
- package/lib/yoke/acp-event-normalizer.js +179 -0
- package/lib/yoke/acp-process-manager.js +264 -0
- package/lib/yoke/acp-query-handle.js +487 -0
- package/lib/yoke/adapters/acp.js +317 -0
- package/lib/yoke/adapters/gemini.js +7 -0
- package/lib/yoke/adapters/kiro.js +4 -4
- package/lib/yoke/adapters/opencode.js +7 -0
- package/lib/yoke/index.js +45 -11
- package/lib/yoke/interface.js +2 -0
- package/lib/yoke/kiro-acp-server.js +30 -276
- package/lib/yoke/vendor-registry.js +22 -0
- package/package.json +1 -1
|
@@ -1,33 +1,24 @@
|
|
|
1
|
-
// Kiro ACP Server
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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).
|
|
1
|
+
// Kiro ACP Server Profile
|
|
2
|
+
// -----------------------
|
|
3
|
+
// Supplies Kiro's executable discovery, command arguments, and auth-error
|
|
4
|
+
// translation to the shared ACP process manager.
|
|
10
5
|
|
|
11
|
-
var { spawn } = require("child_process");
|
|
12
|
-
var readline = require("readline");
|
|
13
6
|
var path = require("path");
|
|
14
7
|
var fs = require("fs");
|
|
8
|
+
var { AcpProcessManager } = require("./acp-process-manager");
|
|
15
9
|
|
|
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
10
|
function findKiroPath() {
|
|
20
11
|
if (process.env.KIRO_CLI_PATH && fs.existsSync(process.env.KIRO_CLI_PATH)) {
|
|
21
12
|
return process.env.KIRO_CLI_PATH;
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
var binName = process.platform === "win32" ? "kiro-cli.exe" : "kiro-cli";
|
|
25
|
-
var
|
|
26
|
-
try {
|
|
16
|
+
var realHome;
|
|
17
|
+
try { realHome = require("../config").REAL_HOME; } catch (e) { realHome = require("os").homedir(); }
|
|
27
18
|
|
|
28
19
|
var candidates = [
|
|
29
|
-
path.join(
|
|
30
|
-
path.join(
|
|
20
|
+
path.join(realHome || "", ".local", "bin", binName),
|
|
21
|
+
path.join(realHome || "", "bin", binName),
|
|
31
22
|
"/usr/local/bin/" + binName,
|
|
32
23
|
"/opt/homebrew/bin/" + binName,
|
|
33
24
|
];
|
|
@@ -35,7 +26,6 @@ function findKiroPath() {
|
|
|
35
26
|
if (candidates[i] && fs.existsSync(candidates[i])) return candidates[i];
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
// Fall back to a PATH lookup.
|
|
39
29
|
try {
|
|
40
30
|
var execFileSync = require("child_process").execFileSync;
|
|
41
31
|
var out = process.platform === "win32"
|
|
@@ -48,280 +38,44 @@ function findKiroPath() {
|
|
|
48
38
|
throw new Error("Could not find kiro-cli binary (looked in ~/.local/bin, PATH, KIRO_CLI_PATH)");
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
// --- KiroAcpServer ---
|
|
52
|
-
|
|
53
41
|
function KiroAcpServer(executablePath, opts) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
42
|
+
var kiroOpts = opts || {};
|
|
43
|
+
var args = ["acp"];
|
|
44
|
+
if (kiroOpts.extraArgs && kiroOpts.extraArgs.length) args = args.concat(kiroOpts.extraArgs);
|
|
45
|
+
|
|
46
|
+
AcpProcessManager.call(this, executablePath || findKiroPath(), {
|
|
47
|
+
args: args,
|
|
48
|
+
cwd: kiroOpts.cwd,
|
|
49
|
+
env: kiroOpts.env,
|
|
50
|
+
logPrefix: "kiro-acp-server",
|
|
51
|
+
logStderr: false,
|
|
52
|
+
onStderrLine: function(line, manager) {
|
|
53
|
+
if (line.trim()) console.log("[kiro-acp-server stderr]", line);
|
|
54
|
+
manager._maybeSignalAuthError(line);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
68
57
|
this._authSignalSent = false;
|
|
69
58
|
}
|
|
70
59
|
|
|
71
|
-
KiroAcpServer.prototype
|
|
72
|
-
|
|
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
|
-
}
|
|
60
|
+
KiroAcpServer.prototype = Object.create(AcpProcessManager.prototype);
|
|
61
|
+
KiroAcpServer.prototype.constructor = KiroAcpServer;
|
|
184
62
|
|
|
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
63
|
KiroAcpServer.prototype._maybeSignalAuthError = function(line) {
|
|
236
64
|
if (!this.handlers.length || !line || this._authSignalSent) return;
|
|
237
65
|
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
66
|
|| (/\b401\b/.test(line) && /unauthorized|credential|token/i.test(line));
|
|
239
67
|
if (!isAuth) return;
|
|
68
|
+
|
|
240
69
|
this._authSignalSent = true;
|
|
241
70
|
var self = this;
|
|
242
71
|
var dedupeTimer = setTimeout(function() { self._authSignalSent = false; }, 15000);
|
|
243
|
-
// Don't hold the event loop open just to reset a dedupe flag.
|
|
244
72
|
if (dedupeTimer && typeof dedupeTimer.unref === "function") dedupeTimer.unref();
|
|
245
|
-
|
|
246
|
-
|
|
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);
|
|
73
|
+
this._handleMessage({
|
|
74
|
+
method: "_kiro/error",
|
|
75
|
+
params: { error: { kiroErrorInfo: "unauthorized", message: line } },
|
|
268
76
|
});
|
|
269
77
|
};
|
|
270
78
|
|
|
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
79
|
module.exports = {
|
|
326
80
|
KiroAcpServer: KiroAcpServer,
|
|
327
81
|
findKiroPath: findKiroPath,
|
|
@@ -40,6 +40,28 @@ 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",
|
|
48
|
+
sessionModes: ["gui"],
|
|
49
|
+
effortLevels: [],
|
|
50
|
+
osUserIsolation: false,
|
|
51
|
+
usageDashboard: null,
|
|
52
|
+
rateLimitTracking: false,
|
|
53
|
+
},
|
|
54
|
+
opencode: {
|
|
55
|
+
displayName: "OpenCode",
|
|
56
|
+
loginCommand: "opencode auth login",
|
|
57
|
+
binaryName: "opencode",
|
|
58
|
+
avatar: "/opencode-avatar.svg",
|
|
59
|
+
sessionModes: ["gui"],
|
|
60
|
+
effortLevels: [],
|
|
61
|
+
osUserIsolation: false,
|
|
62
|
+
usageDashboard: null,
|
|
63
|
+
rateLimitTracking: false,
|
|
64
|
+
},
|
|
43
65
|
kiro: {
|
|
44
66
|
displayName: "Kiro CLI",
|
|
45
67
|
loginCommand: "kiro-cli login",
|
package/package.json
CHANGED