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.
Files changed (77) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +1 -1
  3. package/lib/project-connection.js +20 -5
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-file-watch.js +77 -16
  6. package/lib/project-mate-interaction.js +21 -1
  7. package/lib/project-memory.js +3 -0
  8. package/lib/project-shell-command.js +160 -0
  9. package/lib/project-user-message.js +10 -0
  10. package/lib/project.js +19 -4
  11. package/lib/public/antigravity-avatar.png +0 -0
  12. package/lib/public/app.js +28 -0
  13. package/lib/public/copilot-avatar.svg +5 -0
  14. package/lib/public/css/command-palette.css +22 -2
  15. package/lib/public/css/icon-strip.css +29 -13
  16. package/lib/public/css/input.css +79 -0
  17. package/lib/public/css/mates.css +7 -0
  18. package/lib/public/css/menus.css +48 -14
  19. package/lib/public/css/messages.css +9 -0
  20. package/lib/public/css/pane.css +4 -0
  21. package/lib/public/css/pwa-mobile.css +17 -0
  22. package/lib/public/css/title-bar.css +19 -0
  23. package/lib/public/grok-avatar.svg +4 -0
  24. package/lib/public/index.html +43 -4
  25. package/lib/public/junie-avatar.svg +11 -0
  26. package/lib/public/kimi-avatar.svg +4 -0
  27. package/lib/public/modules/app-header.js +4 -0
  28. package/lib/public/modules/app-messages.js +28 -5
  29. package/lib/public/modules/app-panels.js +37 -5
  30. package/lib/public/modules/app-projects.js +3 -1
  31. package/lib/public/modules/app-rendering.js +22 -1
  32. package/lib/public/modules/input.js +58 -28
  33. package/lib/public/modules/mate-sidebar.js +17 -3
  34. package/lib/public/modules/notifications.js +7 -1
  35. package/lib/public/modules/pane-bridge.js +11 -0
  36. package/lib/public/modules/pane-links.js +23 -0
  37. package/lib/public/modules/project-switcher.js +7 -6
  38. package/lib/public/modules/shell-command.js +148 -0
  39. package/lib/public/modules/sidebar-mates.js +7 -1
  40. package/lib/public/modules/sidebar-mobile.js +2 -1
  41. package/lib/public/modules/sidebar-projects.js +34 -43
  42. package/lib/public/modules/sidebar-sessions.js +6 -6
  43. package/lib/public/modules/split-pair-ui.js +3 -2
  44. package/lib/public/modules/tools.js +12 -1
  45. package/lib/public/modules/vendor-priority.js +14 -0
  46. package/lib/public/modules/vendor-selection.js +20 -0
  47. package/lib/public/modules/worktree-location.js +17 -0
  48. package/lib/public/opencode-avatar.svg +4 -0
  49. package/lib/public/qwen-avatar.svg +4 -0
  50. package/lib/public/style.css +3 -2
  51. package/lib/sdk-bridge.js +59 -19
  52. package/lib/sdk-message-processor.js +10 -1
  53. package/lib/session-notes-mcp-server.js +7 -1
  54. package/lib/worktree.js +9 -4
  55. package/lib/ws-schema.js +3 -0
  56. package/lib/yoke/acp-agent-profiles.js +238 -0
  57. package/lib/yoke/acp-driver-runtime.js +50 -0
  58. package/lib/yoke/acp-event-normalizer.js +179 -0
  59. package/lib/yoke/acp-process-manager.js +264 -0
  60. package/lib/yoke/acp-query-handle.js +487 -0
  61. package/lib/yoke/adapters/acp.js +317 -0
  62. package/lib/yoke/adapters/antigravity.js +417 -0
  63. package/lib/yoke/adapters/codex.js +98 -3
  64. package/lib/yoke/adapters/copilot.js +7 -0
  65. package/lib/yoke/adapters/grok.js +7 -0
  66. package/lib/yoke/adapters/junie.js +7 -0
  67. package/lib/yoke/adapters/kimi.js +7 -0
  68. package/lib/yoke/adapters/kiro.js +4 -4
  69. package/lib/yoke/adapters/opencode.js +7 -0
  70. package/lib/yoke/adapters/qwen.js +7 -0
  71. package/lib/yoke/codex-app-server.js +25 -8
  72. package/lib/yoke/index.js +81 -13
  73. package/lib/yoke/instructions.js +3 -0
  74. package/lib/yoke/interface.js +2 -0
  75. package/lib/yoke/kiro-acp-server.js +30 -276
  76. package/lib/yoke/vendor-registry.js +77 -0
  77. 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
- console.log("[codex-app-server] Spawning:", self.executablePath, args.join(" "));
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(self.executablePath, args, {
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: claude=" + auth.claude + " codex=" + auth.codex + " kiro=" + auth.kiro);
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 = { claude: checkClaude(), codex: checkCodex(), kiro: checkKiro() };
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
- var whichOut = process.platform === "win32"
276
- ? execFileSync("where", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
277
- : execFileSync("which", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
278
- if (whichOut.trim()) result.codex = true;
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({ vendor: vendor, cwd: opts.cwd, slug: opts.slug });
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
  }
@@ -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.
@@ -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 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).
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 REAL_HOME;
26
- try { REAL_HOME = require("../config").REAL_HOME; } catch (e) { REAL_HOME = require("os").homedir(); }
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(REAL_HOME || "", ".local", "bin", binName),
30
- path.join(REAL_HOME || "", "bin", binName),
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
- 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 = "";
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.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
- }
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
- // 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);
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,