clay-server 3.3.2-beta.3 → 3.4.0-beta.10
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/daemon-projects.js +3 -3
- package/lib/daemon.js +1 -1
- package/lib/project-connection.js +20 -5
- package/lib/project-debate.js +4 -0
- package/lib/project-file-watch.js +77 -16
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project-shell-command.js +160 -0
- package/lib/project-user-message.js +10 -0
- package/lib/project.js +19 -4
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +28 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/icon-strip.css +29 -13
- package/lib/public/css/input.css +79 -0
- package/lib/public/css/mates.css +7 -0
- package/lib/public/css/menus.css +48 -14
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +4 -0
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +43 -4
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/app-header.js +4 -0
- package/lib/public/modules/app-messages.js +28 -5
- package/lib/public/modules/app-panels.js +37 -5
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +22 -1
- package/lib/public/modules/input.js +58 -28
- package/lib/public/modules/mate-sidebar.js +17 -3
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/shell-command.js +148 -0
- package/lib/public/modules/sidebar-mates.js +7 -1
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-projects.js +34 -43
- package/lib/public/modules/sidebar-sessions.js +6 -6
- package/lib/public/modules/split-pair-ui.js +3 -2
- package/lib/public/modules/tools.js +12 -1
- package/lib/public/modules/vendor-priority.js +14 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/lib/public/modules/worktree-location.js +17 -0
- package/lib/public/opencode-avatar.svg +4 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +3 -2
- package/lib/sdk-bridge.js +59 -19
- package/lib/sdk-message-processor.js +10 -1
- package/lib/session-notes-mcp-server.js +7 -1
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/acp-agent-profiles.js +238 -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/antigravity.js +417 -0
- package/lib/yoke/adapters/codex.js +98 -3
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +4 -4
- package/lib/yoke/adapters/opencode.js +7 -0
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +81 -13
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +2 -0
- package/lib/yoke/kiro-acp-server.js +30 -276
- package/lib/yoke/vendor-registry.js +77 -0
- package/package.json +1 -1
|
@@ -9,6 +9,8 @@ var readline = require("readline");
|
|
|
9
9
|
var path = require("path");
|
|
10
10
|
var fs = require("fs");
|
|
11
11
|
var { createRequire } = require("module");
|
|
12
|
+
var { buildUserEnv } = require("../build-user-env");
|
|
13
|
+
var { wrapSpawnAsUser } = require("../os-users");
|
|
12
14
|
|
|
13
15
|
// --- Find the codex binary path ---
|
|
14
16
|
// Mirrors the logic from @openai/codex-sdk findCodexPath()
|
|
@@ -120,14 +122,30 @@ function CodexAppServer(executablePath, opts) {
|
|
|
120
122
|
this._stderrBuf = "";
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
function buildSpawnSpec(executablePath, args, opts, wrapFn) {
|
|
126
|
+
opts = opts || {};
|
|
127
|
+
var osUserInfo = opts.osUserInfo || null;
|
|
128
|
+
var env = osUserInfo
|
|
129
|
+
? Object.assign(buildUserEnv(osUserInfo), opts.env || {})
|
|
130
|
+
: Object.assign({}, process.env, opts.env || {});
|
|
131
|
+
var spawnOptions = {
|
|
132
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
133
|
+
env: env,
|
|
134
|
+
cwd: opts.cwd || process.cwd(),
|
|
135
|
+
};
|
|
136
|
+
if (osUserInfo) {
|
|
137
|
+
spawnOptions.uid = osUserInfo.uid;
|
|
138
|
+
spawnOptions.gid = osUserInfo.gid;
|
|
139
|
+
}
|
|
140
|
+
return (wrapFn || wrapSpawnAsUser)(executablePath, args, spawnOptions);
|
|
141
|
+
}
|
|
142
|
+
|
|
123
143
|
CodexAppServer.prototype.start = function() {
|
|
124
144
|
var self = this;
|
|
125
145
|
|
|
126
146
|
return new Promise(function(resolve, reject) {
|
|
127
147
|
try {
|
|
128
148
|
var args = ["app-server"];
|
|
129
|
-
var env = Object.assign({}, process.env, self.opts.env || {});
|
|
130
|
-
|
|
131
149
|
// Pass config overrides via --config key=value flags
|
|
132
150
|
if (self.opts.config) {
|
|
133
151
|
var configArgs = serializeConfig(self.opts.config, "");
|
|
@@ -136,13 +154,11 @@ CodexAppServer.prototype.start = function() {
|
|
|
136
154
|
}
|
|
137
155
|
}
|
|
138
156
|
|
|
139
|
-
|
|
157
|
+
var spawnSpec = buildSpawnSpec(self.executablePath, args, self.opts);
|
|
158
|
+
var userSuffix = self.opts.osUserInfo ? " as " + self.opts.osUserInfo.user : "";
|
|
159
|
+
console.log("[codex-app-server] Spawning" + userSuffix + ":", spawnSpec.command, spawnSpec.args.join(" "));
|
|
140
160
|
|
|
141
|
-
self.proc = spawn(
|
|
142
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
143
|
-
env: env,
|
|
144
|
-
cwd: self.opts.cwd || process.cwd(),
|
|
145
|
-
});
|
|
161
|
+
self.proc = spawn(spawnSpec.command, spawnSpec.args, spawnSpec.options);
|
|
146
162
|
|
|
147
163
|
self.proc.on("error", function(err) {
|
|
148
164
|
console.error("[codex-app-server] Process error:", err.message);
|
|
@@ -383,4 +399,5 @@ CodexAppServer.prototype.stop = function() {
|
|
|
383
399
|
module.exports = {
|
|
384
400
|
CodexAppServer: CodexAppServer,
|
|
385
401
|
findCodexPath: findCodexPath,
|
|
402
|
+
buildSpawnSpec: buildSpawnSpec,
|
|
386
403
|
};
|
package/lib/yoke/index.js
CHANGED
|
@@ -6,11 +6,18 @@ var instructions = require("./instructions");
|
|
|
6
6
|
var vendorRegistry = require("./vendor-registry");
|
|
7
7
|
var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
|
|
8
8
|
var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
|
|
9
|
+
var createAntigravityAdapter = require("./adapters/antigravity").createAntigravityAdapter;
|
|
10
|
+
var createOpenCodeAdapter = require("./adapters/opencode").createOpenCodeAdapter;
|
|
11
|
+
var createKimiAdapter = require("./adapters/kimi").createKimiAdapter;
|
|
12
|
+
var createGrokAdapter = require("./adapters/grok").createGrokAdapter;
|
|
13
|
+
var createCopilotAdapter = require("./adapters/copilot").createCopilotAdapter;
|
|
14
|
+
var createQwenAdapter = require("./adapters/qwen").createQwenAdapter;
|
|
15
|
+
var createJunieAdapter = require("./adapters/junie").createJunieAdapter;
|
|
9
16
|
var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
|
|
10
17
|
|
|
11
18
|
// Keep the first session in a new project predictable. This order is also
|
|
12
19
|
// used by the UI when a project has no remembered vendor yet.
|
|
13
|
-
var DEFAULT_VENDOR_ORDER = ["claude", "codex", "kiro"];
|
|
20
|
+
var DEFAULT_VENDOR_ORDER = ["claude", "codex", "grok", "kimi", "copilot", "qwen", "junie", "antigravity", "opencode", "kiro"];
|
|
14
21
|
|
|
15
22
|
function resolveDefaultVendor(availableVendors) {
|
|
16
23
|
availableVendors = availableVendors || {};
|
|
@@ -71,6 +78,20 @@ function createAdapter(opts) {
|
|
|
71
78
|
adapter = createClaudeAdapter(opts);
|
|
72
79
|
} else if (vendor === "codex") {
|
|
73
80
|
adapter = createCodexAdapter(opts);
|
|
81
|
+
} else if (vendor === "antigravity") {
|
|
82
|
+
adapter = createAntigravityAdapter(opts);
|
|
83
|
+
} else if (vendor === "opencode") {
|
|
84
|
+
adapter = createOpenCodeAdapter(opts);
|
|
85
|
+
} else if (vendor === "kimi") {
|
|
86
|
+
adapter = createKimiAdapter(opts);
|
|
87
|
+
} else if (vendor === "grok") {
|
|
88
|
+
adapter = createGrokAdapter(opts);
|
|
89
|
+
} else if (vendor === "copilot") {
|
|
90
|
+
adapter = createCopilotAdapter(opts);
|
|
91
|
+
} else if (vendor === "qwen") {
|
|
92
|
+
adapter = createQwenAdapter(opts);
|
|
93
|
+
} else if (vendor === "junie") {
|
|
94
|
+
adapter = createJunieAdapter(opts);
|
|
74
95
|
} else if (vendor === "kiro") {
|
|
75
96
|
adapter = createKiroAdapter(opts);
|
|
76
97
|
} else {
|
|
@@ -96,7 +117,7 @@ function logAuthCheck(auth) {
|
|
|
96
117
|
if (_lastAuthLogKey === key && now - _lastAuthLogAt < 30000) return;
|
|
97
118
|
_lastAuthLogKey = key;
|
|
98
119
|
_lastAuthLogAt = now;
|
|
99
|
-
console.log("[yoke] Auth check:
|
|
120
|
+
console.log("[yoke] Auth check: " + Object.keys(auth).map(function(vendor) { return vendor + "=" + auth[vendor]; }).join(" "));
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
function checkAuth() {
|
|
@@ -227,7 +248,18 @@ function checkAuth() {
|
|
|
227
248
|
}
|
|
228
249
|
}
|
|
229
250
|
|
|
230
|
-
_authCache = {
|
|
251
|
+
_authCache = {
|
|
252
|
+
claude: checkClaude(),
|
|
253
|
+
codex: checkCodex(),
|
|
254
|
+
antigravity: !!lookupBinary("agy"),
|
|
255
|
+
opencode: !!lookupBinary("opencode"),
|
|
256
|
+
kimi: !!lookupBinary("kimi"),
|
|
257
|
+
grok: !!lookupBinary("grok"),
|
|
258
|
+
copilot: !!lookupBinary("copilot"),
|
|
259
|
+
qwen: !!lookupBinary("qwen"),
|
|
260
|
+
junie: !!lookupBinary("junie"),
|
|
261
|
+
kiro: checkKiro(),
|
|
262
|
+
};
|
|
231
263
|
logAuthCheck(_authCache);
|
|
232
264
|
return _authCache;
|
|
233
265
|
}
|
|
@@ -249,7 +281,7 @@ function checkInstalled() {
|
|
|
249
281
|
|
|
250
282
|
var fs = require("fs");
|
|
251
283
|
var execFileSync = require("child_process").execFileSync;
|
|
252
|
-
var result = { claude: false, codex: false, kiro: false };
|
|
284
|
+
var result = { claude: false, codex: false, antigravity: false, opencode: false, kimi: false, grok: false, copilot: false, qwen: false, junie: false, kiro: false };
|
|
253
285
|
try {
|
|
254
286
|
if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
255
287
|
else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -267,16 +299,30 @@ function checkInstalled() {
|
|
|
267
299
|
codexBin = findCodexPath();
|
|
268
300
|
if (codexBin && fs.existsSync(codexBin)) {
|
|
269
301
|
result.codex = true;
|
|
270
|
-
_installedCache = result;
|
|
271
|
-
return result;
|
|
272
302
|
}
|
|
273
303
|
} catch (e) {}
|
|
274
304
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
if (!result.codex) {
|
|
306
|
+
var whichOut = process.platform === "win32"
|
|
307
|
+
? execFileSync("where", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
|
|
308
|
+
: execFileSync("which", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
309
|
+
if (whichOut.trim()) result.codex = true;
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {}
|
|
312
|
+
try {
|
|
313
|
+
var antigravityBinary = process.platform === "win32"
|
|
314
|
+
? execFileSync("where", ["agy"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
|
|
315
|
+
: execFileSync("which", ["agy"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
316
|
+
result.antigravity = !!antigravityBinary.trim();
|
|
279
317
|
} catch (e) {}
|
|
318
|
+
var acpVendorKeys = ["opencode", "kimi", "grok", "copilot", "qwen", "junie"];
|
|
319
|
+
for (var acpIndex = 0; acpIndex < acpVendorKeys.length; acpIndex++) {
|
|
320
|
+
try {
|
|
321
|
+
var acpProfiles = require("./acp-agent-profiles");
|
|
322
|
+
var acpVendor = acpVendorKeys[acpIndex];
|
|
323
|
+
result[acpVendor] = !!acpProfiles.findAcpAgentPath(acpProfiles.getAcpAgentProfile(acpVendor));
|
|
324
|
+
} catch (e) {}
|
|
325
|
+
}
|
|
280
326
|
_installedCache = result;
|
|
281
327
|
return result;
|
|
282
328
|
}
|
|
@@ -301,7 +347,7 @@ function createAdapters(opts) {
|
|
|
301
347
|
// that `claude auth status` does not always detect. Runtime auth failures are
|
|
302
348
|
// handled downstream via query-level error detection.
|
|
303
349
|
var installed = checkInstalled();
|
|
304
|
-
var auth = { claude: false, codex: false, kiro: false };
|
|
350
|
+
var auth = { claude: false, codex: false, antigravity: false, opencode: false, kimi: false, grok: false, copilot: false, qwen: false, junie: false, kiro: false };
|
|
305
351
|
var adapters = {};
|
|
306
352
|
|
|
307
353
|
function supportsConfiguredIsolation(vendor) {
|
|
@@ -328,7 +374,7 @@ function createAdapters(opts) {
|
|
|
328
374
|
|
|
329
375
|
if (installed.codex && supportsConfiguredIsolation("codex")) {
|
|
330
376
|
try {
|
|
331
|
-
adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
|
|
377
|
+
adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers });
|
|
332
378
|
auth.codex = true;
|
|
333
379
|
console.log("[yoke] Adapter created: codex");
|
|
334
380
|
} catch (e) {
|
|
@@ -336,6 +382,23 @@ function createAdapters(opts) {
|
|
|
336
382
|
}
|
|
337
383
|
}
|
|
338
384
|
|
|
385
|
+
var subprocessVendors = ["antigravity", "opencode", "kimi", "grok", "copilot", "qwen", "junie"];
|
|
386
|
+
for (var subprocessIndex = 0; subprocessIndex < subprocessVendors.length; subprocessIndex++) {
|
|
387
|
+
var subprocessVendor = subprocessVendors[subprocessIndex];
|
|
388
|
+
var subprocessInfo = vendorRegistry.getVendorInfo(subprocessVendor);
|
|
389
|
+
if (installed[subprocessVendor] && supportsConfiguredIsolation(subprocessVendor)) {
|
|
390
|
+
try {
|
|
391
|
+
adapters[subprocessVendor] = createAdapter({ vendor: subprocessVendor, cwd: opts.cwd, slug: opts.slug });
|
|
392
|
+
auth[subprocessVendor] = true;
|
|
393
|
+
console.log("[yoke] Adapter created: " + subprocessVendor);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.error("[yoke] Failed to create adapter for " + subprocessVendor + ":", e.message);
|
|
396
|
+
}
|
|
397
|
+
} else if (installed[subprocessVendor] && opts.osUsers) {
|
|
398
|
+
console.log("[yoke] " + subprocessInfo.displayName + " adapter disabled: OS-user isolation requires per-user spawning");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
339
402
|
var kiroInfo = vendorRegistry.getVendorInfo("kiro");
|
|
340
403
|
if (installed.kiro && supportsConfiguredIsolation("kiro")) {
|
|
341
404
|
try {
|
|
@@ -372,7 +435,12 @@ async function lazyCreateAdapter(adapters, vendor, opts) {
|
|
|
372
435
|
if (!installed[vendor]) return null;
|
|
373
436
|
|
|
374
437
|
try {
|
|
375
|
-
var ad = createAdapter({
|
|
438
|
+
var ad = createAdapter({
|
|
439
|
+
vendor: vendor,
|
|
440
|
+
cwd: opts.cwd,
|
|
441
|
+
slug: opts.slug,
|
|
442
|
+
osUsers: !!(opts.osUsers || opts.linuxUser),
|
|
443
|
+
});
|
|
376
444
|
if (typeof ad.init === "function") {
|
|
377
445
|
await ad.init(opts || {});
|
|
378
446
|
}
|
package/lib/yoke/instructions.js
CHANGED
|
@@ -18,12 +18,15 @@ var KNOWN_FILES = [
|
|
|
18
18
|
{ file: ".cursorrules", label: ".cursorrules" },
|
|
19
19
|
{ file: ".github/copilot-instructions.md", label: ".github/copilot-instructions.md" },
|
|
20
20
|
{ file: "COPILOT.md", label: "COPILOT.md" },
|
|
21
|
+
{ file: "QWEN.md", label: "QWEN.md" },
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
// Files each vendor reads natively (skip these to avoid duplication).
|
|
24
25
|
var NATIVE_FILES = {
|
|
25
26
|
claude: ["CLAUDE.md"],
|
|
26
27
|
codex: ["AGENTS.md"],
|
|
28
|
+
copilot: [".github/copilot-instructions.md", "COPILOT.md"],
|
|
29
|
+
qwen: ["QWEN.md"],
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
// Scan projectDir for instruction files and return merged text.
|
package/lib/yoke/interface.js
CHANGED
|
@@ -41,6 +41,7 @@ var TOOL_POLICIES = ["ask", "allow-all"];
|
|
|
41
41
|
* .setToolPolicy(policy) - "ask" | "allow-all"
|
|
42
42
|
* .stopTask(taskId)
|
|
43
43
|
* .getContextUsage() - Promise<object|null>
|
|
44
|
+
* .endInput() - stop accepting turns after queued input drains
|
|
44
45
|
* .abort()
|
|
45
46
|
* .close()
|
|
46
47
|
*/
|
|
@@ -59,6 +60,7 @@ var QUERY_HANDLE_METHODS = [
|
|
|
59
60
|
"setToolPolicy",
|
|
60
61
|
"stopTask",
|
|
61
62
|
"getContextUsage",
|
|
63
|
+
"endInput",
|
|
62
64
|
"abort",
|
|
63
65
|
"close",
|
|
64
66
|
];
|
|
@@ -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,
|