acpx 0.1.0 → 0.1.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.
Files changed (4) hide show
  1. package/LICENSE +18 -12
  2. package/README.md +123 -61
  3. package/dist/cli.js +1665 -222
  4. package/package.json +33 -5
package/dist/cli.js CHANGED
@@ -2,54 +2,396 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command, CommanderError, InvalidArgumentError } from "commander";
5
+ import path3 from "path";
6
+
7
+ // src/agent-registry.ts
8
+ var AGENT_REGISTRY = {
9
+ codex: "npx @zed-industries/codex-acp",
10
+ claude: "npx @zed-industries/claude-agent-acp",
11
+ gemini: "gemini",
12
+ opencode: "npx opencode-ai",
13
+ pi: "npx pi-acp"
14
+ };
15
+ var DEFAULT_AGENT_NAME = "codex";
16
+ function normalizeAgentName(value) {
17
+ return value.trim().toLowerCase();
18
+ }
19
+ function resolveAgentCommand(agentName) {
20
+ const normalized = normalizeAgentName(agentName);
21
+ return AGENT_REGISTRY[normalized] ?? agentName;
22
+ }
23
+ function listBuiltInAgents() {
24
+ return Object.keys(AGENT_REGISTRY);
25
+ }
5
26
 
6
27
  // src/output.ts
28
+ var MAX_THOUGHT_CHARS = 900;
29
+ var MAX_INLINE_CHARS = 220;
30
+ var MAX_OUTPUT_CHARS = 2e3;
31
+ var MAX_OUTPUT_LINES = 28;
32
+ var MAX_LOCATION_ITEMS = 5;
33
+ var OUTPUT_PRIORITY_KEYS = [
34
+ "stdout",
35
+ "stderr",
36
+ "output",
37
+ "content",
38
+ "text",
39
+ "message",
40
+ "result",
41
+ "response",
42
+ "value"
43
+ ];
7
44
  function nowIso() {
8
45
  return (/* @__PURE__ */ new Date()).toISOString();
9
46
  }
10
47
  function asStatus(status) {
11
48
  return status ?? "unknown";
12
49
  }
50
+ function isFinalStatus(status) {
51
+ return status === "completed" || status === "failed";
52
+ }
53
+ function toStatusLabel(status) {
54
+ switch (status) {
55
+ case "in_progress":
56
+ return "running";
57
+ case "pending":
58
+ return "pending";
59
+ case "completed":
60
+ return "completed";
61
+ case "failed":
62
+ return "failed";
63
+ default:
64
+ return "running";
65
+ }
66
+ }
67
+ function asRecord(value) {
68
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
69
+ return void 0;
70
+ }
71
+ return value;
72
+ }
73
+ function collapseWhitespace(value) {
74
+ return value.replace(/\s+/g, " ").trim();
75
+ }
76
+ function truncate(value, maxChars) {
77
+ if (value.length <= maxChars) {
78
+ return value;
79
+ }
80
+ if (maxChars <= 3) {
81
+ return value.slice(0, maxChars);
82
+ }
83
+ return `${value.slice(0, maxChars - 3)}...`;
84
+ }
85
+ function toInline(value, maxChars = MAX_INLINE_CHARS) {
86
+ return truncate(collapseWhitespace(value), maxChars);
87
+ }
88
+ function indentBlock(value, prefix) {
89
+ return value.split("\n").map((line) => `${prefix}${line}`).join("\n");
90
+ }
91
+ function dedupeStrings(values) {
92
+ const seen = /* @__PURE__ */ new Set();
93
+ const result = [];
94
+ for (const value of values) {
95
+ if (seen.has(value)) {
96
+ continue;
97
+ }
98
+ seen.add(value);
99
+ result.push(value);
100
+ }
101
+ return result;
102
+ }
103
+ function safeJson(value, spacing) {
104
+ const seen = /* @__PURE__ */ new WeakSet();
105
+ try {
106
+ return JSON.stringify(
107
+ value,
108
+ (_key, entry) => {
109
+ if (typeof entry === "bigint") {
110
+ return `${entry}n`;
111
+ }
112
+ if (typeof entry === "function") {
113
+ return `[Function ${entry.name || "anonymous"}]`;
114
+ }
115
+ if (typeof entry === "symbol") {
116
+ return entry.toString();
117
+ }
118
+ if (entry && typeof entry === "object") {
119
+ if (seen.has(entry)) {
120
+ return "[Circular]";
121
+ }
122
+ seen.add(entry);
123
+ }
124
+ return entry;
125
+ },
126
+ spacing
127
+ );
128
+ } catch {
129
+ return void 0;
130
+ }
131
+ }
132
+ function readFirstString(source, keys) {
133
+ for (const key of keys) {
134
+ const value = source[key];
135
+ if (typeof value === "string" && value.trim()) {
136
+ return value.trim();
137
+ }
138
+ }
139
+ return void 0;
140
+ }
141
+ function readFirstStringArray(source, keys) {
142
+ for (const key of keys) {
143
+ const value = source[key];
144
+ if (!Array.isArray(value)) {
145
+ continue;
146
+ }
147
+ const entries = value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
148
+ if (entries.length > 0) {
149
+ return entries;
150
+ }
151
+ }
152
+ return void 0;
153
+ }
154
+ function summarizeToolInput(rawInput) {
155
+ if (rawInput == null) {
156
+ return void 0;
157
+ }
158
+ if (typeof rawInput === "string" || typeof rawInput === "number" || typeof rawInput === "boolean") {
159
+ return toInline(String(rawInput));
160
+ }
161
+ const record = asRecord(rawInput);
162
+ if (record) {
163
+ const command = readFirstString(record, ["command", "cmd", "program"]);
164
+ const args = readFirstStringArray(record, ["args", "arguments"]);
165
+ if (command) {
166
+ const invocation = [command, ...args ?? []].join(" ");
167
+ return toInline(invocation);
168
+ }
169
+ const location = readFirstString(record, [
170
+ "path",
171
+ "file",
172
+ "filePath",
173
+ "filepath",
174
+ "target",
175
+ "uri",
176
+ "url"
177
+ ]);
178
+ if (location) {
179
+ return toInline(location);
180
+ }
181
+ const query = readFirstString(record, ["query", "pattern", "text", "search"]);
182
+ if (query) {
183
+ return toInline(query);
184
+ }
185
+ }
186
+ const json = safeJson(rawInput, 0);
187
+ return json ? toInline(json) : void 0;
188
+ }
189
+ function formatLocations(locations) {
190
+ if (!locations || locations.length === 0) {
191
+ return void 0;
192
+ }
193
+ const unique = /* @__PURE__ */ new Set();
194
+ for (const location of locations) {
195
+ const path4 = location.path?.trim();
196
+ if (!path4) {
197
+ continue;
198
+ }
199
+ const line = typeof location.line === "number" && Number.isFinite(location.line) ? `:${Math.max(1, Math.trunc(location.line))}` : "";
200
+ unique.add(`${path4}${line}`);
201
+ }
202
+ const items = [...unique];
203
+ if (items.length === 0) {
204
+ return void 0;
205
+ }
206
+ const visible = items.slice(0, MAX_LOCATION_ITEMS);
207
+ const hidden = items.length - visible.length;
208
+ if (hidden <= 0) {
209
+ return visible.join(", ");
210
+ }
211
+ return `${visible.join(", ")}, +${hidden} more`;
212
+ }
213
+ function summarizeDiff(path4, oldText, newText) {
214
+ const oldLines = oldText ? oldText.split("\n").length : 0;
215
+ const newLines = newText.split("\n").length;
216
+ const delta = newLines - oldLines;
217
+ if (delta === 0) {
218
+ return `diff ${path4} (line count unchanged)`;
219
+ }
220
+ const signedDelta = `${delta > 0 ? "+" : ""}${delta}`;
221
+ return `diff ${path4} (${signedDelta} lines)`;
222
+ }
223
+ function textFromContentBlock(content) {
224
+ switch (content.type) {
225
+ case "text":
226
+ return content.text;
227
+ case "resource_link":
228
+ return content.title ?? content.name ?? content.uri;
229
+ case "resource": {
230
+ if ("text" in content.resource && typeof content.resource.text === "string") {
231
+ return content.resource.text;
232
+ }
233
+ const uri = content.resource.uri;
234
+ const mimeType = content.resource.mimeType;
235
+ return `[resource] ${uri}${mimeType ? ` (${mimeType})` : ""}`;
236
+ }
237
+ case "image":
238
+ return `[image] ${content.mimeType}`;
239
+ case "audio":
240
+ return `[audio] ${content.mimeType}`;
241
+ default:
242
+ return void 0;
243
+ }
244
+ }
245
+ function summarizeToolContent(content) {
246
+ if (!content || content.length === 0) {
247
+ return void 0;
248
+ }
249
+ const fragments = [];
250
+ for (const entry of content) {
251
+ if (entry.type === "content") {
252
+ const text = textFromContentBlock(entry.content);
253
+ if (text && text.trim()) {
254
+ fragments.push(text.trimEnd());
255
+ }
256
+ continue;
257
+ }
258
+ if (entry.type === "diff") {
259
+ fragments.push(summarizeDiff(entry.path, entry.oldText, entry.newText));
260
+ continue;
261
+ }
262
+ if (entry.type === "terminal") {
263
+ fragments.push(`[terminal] ${entry.terminalId}`);
264
+ }
265
+ }
266
+ const unique = dedupeStrings(
267
+ fragments.map((fragment) => fragment.trim()).filter((fragment) => fragment.length > 0)
268
+ );
269
+ if (unique.length === 0) {
270
+ return void 0;
271
+ }
272
+ return unique.join("\n\n");
273
+ }
274
+ function extractOutputText(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
275
+ if (value == null) {
276
+ return void 0;
277
+ }
278
+ if (typeof value === "string") {
279
+ const trimmed = value.trimEnd();
280
+ return trimmed.length > 0 ? trimmed : void 0;
281
+ }
282
+ if (typeof value === "number" || typeof value === "boolean") {
283
+ return String(value);
284
+ }
285
+ if (depth >= 4) {
286
+ return void 0;
287
+ }
288
+ if (Array.isArray(value)) {
289
+ const parts = value.map((entry) => extractOutputText(entry, depth + 1, seen)).filter((entry) => Boolean(entry));
290
+ if (parts.length === 0) {
291
+ return void 0;
292
+ }
293
+ return dedupeStrings(parts).join("\n");
294
+ }
295
+ const record = asRecord(value);
296
+ if (!record) {
297
+ return void 0;
298
+ }
299
+ if (seen.has(record)) {
300
+ return void 0;
301
+ }
302
+ seen.add(record);
303
+ const preferred = [];
304
+ for (const key of OUTPUT_PRIORITY_KEYS) {
305
+ if (!(key in record)) {
306
+ continue;
307
+ }
308
+ const extracted = extractOutputText(record[key], depth + 1, seen);
309
+ if (extracted) {
310
+ preferred.push(extracted);
311
+ }
312
+ }
313
+ const uniquePreferred = dedupeStrings(preferred);
314
+ if (uniquePreferred.length > 0) {
315
+ return uniquePreferred.join("\n");
316
+ }
317
+ const json = safeJson(record, 2);
318
+ if (!json || json === "{}") {
319
+ return void 0;
320
+ }
321
+ return json;
322
+ }
323
+ function summarizeToolOutput(rawOutput, content) {
324
+ const outputFromRaw = extractOutputText(rawOutput);
325
+ const outputFromContent = summarizeToolContent(content);
326
+ const fragments = dedupeStrings(
327
+ [outputFromRaw, outputFromContent].map((fragment) => fragment?.trim()).filter((fragment) => Boolean(fragment))
328
+ );
329
+ if (fragments.length === 0) {
330
+ return void 0;
331
+ }
332
+ return fragments.join("\n\n");
333
+ }
334
+ function limitOutputBlock(value) {
335
+ const normalized = value.replace(/\r\n/g, "\n").trim();
336
+ if (!normalized) {
337
+ return "";
338
+ }
339
+ const lines = normalized.split("\n");
340
+ const visible = lines.slice(0, MAX_OUTPUT_LINES);
341
+ let result = visible.join("\n");
342
+ if (lines.length > visible.length) {
343
+ const hidden = lines.length - visible.length;
344
+ result += `
345
+ ... (${hidden} more lines)`;
346
+ }
347
+ if (result.length > MAX_OUTPUT_CHARS) {
348
+ result = `${result.slice(0, MAX_OUTPUT_CHARS - 3)}...`;
349
+ }
350
+ return result;
351
+ }
13
352
  var TextOutputFormatter = class {
14
353
  stdout;
354
+ useColor;
355
+ toolStates = /* @__PURE__ */ new Map();
356
+ thoughtBuffer = "";
357
+ wroteAny = false;
358
+ atLineStart = true;
359
+ section = null;
15
360
  constructor(stdout) {
16
361
  this.stdout = stdout;
362
+ this.useColor = Boolean(stdout.isTTY);
17
363
  }
18
364
  onSessionUpdate(notification) {
19
365
  const update = notification.update;
366
+ if (update.sessionUpdate !== "agent_thought_chunk") {
367
+ this.flushThoughtBuffer();
368
+ }
20
369
  switch (update.sessionUpdate) {
21
370
  case "agent_message_chunk": {
22
371
  if (update.content.type === "text") {
23
- this.stdout.write(update.content.text);
372
+ this.writeAssistantChunk(update.content.text);
24
373
  }
25
374
  return;
26
375
  }
27
376
  case "agent_thought_chunk": {
28
377
  if (update.content.type === "text") {
29
- this.stdout.write(`
30
- [thought] ${update.content.text}
31
- `);
378
+ this.thoughtBuffer += update.content.text;
32
379
  }
33
380
  return;
34
381
  }
35
382
  case "tool_call": {
36
- this.stdout.write(`
37
- [tool] ${update.title} (${asStatus(update.status)})
38
- `);
383
+ this.renderToolUpdate(update);
39
384
  return;
40
385
  }
41
386
  case "tool_call_update": {
42
- const title = update.title ?? update.toolCallId;
43
- this.stdout.write(`
44
- [tool] ${title} (${asStatus(update.status)})
45
- `);
387
+ this.renderToolUpdate(update);
46
388
  return;
47
389
  }
48
390
  case "plan": {
49
- this.stdout.write("\n[plan]\n");
391
+ this.beginSection("plan");
392
+ this.writeLine(this.bold("[plan]"));
50
393
  for (const entry of update.entries) {
51
- this.stdout.write(`- (${entry.status}) ${entry.content}
52
- `);
394
+ this.writeLine(` - [${entry.status}] ${entry.content}`);
53
395
  }
54
396
  return;
55
397
  }
@@ -58,11 +400,181 @@ var TextOutputFormatter = class {
58
400
  }
59
401
  }
60
402
  onDone(stopReason) {
61
- this.stdout.write(`
62
- [done] ${stopReason}
63
- `);
403
+ this.flushThoughtBuffer();
404
+ this.beginSection("done");
405
+ this.writeLine(this.dim(`[done] ${stopReason}`));
64
406
  }
65
407
  flush() {
408
+ this.flushThoughtBuffer();
409
+ if (!this.atLineStart) {
410
+ this.write("\n");
411
+ }
412
+ }
413
+ write(chunk) {
414
+ if (!chunk) {
415
+ return;
416
+ }
417
+ this.stdout.write(chunk);
418
+ this.wroteAny = true;
419
+ this.atLineStart = chunk.endsWith("\n");
420
+ }
421
+ writeLine(line) {
422
+ this.write(`${line}
423
+ `);
424
+ }
425
+ beginSection(next) {
426
+ if (!this.atLineStart) {
427
+ this.write("\n");
428
+ }
429
+ if (this.wroteAny) {
430
+ this.write("\n");
431
+ }
432
+ this.section = next;
433
+ }
434
+ writeAssistantChunk(text) {
435
+ if (!text) {
436
+ return;
437
+ }
438
+ this.section = "assistant";
439
+ this.write(text);
440
+ }
441
+ flushThoughtBuffer() {
442
+ const thought = truncate(collapseWhitespace(this.thoughtBuffer), MAX_THOUGHT_CHARS);
443
+ this.thoughtBuffer = "";
444
+ if (!thought) {
445
+ return;
446
+ }
447
+ this.beginSection("thought");
448
+ this.writeLine(this.dim(`[thinking] ${thought}`));
449
+ }
450
+ renderToolUpdate(update) {
451
+ const state = this.getOrCreateToolState(update.toolCallId);
452
+ this.mergeToolState(state, update);
453
+ const status = asStatus(state.status);
454
+ if (isFinalStatus(status)) {
455
+ const signature = this.toolSignature(state);
456
+ if (signature !== state.finalSignature) {
457
+ state.finalSignature = signature;
458
+ this.renderFinalToolState(state, status);
459
+ }
460
+ return;
461
+ }
462
+ if (state.startedPrinted) {
463
+ return;
464
+ }
465
+ state.startedPrinted = true;
466
+ this.renderStartingToolState(state, status);
467
+ }
468
+ getOrCreateToolState(toolCallId) {
469
+ const existing = this.toolStates.get(toolCallId);
470
+ if (existing) {
471
+ return existing;
472
+ }
473
+ const created = {
474
+ id: toolCallId,
475
+ startedPrinted: false
476
+ };
477
+ this.toolStates.set(toolCallId, created);
478
+ return created;
479
+ }
480
+ mergeToolState(state, update) {
481
+ if (typeof update.title === "string" && update.title.trim().length > 0) {
482
+ state.title = update.title;
483
+ }
484
+ if (update.status !== void 0) {
485
+ state.status = update.status;
486
+ }
487
+ if (update.kind !== void 0) {
488
+ state.kind = update.kind;
489
+ }
490
+ if (update.locations !== void 0) {
491
+ state.locations = update.locations;
492
+ }
493
+ if (update.rawInput !== void 0) {
494
+ state.rawInput = update.rawInput;
495
+ }
496
+ if (update.rawOutput !== void 0) {
497
+ state.rawOutput = update.rawOutput;
498
+ }
499
+ if (update.content !== void 0) {
500
+ state.content = update.content;
501
+ }
502
+ }
503
+ toolSignature(state) {
504
+ const signaturePayload = {
505
+ title: state.title,
506
+ status: state.status,
507
+ kind: state.kind,
508
+ input: summarizeToolInput(state.rawInput),
509
+ files: formatLocations(state.locations),
510
+ output: summarizeToolOutput(state.rawOutput, state.content)
511
+ };
512
+ return safeJson(signaturePayload, 0) ?? JSON.stringify(signaturePayload);
513
+ }
514
+ renderStartingToolState(state, status) {
515
+ this.beginSection("tool");
516
+ const title = state.title ?? state.id;
517
+ const label = status === "pending" ? "pending" : "running";
518
+ const statusText = this.colorStatus(label, status);
519
+ this.writeLine(`${this.bold("[tool]")} ${title} (${statusText})`);
520
+ const input = summarizeToolInput(state.rawInput);
521
+ if (input) {
522
+ this.writeLine(` input: ${input}`);
523
+ }
524
+ const files = formatLocations(state.locations);
525
+ if (files) {
526
+ this.writeLine(` files: ${files}`);
527
+ }
528
+ }
529
+ renderFinalToolState(state, status) {
530
+ this.beginSection("tool");
531
+ const title = state.title ?? state.id;
532
+ const statusText = this.colorStatus(toStatusLabel(status), status);
533
+ this.writeLine(`${this.bold("[tool]")} ${title} (${statusText})`);
534
+ if (state.kind) {
535
+ this.writeLine(` kind: ${state.kind}`);
536
+ }
537
+ const input = summarizeToolInput(state.rawInput);
538
+ if (input) {
539
+ this.writeLine(` input: ${input}`);
540
+ }
541
+ const files = formatLocations(state.locations);
542
+ if (files) {
543
+ this.writeLine(` files: ${files}`);
544
+ }
545
+ const output = summarizeToolOutput(state.rawOutput, state.content);
546
+ if (output) {
547
+ this.writeLine(" output:");
548
+ this.writeLine(indentBlock(limitOutputBlock(output), " "));
549
+ }
550
+ }
551
+ formatAnsi(text, code) {
552
+ if (!this.useColor) {
553
+ return text;
554
+ }
555
+ return `\x1B[${code}m${text}\x1B[0m`;
556
+ }
557
+ bold(text) {
558
+ return this.formatAnsi(text, "1");
559
+ }
560
+ dim(text) {
561
+ return this.formatAnsi(text, "2");
562
+ }
563
+ colorStatus(text, status) {
564
+ if (!this.useColor) {
565
+ return text;
566
+ }
567
+ switch (status) {
568
+ case "completed":
569
+ return this.formatAnsi(text, "32");
570
+ case "failed":
571
+ return this.formatAnsi(text, "31");
572
+ case "pending":
573
+ case "in_progress":
574
+ case "unknown":
575
+ default:
576
+ return this.formatAnsi(text, "33");
577
+ }
66
578
  }
67
579
  };
68
580
  var JsonOutputFormatter = class {
@@ -190,7 +702,9 @@ function createOutputFormatter(format, options = {}) {
190
702
  }
191
703
 
192
704
  // src/session.ts
705
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
193
706
  import fs2 from "fs/promises";
707
+ import net from "net";
194
708
  import os from "os";
195
709
  import path2 from "path";
196
710
 
@@ -200,9 +714,7 @@ import {
200
714
  PROTOCOL_VERSION,
201
715
  ndJsonStream
202
716
  } from "@agentclientprotocol/sdk";
203
- import {
204
- spawn
205
- } from "child_process";
717
+ import { spawn } from "child_process";
206
718
  import { randomUUID } from "crypto";
207
719
  import fs from "fs/promises";
208
720
  import path from "path";
@@ -463,9 +975,15 @@ var AcpClient = class {
463
975
  this.log(`spawning agent: ${command} ${args.join(" ")}`);
464
976
  const child = spawn(command, args, {
465
977
  cwd: this.options.cwd,
466
- stdio: ["pipe", "pipe", "inherit"]
978
+ stdio: ["pipe", "pipe", "pipe"]
467
979
  });
468
980
  await waitForSpawn(child);
981
+ child.stderr.on("data", (chunk) => {
982
+ if (!this.options.verbose) {
983
+ return;
984
+ }
985
+ process.stderr.write(chunk);
986
+ });
469
987
  const input = Writable.toWeb(child.stdin);
470
988
  const output = Readable.toWeb(child.stdout);
471
989
  const stream = ndJsonStream(input, output);
@@ -777,8 +1295,12 @@ var AcpClient = class {
777
1295
 
778
1296
  // src/session.ts
779
1297
  var SESSION_BASE_DIR = path2.join(os.homedir(), ".acpx", "sessions");
1298
+ var QUEUE_BASE_DIR = path2.join(os.homedir(), ".acpx", "queues");
780
1299
  var PROCESS_EXIT_GRACE_MS = 1500;
781
1300
  var PROCESS_POLL_MS = 50;
1301
+ var QUEUE_CONNECT_ATTEMPTS = 40;
1302
+ var QUEUE_CONNECT_RETRY_MS = 50;
1303
+ var QUEUE_IDLE_DRAIN_WAIT_MS = 150;
782
1304
  var TimeoutError = class extends Error {
783
1305
  constructor(timeoutMs) {
784
1306
  super(`Timed out after ${timeoutMs}ms`);
@@ -851,8 +1373,9 @@ function parseSessionRecord(raw) {
851
1373
  return null;
852
1374
  }
853
1375
  const record = raw;
1376
+ const name = record.name == null ? void 0 : typeof record.name === "string" && record.name.trim().length > 0 ? record.name.trim() : null;
854
1377
  const pid = record.pid == null ? void 0 : Number.isInteger(record.pid) && record.pid > 0 ? record.pid : null;
855
- if (typeof record.id !== "string" || typeof record.sessionId !== "string" || typeof record.agentCommand !== "string" || typeof record.cwd !== "string" || typeof record.createdAt !== "string" || typeof record.lastUsedAt !== "string" || pid === null) {
1378
+ if (typeof record.id !== "string" || typeof record.sessionId !== "string" || typeof record.agentCommand !== "string" || typeof record.cwd !== "string" || name === null || typeof record.createdAt !== "string" || typeof record.lastUsedAt !== "string" || pid === null) {
856
1379
  return null;
857
1380
  }
858
1381
  return {
@@ -861,6 +1384,7 @@ function parseSessionRecord(raw) {
861
1384
  sessionId: record.sessionId,
862
1385
  agentCommand: record.agentCommand,
863
1386
  cwd: record.cwd,
1387
+ name,
864
1388
  createdAt: record.createdAt,
865
1389
  lastUsedAt: record.lastUsedAt,
866
1390
  pid
@@ -917,6 +1441,13 @@ function toPromptResult(stopReason, sessionId, client) {
917
1441
  function absolutePath(value) {
918
1442
  return path2.resolve(value);
919
1443
  }
1444
+ function normalizeName(value) {
1445
+ if (value == null) {
1446
+ return void 0;
1447
+ }
1448
+ const trimmed = value.trim();
1449
+ return trimmed.length > 0 ? trimmed : void 0;
1450
+ }
920
1451
  function isoNow() {
921
1452
  return (/* @__PURE__ */ new Date()).toISOString();
922
1453
  }
@@ -1017,92 +1548,682 @@ async function isLikelyMatchingProcess(pid, agentCommand) {
1017
1548
  return true;
1018
1549
  }
1019
1550
  }
1020
- async function runOnce(options) {
1021
- const output = options.outputFormatter;
1022
- const client = new AcpClient({
1023
- agentCommand: options.agentCommand,
1024
- cwd: absolutePath(options.cwd),
1025
- permissionMode: options.permissionMode,
1026
- verbose: options.verbose,
1027
- onSessionUpdate: (notification) => output.onSessionUpdate(notification)
1028
- });
1029
- try {
1030
- return await withInterrupt(
1031
- async () => {
1032
- await withTimeout(client.start(), options.timeoutMs);
1033
- const sessionId = await withTimeout(
1034
- client.createSession(absolutePath(options.cwd)),
1035
- options.timeoutMs
1036
- );
1037
- const response = await withTimeout(
1038
- client.prompt(sessionId, options.message),
1039
- options.timeoutMs
1040
- );
1041
- output.onDone(response.stopReason);
1042
- output.flush();
1043
- return toPromptResult(response.stopReason, sessionId, client);
1044
- },
1045
- async () => {
1046
- await client.close();
1047
- }
1048
- );
1049
- } finally {
1050
- await client.close();
1551
+ function asRecord2(value) {
1552
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1553
+ return void 0;
1051
1554
  }
1555
+ return value;
1052
1556
  }
1053
- async function createSession(options) {
1054
- const client = new AcpClient({
1055
- agentCommand: options.agentCommand,
1056
- cwd: absolutePath(options.cwd),
1057
- permissionMode: options.permissionMode,
1058
- verbose: options.verbose
1059
- });
1060
- try {
1061
- return await withInterrupt(
1062
- async () => {
1063
- await withTimeout(client.start(), options.timeoutMs);
1064
- const sessionId = await withTimeout(
1065
- client.createSession(absolutePath(options.cwd)),
1066
- options.timeoutMs
1067
- );
1068
- const now = isoNow();
1069
- const record = {
1070
- id: sessionId,
1071
- sessionId,
1072
- agentCommand: options.agentCommand,
1073
- cwd: absolutePath(options.cwd),
1074
- createdAt: now,
1075
- lastUsedAt: now,
1076
- pid: client.getAgentPid(),
1077
- protocolVersion: client.initializeResult?.protocolVersion,
1078
- agentCapabilities: client.initializeResult?.agentCapabilities
1079
- };
1080
- await writeSessionRecord(record);
1081
- return record;
1082
- },
1083
- async () => {
1084
- await client.close();
1085
- }
1086
- );
1087
- } finally {
1088
- await client.close();
1557
+ function isPermissionMode(value) {
1558
+ return value === "approve-all" || value === "approve-reads" || value === "deny-all";
1559
+ }
1560
+ function parseQueueOwnerRecord(raw) {
1561
+ const record = asRecord2(raw);
1562
+ if (!record) {
1563
+ return null;
1564
+ }
1565
+ if (!Number.isInteger(record.pid) || record.pid <= 0 || typeof record.sessionId !== "string" || typeof record.socketPath !== "string") {
1566
+ return null;
1089
1567
  }
1568
+ return {
1569
+ pid: record.pid,
1570
+ sessionId: record.sessionId,
1571
+ socketPath: record.socketPath
1572
+ };
1090
1573
  }
1091
- async function sendSession(options) {
1092
- const output = options.outputFormatter;
1093
- const record = await resolveSessionRecord(options.sessionId);
1094
- const storedProcessAlive = isProcessAlive(record.pid);
1095
- if (storedProcessAlive && options.verbose) {
1096
- process.stderr.write(
1097
- `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
1098
- `
1099
- );
1574
+ function parseQueueSubmitRequest(raw) {
1575
+ const request = asRecord2(raw);
1576
+ if (!request) {
1577
+ return null;
1100
1578
  }
1101
- const client = new AcpClient({
1102
- agentCommand: record.agentCommand,
1103
- cwd: absolutePath(record.cwd),
1104
- permissionMode: options.permissionMode,
1105
- verbose: options.verbose,
1579
+ if (request.type !== "submit_prompt" || typeof request.requestId !== "string" || typeof request.message !== "string" || !isPermissionMode(request.permissionMode) || typeof request.waitForCompletion !== "boolean") {
1580
+ return null;
1581
+ }
1582
+ const timeoutRaw = request.timeoutMs;
1583
+ const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.round(timeoutRaw) : void 0;
1584
+ return {
1585
+ type: "submit_prompt",
1586
+ requestId: request.requestId,
1587
+ message: request.message,
1588
+ permissionMode: request.permissionMode,
1589
+ timeoutMs,
1590
+ waitForCompletion: request.waitForCompletion
1591
+ };
1592
+ }
1593
+ function parseSessionSendResult(raw) {
1594
+ const result = asRecord2(raw);
1595
+ if (!result) {
1596
+ return null;
1597
+ }
1598
+ if (typeof result.stopReason !== "string" || typeof result.sessionId !== "string" || typeof result.resumed !== "boolean") {
1599
+ return null;
1600
+ }
1601
+ const permissionStats = asRecord2(result.permissionStats);
1602
+ const record = asRecord2(result.record);
1603
+ if (!permissionStats || !record) {
1604
+ return null;
1605
+ }
1606
+ const statsValid = typeof permissionStats.requested === "number" && typeof permissionStats.approved === "number" && typeof permissionStats.denied === "number" && typeof permissionStats.cancelled === "number";
1607
+ if (!statsValid) {
1608
+ return null;
1609
+ }
1610
+ const recordValid = typeof record.id === "string" && typeof record.sessionId === "string" && typeof record.agentCommand === "string" && typeof record.cwd === "string" && typeof record.createdAt === "string" && typeof record.lastUsedAt === "string";
1611
+ if (!recordValid) {
1612
+ return null;
1613
+ }
1614
+ return result;
1615
+ }
1616
+ function parseQueueOwnerMessage(raw) {
1617
+ const message = asRecord2(raw);
1618
+ if (!message || typeof message.type !== "string") {
1619
+ return null;
1620
+ }
1621
+ if (typeof message.requestId !== "string") {
1622
+ return null;
1623
+ }
1624
+ if (message.type === "accepted") {
1625
+ return {
1626
+ type: "accepted",
1627
+ requestId: message.requestId
1628
+ };
1629
+ }
1630
+ if (message.type === "session_update") {
1631
+ const notification = message.notification;
1632
+ if (!notification || typeof notification !== "object") {
1633
+ return null;
1634
+ }
1635
+ return {
1636
+ type: "session_update",
1637
+ requestId: message.requestId,
1638
+ notification
1639
+ };
1640
+ }
1641
+ if (message.type === "done") {
1642
+ if (typeof message.stopReason !== "string") {
1643
+ return null;
1644
+ }
1645
+ return {
1646
+ type: "done",
1647
+ requestId: message.requestId,
1648
+ stopReason: message.stopReason
1649
+ };
1650
+ }
1651
+ if (message.type === "result") {
1652
+ const parsedResult = parseSessionSendResult(message.result);
1653
+ if (!parsedResult) {
1654
+ return null;
1655
+ }
1656
+ return {
1657
+ type: "result",
1658
+ requestId: message.requestId,
1659
+ result: parsedResult
1660
+ };
1661
+ }
1662
+ if (message.type === "error") {
1663
+ if (typeof message.message !== "string") {
1664
+ return null;
1665
+ }
1666
+ return {
1667
+ type: "error",
1668
+ requestId: message.requestId,
1669
+ message: message.message
1670
+ };
1671
+ }
1672
+ return null;
1673
+ }
1674
+ function queueKeyForSession(sessionId) {
1675
+ return createHash("sha256").update(sessionId).digest("hex").slice(0, 24);
1676
+ }
1677
+ function queueLockFilePath(sessionId) {
1678
+ return path2.join(QUEUE_BASE_DIR, `${queueKeyForSession(sessionId)}.lock`);
1679
+ }
1680
+ function queueSocketPath(sessionId) {
1681
+ const key = queueKeyForSession(sessionId);
1682
+ if (process.platform === "win32") {
1683
+ return `\\\\.\\pipe\\acpx-${key}`;
1684
+ }
1685
+ return path2.join(QUEUE_BASE_DIR, `${key}.sock`);
1686
+ }
1687
+ async function ensureQueueDir() {
1688
+ await fs2.mkdir(QUEUE_BASE_DIR, { recursive: true });
1689
+ }
1690
+ async function removeSocketFile(socketPath) {
1691
+ if (process.platform === "win32") {
1692
+ return;
1693
+ }
1694
+ try {
1695
+ await fs2.unlink(socketPath);
1696
+ } catch (error) {
1697
+ if (error.code !== "ENOENT") {
1698
+ throw error;
1699
+ }
1700
+ }
1701
+ }
1702
+ async function readQueueOwnerRecord(sessionId) {
1703
+ const lockPath = queueLockFilePath(sessionId);
1704
+ try {
1705
+ const payload = await fs2.readFile(lockPath, "utf8");
1706
+ const parsed = parseQueueOwnerRecord(JSON.parse(payload));
1707
+ return parsed ?? void 0;
1708
+ } catch {
1709
+ return void 0;
1710
+ }
1711
+ }
1712
+ async function cleanupStaleQueueOwner(sessionId, owner) {
1713
+ const lockPath = queueLockFilePath(sessionId);
1714
+ const socketPath = owner?.socketPath ?? queueSocketPath(sessionId);
1715
+ await removeSocketFile(socketPath).catch(() => {
1716
+ });
1717
+ await fs2.unlink(lockPath).catch((error) => {
1718
+ if (error.code !== "ENOENT") {
1719
+ throw error;
1720
+ }
1721
+ });
1722
+ }
1723
+ async function tryAcquireQueueOwnerLease(sessionId) {
1724
+ await ensureQueueDir();
1725
+ const lockPath = queueLockFilePath(sessionId);
1726
+ const socketPath = queueSocketPath(sessionId);
1727
+ const payload = JSON.stringify(
1728
+ {
1729
+ pid: process.pid,
1730
+ sessionId,
1731
+ socketPath,
1732
+ createdAt: isoNow()
1733
+ },
1734
+ null,
1735
+ 2
1736
+ );
1737
+ try {
1738
+ await fs2.writeFile(lockPath, `${payload}
1739
+ `, {
1740
+ encoding: "utf8",
1741
+ flag: "wx"
1742
+ });
1743
+ await removeSocketFile(socketPath).catch(() => {
1744
+ });
1745
+ return { lockPath, socketPath };
1746
+ } catch (error) {
1747
+ if (error.code !== "EEXIST") {
1748
+ throw error;
1749
+ }
1750
+ const owner = await readQueueOwnerRecord(sessionId);
1751
+ if (!owner || !isProcessAlive(owner.pid)) {
1752
+ await cleanupStaleQueueOwner(sessionId, owner);
1753
+ }
1754
+ return void 0;
1755
+ }
1756
+ }
1757
+ async function releaseQueueOwnerLease(lease) {
1758
+ await removeSocketFile(lease.socketPath).catch(() => {
1759
+ });
1760
+ await fs2.unlink(lease.lockPath).catch((error) => {
1761
+ if (error.code !== "ENOENT") {
1762
+ throw error;
1763
+ }
1764
+ });
1765
+ }
1766
+ function shouldRetryQueueConnect(error) {
1767
+ const code = error.code;
1768
+ return code === "ENOENT" || code === "ECONNREFUSED";
1769
+ }
1770
+ async function waitMs(ms) {
1771
+ await new Promise((resolve) => {
1772
+ setTimeout(resolve, ms);
1773
+ });
1774
+ }
1775
+ async function connectToSocket(socketPath) {
1776
+ return await new Promise((resolve, reject) => {
1777
+ const socket = net.createConnection(socketPath);
1778
+ const onConnect = () => {
1779
+ socket.off("error", onError);
1780
+ resolve(socket);
1781
+ };
1782
+ const onError = (error) => {
1783
+ socket.off("connect", onConnect);
1784
+ reject(error);
1785
+ };
1786
+ socket.once("connect", onConnect);
1787
+ socket.once("error", onError);
1788
+ });
1789
+ }
1790
+ async function connectToQueueOwner(owner) {
1791
+ let lastError;
1792
+ for (let attempt = 0; attempt < QUEUE_CONNECT_ATTEMPTS; attempt += 1) {
1793
+ try {
1794
+ return await connectToSocket(owner.socketPath);
1795
+ } catch (error) {
1796
+ lastError = error;
1797
+ if (!shouldRetryQueueConnect(error)) {
1798
+ throw error;
1799
+ }
1800
+ if (!isProcessAlive(owner.pid)) {
1801
+ return void 0;
1802
+ }
1803
+ await waitMs(QUEUE_CONNECT_RETRY_MS);
1804
+ }
1805
+ }
1806
+ if (lastError && !shouldRetryQueueConnect(lastError)) {
1807
+ throw lastError;
1808
+ }
1809
+ return void 0;
1810
+ }
1811
+ function writeQueueMessage(socket, message) {
1812
+ if (socket.destroyed || !socket.writable) {
1813
+ return;
1814
+ }
1815
+ socket.write(`${JSON.stringify(message)}
1816
+ `);
1817
+ }
1818
+ var QueueTaskOutputFormatter = class {
1819
+ requestId;
1820
+ send;
1821
+ constructor(task) {
1822
+ this.requestId = task.requestId;
1823
+ this.send = task.send;
1824
+ }
1825
+ onSessionUpdate(notification) {
1826
+ this.send({
1827
+ type: "session_update",
1828
+ requestId: this.requestId,
1829
+ notification
1830
+ });
1831
+ }
1832
+ onDone(stopReason) {
1833
+ this.send({
1834
+ type: "done",
1835
+ requestId: this.requestId,
1836
+ stopReason
1837
+ });
1838
+ }
1839
+ flush() {
1840
+ }
1841
+ };
1842
+ var DISCARD_OUTPUT_FORMATTER = {
1843
+ onSessionUpdate() {
1844
+ },
1845
+ onDone() {
1846
+ },
1847
+ flush() {
1848
+ }
1849
+ };
1850
+ var SessionQueueOwner = class _SessionQueueOwner {
1851
+ server;
1852
+ pending = [];
1853
+ waiters = [];
1854
+ closed = false;
1855
+ constructor(server) {
1856
+ this.server = server;
1857
+ }
1858
+ static async start(lease) {
1859
+ const ownerRef = { current: void 0 };
1860
+ const server = net.createServer((socket) => {
1861
+ ownerRef.current?.handleConnection(socket);
1862
+ });
1863
+ ownerRef.current = new _SessionQueueOwner(server);
1864
+ await new Promise((resolve, reject) => {
1865
+ const onListening = () => {
1866
+ server.off("error", onError);
1867
+ resolve();
1868
+ };
1869
+ const onError = (error) => {
1870
+ server.off("listening", onListening);
1871
+ reject(error);
1872
+ };
1873
+ server.once("listening", onListening);
1874
+ server.once("error", onError);
1875
+ server.listen(lease.socketPath);
1876
+ });
1877
+ return ownerRef.current;
1878
+ }
1879
+ async close() {
1880
+ if (this.closed) {
1881
+ return;
1882
+ }
1883
+ this.closed = true;
1884
+ for (const waiter of this.waiters.splice(0)) {
1885
+ waiter(void 0);
1886
+ }
1887
+ for (const task of this.pending.splice(0)) {
1888
+ if (task.waitForCompletion) {
1889
+ task.send({
1890
+ type: "error",
1891
+ requestId: task.requestId,
1892
+ message: "Queue owner shutting down before prompt execution"
1893
+ });
1894
+ }
1895
+ task.close();
1896
+ }
1897
+ await new Promise((resolve) => {
1898
+ this.server.close(() => resolve());
1899
+ });
1900
+ }
1901
+ async nextTask(timeoutMs) {
1902
+ if (this.pending.length > 0) {
1903
+ return this.pending.shift();
1904
+ }
1905
+ if (this.closed) {
1906
+ return void 0;
1907
+ }
1908
+ return await new Promise((resolve) => {
1909
+ const timer = setTimeout(
1910
+ () => {
1911
+ const index = this.waiters.indexOf(waiter);
1912
+ if (index >= 0) {
1913
+ this.waiters.splice(index, 1);
1914
+ }
1915
+ resolve(void 0);
1916
+ },
1917
+ Math.max(0, timeoutMs)
1918
+ );
1919
+ const waiter = (task) => {
1920
+ clearTimeout(timer);
1921
+ resolve(task);
1922
+ };
1923
+ this.waiters.push(waiter);
1924
+ });
1925
+ }
1926
+ enqueue(task) {
1927
+ if (this.closed) {
1928
+ if (task.waitForCompletion) {
1929
+ task.send({
1930
+ type: "error",
1931
+ requestId: task.requestId,
1932
+ message: "Queue owner is shutting down"
1933
+ });
1934
+ }
1935
+ task.close();
1936
+ return;
1937
+ }
1938
+ const waiter = this.waiters.shift();
1939
+ if (waiter) {
1940
+ waiter(task);
1941
+ return;
1942
+ }
1943
+ this.pending.push(task);
1944
+ }
1945
+ handleConnection(socket) {
1946
+ socket.setEncoding("utf8");
1947
+ if (this.closed) {
1948
+ writeQueueMessage(socket, {
1949
+ type: "error",
1950
+ requestId: "unknown",
1951
+ message: "Queue owner is closed"
1952
+ });
1953
+ socket.end();
1954
+ return;
1955
+ }
1956
+ let buffer = "";
1957
+ let handled = false;
1958
+ const fail = (requestId, message) => {
1959
+ writeQueueMessage(socket, {
1960
+ type: "error",
1961
+ requestId,
1962
+ message
1963
+ });
1964
+ socket.end();
1965
+ };
1966
+ const processLine = (line) => {
1967
+ if (handled) {
1968
+ return;
1969
+ }
1970
+ handled = true;
1971
+ let parsed;
1972
+ try {
1973
+ parsed = JSON.parse(line);
1974
+ } catch {
1975
+ fail("unknown", "Invalid queue request payload");
1976
+ return;
1977
+ }
1978
+ const request = parseQueueSubmitRequest(parsed);
1979
+ if (!request) {
1980
+ fail("unknown", "Invalid queue request");
1981
+ return;
1982
+ }
1983
+ const task = {
1984
+ requestId: request.requestId,
1985
+ message: request.message,
1986
+ permissionMode: request.permissionMode,
1987
+ timeoutMs: request.timeoutMs,
1988
+ waitForCompletion: request.waitForCompletion,
1989
+ send: (message) => {
1990
+ writeQueueMessage(socket, message);
1991
+ },
1992
+ close: () => {
1993
+ if (!socket.destroyed) {
1994
+ socket.end();
1995
+ }
1996
+ }
1997
+ };
1998
+ writeQueueMessage(socket, {
1999
+ type: "accepted",
2000
+ requestId: request.requestId
2001
+ });
2002
+ if (!request.waitForCompletion) {
2003
+ task.close();
2004
+ }
2005
+ this.enqueue(task);
2006
+ };
2007
+ socket.on("data", (chunk) => {
2008
+ buffer += chunk;
2009
+ let index = buffer.indexOf("\n");
2010
+ while (index >= 0) {
2011
+ const line = buffer.slice(0, index).trim();
2012
+ buffer = buffer.slice(index + 1);
2013
+ if (line.length > 0) {
2014
+ processLine(line);
2015
+ }
2016
+ index = buffer.indexOf("\n");
2017
+ }
2018
+ });
2019
+ socket.on("error", () => {
2020
+ });
2021
+ }
2022
+ };
2023
+ async function submitToQueueOwner(owner, options) {
2024
+ const socket = await connectToQueueOwner(owner);
2025
+ if (!socket) {
2026
+ return void 0;
2027
+ }
2028
+ socket.setEncoding("utf8");
2029
+ const requestId = randomUUID2();
2030
+ const request = {
2031
+ type: "submit_prompt",
2032
+ requestId,
2033
+ message: options.message,
2034
+ permissionMode: options.permissionMode,
2035
+ timeoutMs: options.timeoutMs,
2036
+ waitForCompletion: options.waitForCompletion
2037
+ };
2038
+ return await new Promise((resolve, reject) => {
2039
+ let settled = false;
2040
+ let acknowledged = false;
2041
+ let buffer = "";
2042
+ let sawDone = false;
2043
+ const finishResolve = (result) => {
2044
+ if (settled) {
2045
+ return;
2046
+ }
2047
+ settled = true;
2048
+ socket.removeAllListeners();
2049
+ if (!socket.destroyed) {
2050
+ socket.end();
2051
+ }
2052
+ resolve(result);
2053
+ };
2054
+ const finishReject = (error) => {
2055
+ if (settled) {
2056
+ return;
2057
+ }
2058
+ settled = true;
2059
+ socket.removeAllListeners();
2060
+ if (!socket.destroyed) {
2061
+ socket.destroy();
2062
+ }
2063
+ reject(error);
2064
+ };
2065
+ const processLine = (line) => {
2066
+ let parsed;
2067
+ try {
2068
+ parsed = JSON.parse(line);
2069
+ } catch {
2070
+ finishReject(new Error("Queue owner sent invalid JSON payload"));
2071
+ return;
2072
+ }
2073
+ const message = parseQueueOwnerMessage(parsed);
2074
+ if (!message || message.requestId !== requestId) {
2075
+ finishReject(new Error("Queue owner sent malformed message"));
2076
+ return;
2077
+ }
2078
+ if (message.type === "accepted") {
2079
+ acknowledged = true;
2080
+ if (!options.waitForCompletion) {
2081
+ const queued = {
2082
+ queued: true,
2083
+ sessionId: options.sessionId,
2084
+ requestId
2085
+ };
2086
+ finishResolve(queued);
2087
+ }
2088
+ return;
2089
+ }
2090
+ if (!acknowledged) {
2091
+ finishReject(new Error("Queue owner did not acknowledge request"));
2092
+ return;
2093
+ }
2094
+ if (message.type === "session_update") {
2095
+ options.outputFormatter.onSessionUpdate(message.notification);
2096
+ return;
2097
+ }
2098
+ if (message.type === "done") {
2099
+ options.outputFormatter.onDone(message.stopReason);
2100
+ sawDone = true;
2101
+ return;
2102
+ }
2103
+ if (message.type === "result") {
2104
+ if (!sawDone) {
2105
+ options.outputFormatter.onDone(message.result.stopReason);
2106
+ }
2107
+ options.outputFormatter.flush();
2108
+ finishResolve(message.result);
2109
+ return;
2110
+ }
2111
+ finishReject(new Error(message.message));
2112
+ };
2113
+ socket.on("data", (chunk) => {
2114
+ buffer += chunk;
2115
+ let index = buffer.indexOf("\n");
2116
+ while (index >= 0) {
2117
+ const line = buffer.slice(0, index).trim();
2118
+ buffer = buffer.slice(index + 1);
2119
+ if (line.length > 0) {
2120
+ processLine(line);
2121
+ }
2122
+ index = buffer.indexOf("\n");
2123
+ }
2124
+ });
2125
+ socket.once("error", (error) => {
2126
+ finishReject(error);
2127
+ });
2128
+ socket.once("close", () => {
2129
+ if (settled) {
2130
+ return;
2131
+ }
2132
+ if (!acknowledged) {
2133
+ finishReject(
2134
+ new Error("Queue owner disconnected before acknowledging request")
2135
+ );
2136
+ return;
2137
+ }
2138
+ if (!options.waitForCompletion) {
2139
+ const queued = {
2140
+ queued: true,
2141
+ sessionId: options.sessionId,
2142
+ requestId
2143
+ };
2144
+ finishResolve(queued);
2145
+ return;
2146
+ }
2147
+ finishReject(new Error("Queue owner disconnected before prompt completion"));
2148
+ });
2149
+ socket.write(`${JSON.stringify(request)}
2150
+ `);
2151
+ });
2152
+ }
2153
+ async function trySubmitToRunningOwner(options) {
2154
+ const owner = await readQueueOwnerRecord(options.sessionId);
2155
+ if (!owner) {
2156
+ return void 0;
2157
+ }
2158
+ if (!isProcessAlive(owner.pid)) {
2159
+ await cleanupStaleQueueOwner(options.sessionId, owner);
2160
+ return void 0;
2161
+ }
2162
+ const submitted = await submitToQueueOwner(owner, options);
2163
+ if (submitted) {
2164
+ if (options.verbose) {
2165
+ process.stderr.write(
2166
+ `[acpx] queued prompt on active owner pid ${owner.pid} for session ${options.sessionId}
2167
+ `
2168
+ );
2169
+ }
2170
+ return submitted;
2171
+ }
2172
+ if (!isProcessAlive(owner.pid)) {
2173
+ await cleanupStaleQueueOwner(options.sessionId, owner);
2174
+ return void 0;
2175
+ }
2176
+ throw new Error("Session queue owner is running but not accepting queue requests");
2177
+ }
2178
+ async function runQueuedTask(sessionRecordId, task, verbose) {
2179
+ const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
2180
+ try {
2181
+ const result = await runSessionPrompt({
2182
+ sessionRecordId,
2183
+ message: task.message,
2184
+ permissionMode: task.permissionMode,
2185
+ outputFormatter,
2186
+ timeoutMs: task.timeoutMs,
2187
+ verbose
2188
+ });
2189
+ if (task.waitForCompletion) {
2190
+ task.send({
2191
+ type: "result",
2192
+ requestId: task.requestId,
2193
+ result
2194
+ });
2195
+ }
2196
+ } catch (error) {
2197
+ const message = formatError(error);
2198
+ if (task.waitForCompletion) {
2199
+ task.send({
2200
+ type: "error",
2201
+ requestId: task.requestId,
2202
+ message
2203
+ });
2204
+ }
2205
+ if (error instanceof InterruptedError) {
2206
+ throw error;
2207
+ }
2208
+ } finally {
2209
+ task.close();
2210
+ }
2211
+ }
2212
+ async function runSessionPrompt(options) {
2213
+ const output = options.outputFormatter;
2214
+ const record = await resolveSessionRecord(options.sessionRecordId);
2215
+ const storedProcessAlive = isProcessAlive(record.pid);
2216
+ if (storedProcessAlive && options.verbose) {
2217
+ process.stderr.write(
2218
+ `[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession
2219
+ `
2220
+ );
2221
+ }
2222
+ const client = new AcpClient({
2223
+ agentCommand: record.agentCommand,
2224
+ cwd: absolutePath(record.cwd),
2225
+ permissionMode: options.permissionMode,
2226
+ verbose: options.verbose,
1106
2227
  onSessionUpdate: (notification) => output.onSessionUpdate(notification)
1107
2228
  });
1108
2229
  try {
@@ -1165,6 +2286,137 @@ async function sendSession(options) {
1165
2286
  await client.close();
1166
2287
  }
1167
2288
  }
2289
+ async function runOnce(options) {
2290
+ const output = options.outputFormatter;
2291
+ const client = new AcpClient({
2292
+ agentCommand: options.agentCommand,
2293
+ cwd: absolutePath(options.cwd),
2294
+ permissionMode: options.permissionMode,
2295
+ verbose: options.verbose,
2296
+ onSessionUpdate: (notification) => output.onSessionUpdate(notification)
2297
+ });
2298
+ try {
2299
+ return await withInterrupt(
2300
+ async () => {
2301
+ await withTimeout(client.start(), options.timeoutMs);
2302
+ const sessionId = await withTimeout(
2303
+ client.createSession(absolutePath(options.cwd)),
2304
+ options.timeoutMs
2305
+ );
2306
+ const response = await withTimeout(
2307
+ client.prompt(sessionId, options.message),
2308
+ options.timeoutMs
2309
+ );
2310
+ output.onDone(response.stopReason);
2311
+ output.flush();
2312
+ return toPromptResult(response.stopReason, sessionId, client);
2313
+ },
2314
+ async () => {
2315
+ await client.close();
2316
+ }
2317
+ );
2318
+ } finally {
2319
+ await client.close();
2320
+ }
2321
+ }
2322
+ async function createSession(options) {
2323
+ const client = new AcpClient({
2324
+ agentCommand: options.agentCommand,
2325
+ cwd: absolutePath(options.cwd),
2326
+ permissionMode: options.permissionMode,
2327
+ verbose: options.verbose
2328
+ });
2329
+ try {
2330
+ return await withInterrupt(
2331
+ async () => {
2332
+ await withTimeout(client.start(), options.timeoutMs);
2333
+ const sessionId = await withTimeout(
2334
+ client.createSession(absolutePath(options.cwd)),
2335
+ options.timeoutMs
2336
+ );
2337
+ const now = isoNow();
2338
+ const record = {
2339
+ id: sessionId,
2340
+ sessionId,
2341
+ agentCommand: options.agentCommand,
2342
+ cwd: absolutePath(options.cwd),
2343
+ name: normalizeName(options.name),
2344
+ createdAt: now,
2345
+ lastUsedAt: now,
2346
+ pid: client.getAgentPid(),
2347
+ protocolVersion: client.initializeResult?.protocolVersion,
2348
+ agentCapabilities: client.initializeResult?.agentCapabilities
2349
+ };
2350
+ await writeSessionRecord(record);
2351
+ return record;
2352
+ },
2353
+ async () => {
2354
+ await client.close();
2355
+ }
2356
+ );
2357
+ } finally {
2358
+ await client.close();
2359
+ }
2360
+ }
2361
+ async function sendSession(options) {
2362
+ const waitForCompletion = options.waitForCompletion !== false;
2363
+ const queuedToOwner = await trySubmitToRunningOwner({
2364
+ sessionId: options.sessionId,
2365
+ message: options.message,
2366
+ permissionMode: options.permissionMode,
2367
+ outputFormatter: options.outputFormatter,
2368
+ timeoutMs: options.timeoutMs,
2369
+ waitForCompletion,
2370
+ verbose: options.verbose
2371
+ });
2372
+ if (queuedToOwner) {
2373
+ return queuedToOwner;
2374
+ }
2375
+ for (; ; ) {
2376
+ const lease = await tryAcquireQueueOwnerLease(options.sessionId);
2377
+ if (!lease) {
2378
+ const retryQueued = await trySubmitToRunningOwner({
2379
+ sessionId: options.sessionId,
2380
+ message: options.message,
2381
+ permissionMode: options.permissionMode,
2382
+ outputFormatter: options.outputFormatter,
2383
+ timeoutMs: options.timeoutMs,
2384
+ waitForCompletion,
2385
+ verbose: options.verbose
2386
+ });
2387
+ if (retryQueued) {
2388
+ return retryQueued;
2389
+ }
2390
+ await waitMs(QUEUE_CONNECT_RETRY_MS);
2391
+ continue;
2392
+ }
2393
+ let owner;
2394
+ try {
2395
+ owner = await SessionQueueOwner.start(lease);
2396
+ const localResult = await runSessionPrompt({
2397
+ sessionRecordId: options.sessionId,
2398
+ message: options.message,
2399
+ permissionMode: options.permissionMode,
2400
+ outputFormatter: options.outputFormatter,
2401
+ timeoutMs: options.timeoutMs,
2402
+ verbose: options.verbose
2403
+ });
2404
+ while (true) {
2405
+ const task = await owner.nextTask(QUEUE_IDLE_DRAIN_WAIT_MS);
2406
+ if (!task) {
2407
+ break;
2408
+ }
2409
+ await runQueuedTask(options.sessionId, task, options.verbose);
2410
+ }
2411
+ return localResult;
2412
+ } finally {
2413
+ if (owner) {
2414
+ await owner.close();
2415
+ }
2416
+ await releaseQueueOwnerLease(lease);
2417
+ }
2418
+ }
2419
+ }
1168
2420
  async function listSessions() {
1169
2421
  await ensureSessionDir();
1170
2422
  const entries = await fs2.readdir(SESSION_BASE_DIR, { withFileTypes: true });
@@ -1186,8 +2438,37 @@ async function listSessions() {
1186
2438
  records.sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
1187
2439
  return records;
1188
2440
  }
2441
+ async function listSessionsForAgent(agentCommand) {
2442
+ const sessions = await listSessions();
2443
+ return sessions.filter((session) => session.agentCommand === agentCommand);
2444
+ }
2445
+ async function findSession(options) {
2446
+ const normalizedCwd = absolutePath(options.cwd);
2447
+ const normalizedName = normalizeName(options.name);
2448
+ const sessions = await listSessionsForAgent(options.agentCommand);
2449
+ return sessions.find((session) => {
2450
+ if (session.cwd !== normalizedCwd) {
2451
+ return false;
2452
+ }
2453
+ if (normalizedName == null) {
2454
+ return session.name == null;
2455
+ }
2456
+ return session.name === normalizedName;
2457
+ });
2458
+ }
2459
+ async function terminateQueueOwnerForSession(sessionId) {
2460
+ const owner = await readQueueOwnerRecord(sessionId);
2461
+ if (!owner) {
2462
+ return;
2463
+ }
2464
+ if (isProcessAlive(owner.pid)) {
2465
+ await terminateProcess(owner.pid);
2466
+ }
2467
+ await cleanupStaleQueueOwner(sessionId, owner);
2468
+ }
1189
2469
  async function closeSession(sessionId) {
1190
2470
  const record = await resolveSessionRecord(sessionId);
2471
+ await terminateQueueOwnerForSession(record.id);
1191
2472
  if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) {
1192
2473
  await terminateProcess(record.pid);
1193
2474
  }
@@ -1208,6 +2489,7 @@ var EXIT_CODES = {
1208
2489
  var OUTPUT_FORMATS = ["text", "json", "quiet"];
1209
2490
 
1210
2491
  // src/cli.ts
2492
+ var TOP_LEVEL_VERBS = /* @__PURE__ */ new Set(["prompt", "exec", "sessions", "help"]);
1211
2493
  function parseOutputFormat(value) {
1212
2494
  if (!OUTPUT_FORMATS.includes(value)) {
1213
2495
  throw new InvalidArgumentError(
@@ -1223,6 +2505,13 @@ function parseTimeoutSeconds(value) {
1223
2505
  }
1224
2506
  return Math.round(parsed * 1e3);
1225
2507
  }
2508
+ function parseSessionName(value) {
2509
+ const trimmed = value.trim();
2510
+ if (trimmed.length === 0) {
2511
+ throw new InvalidArgumentError("Session name must not be empty");
2512
+ }
2513
+ return trimmed;
2514
+ }
1226
2515
  function resolvePermissionMode(flags) {
1227
2516
  const selected2 = [flags.approveAll, flags.approveReads, flags.denyAll].filter(
1228
2517
  Boolean
@@ -1240,20 +2529,6 @@ function resolvePermissionMode(flags) {
1240
2529
  }
1241
2530
  return "approve-reads";
1242
2531
  }
1243
- function addPermissionFlags(command) {
1244
- return command.option("--approve-all", "Auto-approve all permission requests").option(
1245
- "--approve-reads",
1246
- "Auto-approve read/search requests and prompt for writes"
1247
- ).option("--deny-all", "Deny all permission requests");
1248
- }
1249
- function addFormatFlag(command) {
1250
- return command.option(
1251
- "--format <fmt>",
1252
- "Output format: text, json, quiet",
1253
- parseOutputFormat,
1254
- "text"
1255
- );
1256
- }
1257
2532
  async function readPrompt(promptParts) {
1258
2533
  const joined = promptParts.join(" ").trim();
1259
2534
  if (joined.length > 0) {
@@ -1281,19 +2556,58 @@ function applyPermissionExitCode(result) {
1281
2556
  process.exitCode = EXIT_CODES.PERMISSION_DENIED;
1282
2557
  }
1283
2558
  }
1284
- function printSessionRecordByFormat(record, format) {
1285
- if (format === "json") {
1286
- process.stdout.write(
1287
- `${JSON.stringify({
1288
- type: "session",
1289
- ...record
1290
- })}
1291
- `
2559
+ function addGlobalFlags(command) {
2560
+ return command.option("--agent <command>", "Raw ACP agent command (escape hatch)").option("--cwd <dir>", "Working directory", process.cwd()).option("--approve-all", "Auto-approve all permission requests").option(
2561
+ "--approve-reads",
2562
+ "Auto-approve read/search requests and prompt for writes"
2563
+ ).option("--deny-all", "Deny all permission requests").option(
2564
+ "--format <fmt>",
2565
+ "Output format: text, json, quiet",
2566
+ parseOutputFormat,
2567
+ "text"
2568
+ ).option(
2569
+ "--timeout <seconds>",
2570
+ "Maximum time to wait for agent response",
2571
+ parseTimeoutSeconds
2572
+ ).option("--verbose", "Enable verbose debug logs");
2573
+ }
2574
+ function addSessionOption(command) {
2575
+ return command.option(
2576
+ "-s, --session <name>",
2577
+ "Use named session instead of cwd default",
2578
+ parseSessionName
2579
+ ).option(
2580
+ "--no-wait",
2581
+ "Queue prompt and return immediately when another prompt is already running"
2582
+ );
2583
+ }
2584
+ function resolveGlobalFlags(command) {
2585
+ const opts = command.optsWithGlobals();
2586
+ return {
2587
+ agent: opts.agent,
2588
+ cwd: opts.cwd ?? process.cwd(),
2589
+ timeout: opts.timeout,
2590
+ verbose: opts.verbose,
2591
+ format: opts.format ?? "text",
2592
+ approveAll: opts.approveAll,
2593
+ approveReads: opts.approveReads,
2594
+ denyAll: opts.denyAll
2595
+ };
2596
+ }
2597
+ function resolveAgentInvocation(explicitAgentName, globalFlags) {
2598
+ const override = globalFlags.agent?.trim();
2599
+ if (override && explicitAgentName) {
2600
+ throw new InvalidArgumentError(
2601
+ "Do not combine positional agent with --agent override"
1292
2602
  );
1293
- return;
1294
2603
  }
1295
- process.stdout.write(`${record.id}
1296
- `);
2604
+ const agentName = explicitAgentName ?? DEFAULT_AGENT_NAME;
2605
+ const agentCommand = override && override.length > 0 ? override : resolveAgentCommand(agentName);
2606
+ return {
2607
+ agentName,
2608
+ agentCommand,
2609
+ cwd: path3.resolve(globalFlags.cwd)
2610
+ };
1297
2611
  }
1298
2612
  function printSessionsByFormat(sessions, format) {
1299
2613
  if (format === "json") {
@@ -1314,115 +2628,244 @@ function printSessionsByFormat(sessions, format) {
1314
2628
  }
1315
2629
  for (const session of sessions) {
1316
2630
  process.stdout.write(
1317
- `${session.id} ${session.cwd} ${session.lastUsedAt}
2631
+ `${session.id} ${session.name ?? "-"} ${session.cwd} ${session.lastUsedAt}
1318
2632
  `
1319
2633
  );
1320
2634
  }
1321
2635
  }
1322
- async function main() {
1323
- const program = new Command();
1324
- program.name("acpx").description("Headless CLI client for the Agent Client Protocol").showHelpAfterError();
1325
- program.command("run").description("Run a one-shot prompt").argument("[prompt...]", "Prompt text").requiredOption("--agent <command>", "ACP adapter command").option("--cwd <dir>", "Working directory", process.cwd()).option(
1326
- "--timeout <seconds>",
1327
- "Maximum time to wait for agent response",
1328
- parseTimeoutSeconds
1329
- ).option("--verbose", "Enable verbose debug logs").allowUnknownOption(false);
1330
- const runCommand = program.commands.find((cmd) => cmd.name() === "run");
1331
- if (!runCommand) {
1332
- throw new Error("Failed to build run command");
1333
- }
1334
- addPermissionFlags(runCommand);
1335
- addFormatFlag(runCommand);
1336
- runCommand.action(async (promptParts, flags) => {
1337
- const prompt = await readPrompt(promptParts);
1338
- const permissionMode = resolvePermissionMode(flags);
1339
- const formatter = createOutputFormatter(flags.format);
1340
- const result = await runOnce({
1341
- agentCommand: flags.agent,
1342
- cwd: flags.cwd,
1343
- message: prompt,
1344
- permissionMode,
1345
- outputFormatter: formatter,
1346
- timeoutMs: flags.timeout,
1347
- verbose: flags.verbose
1348
- });
1349
- applyPermissionExitCode(result);
2636
+ function printClosedSessionByFormat(record, format) {
2637
+ if (format === "json") {
2638
+ process.stdout.write(
2639
+ `${JSON.stringify({
2640
+ type: "session_closed",
2641
+ id: record.id,
2642
+ sessionId: record.sessionId,
2643
+ name: record.name
2644
+ })}
2645
+ `
2646
+ );
2647
+ return;
2648
+ }
2649
+ if (format === "quiet") {
2650
+ return;
2651
+ }
2652
+ process.stdout.write(`${record.id}
2653
+ `);
2654
+ }
2655
+ function printQueuedPromptByFormat(result, format) {
2656
+ if (format === "json") {
2657
+ process.stdout.write(
2658
+ `${JSON.stringify({
2659
+ type: "queued",
2660
+ sessionId: result.sessionId,
2661
+ requestId: result.requestId
2662
+ })}
2663
+ `
2664
+ );
2665
+ return;
2666
+ }
2667
+ if (format === "quiet") {
2668
+ return;
2669
+ }
2670
+ process.stdout.write(`[queued] ${result.requestId}
2671
+ `);
2672
+ }
2673
+ async function handlePrompt(explicitAgentName, promptParts, flags, command) {
2674
+ const globalFlags = resolveGlobalFlags(command);
2675
+ const permissionMode = resolvePermissionMode(globalFlags);
2676
+ const prompt = await readPrompt(promptParts);
2677
+ const outputFormatter = createOutputFormatter(globalFlags.format);
2678
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags);
2679
+ let record = await findSession({
2680
+ agentCommand: agent.agentCommand,
2681
+ cwd: agent.cwd,
2682
+ name: flags.session
1350
2683
  });
1351
- const session = program.command("session").description("Session management");
1352
- const sessionCreate = session.command("create").description("Create a persistent session").requiredOption("--agent <command>", "ACP adapter command").option("--cwd <dir>", "Working directory", process.cwd()).option(
1353
- "--timeout <seconds>",
1354
- "Maximum time to wait for agent response",
1355
- parseTimeoutSeconds
1356
- ).option("--verbose", "Enable verbose debug logs");
1357
- addPermissionFlags(sessionCreate);
1358
- addFormatFlag(sessionCreate);
1359
- sessionCreate.action(async (flags) => {
1360
- const permissionMode = resolvePermissionMode(flags);
1361
- const record = await createSession({
1362
- agentCommand: flags.agent,
1363
- cwd: flags.cwd,
2684
+ if (!record) {
2685
+ record = await createSession({
2686
+ agentCommand: agent.agentCommand,
2687
+ cwd: agent.cwd,
2688
+ name: flags.session,
1364
2689
  permissionMode,
1365
- timeoutMs: flags.timeout,
1366
- verbose: flags.verbose
2690
+ timeoutMs: globalFlags.timeout,
2691
+ verbose: globalFlags.verbose
1367
2692
  });
1368
- printSessionRecordByFormat(record, flags.format);
1369
- });
1370
- const sessionSend = session.command("send").description("Send a prompt to an existing session").argument("<sessionId>", "Session ID").argument("[prompt...]", "Prompt text").option(
1371
- "--timeout <seconds>",
1372
- "Maximum time to wait for agent response",
1373
- parseTimeoutSeconds
1374
- ).option("--verbose", "Enable verbose debug logs");
1375
- addPermissionFlags(sessionSend);
1376
- addFormatFlag(sessionSend);
1377
- sessionSend.action(
1378
- async (sessionId, promptParts, flags) => {
1379
- const prompt = await readPrompt(promptParts);
1380
- const permissionMode = resolvePermissionMode(flags);
1381
- const formatter = createOutputFormatter(flags.format);
1382
- const result = await sendSession({
1383
- sessionId,
1384
- message: prompt,
1385
- permissionMode,
1386
- outputFormatter: formatter,
1387
- timeoutMs: flags.timeout,
1388
- verbose: flags.verbose
1389
- });
1390
- applyPermissionExitCode(result);
1391
- if (flags.verbose && result.loadError) {
1392
- process.stderr.write(
1393
- `[acpx] loadSession failed, started fresh session: ${result.loadError}
1394
- `
1395
- );
1396
- }
2693
+ if (globalFlags.verbose) {
2694
+ const scope = flags.session ? `named session "${flags.session}"` : "cwd session";
2695
+ process.stderr.write(`[acpx] created ${scope}: ${record.id}
2696
+ `);
1397
2697
  }
1398
- );
1399
- const sessionList = session.command("list").description("List saved sessions");
1400
- addFormatFlag(sessionList);
1401
- sessionList.action(async (flags) => {
1402
- const sessions = await listSessions();
1403
- printSessionsByFormat(sessions, flags.format);
2698
+ }
2699
+ const result = await sendSession({
2700
+ sessionId: record.id,
2701
+ message: prompt,
2702
+ permissionMode,
2703
+ outputFormatter,
2704
+ timeoutMs: globalFlags.timeout,
2705
+ verbose: globalFlags.verbose,
2706
+ waitForCompletion: flags.wait !== false
1404
2707
  });
1405
- const sessionClose = session.command("close").description("Close and remove a saved session").argument("<sessionId>", "Session ID");
1406
- addFormatFlag(sessionClose);
1407
- sessionClose.action(async (sessionId, flags) => {
1408
- const record = await closeSession(sessionId);
1409
- if (flags.format === "json") {
1410
- process.stdout.write(
1411
- `${JSON.stringify({
1412
- type: "session_closed",
1413
- id: record.id,
1414
- sessionId: record.sessionId
1415
- })}
2708
+ if ("queued" in result) {
2709
+ printQueuedPromptByFormat(result, globalFlags.format);
2710
+ return;
2711
+ }
2712
+ applyPermissionExitCode(result);
2713
+ if (globalFlags.verbose && result.loadError) {
2714
+ process.stderr.write(
2715
+ `[acpx] loadSession failed, started fresh session: ${result.loadError}
1416
2716
  `
2717
+ );
2718
+ }
2719
+ }
2720
+ async function handleExec(explicitAgentName, promptParts, command) {
2721
+ const globalFlags = resolveGlobalFlags(command);
2722
+ const permissionMode = resolvePermissionMode(globalFlags);
2723
+ const prompt = await readPrompt(promptParts);
2724
+ const outputFormatter = createOutputFormatter(globalFlags.format);
2725
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags);
2726
+ const result = await runOnce({
2727
+ agentCommand: agent.agentCommand,
2728
+ cwd: agent.cwd,
2729
+ message: prompt,
2730
+ permissionMode,
2731
+ outputFormatter,
2732
+ timeoutMs: globalFlags.timeout,
2733
+ verbose: globalFlags.verbose
2734
+ });
2735
+ applyPermissionExitCode(result);
2736
+ }
2737
+ async function handleSessionsList(explicitAgentName, command) {
2738
+ const globalFlags = resolveGlobalFlags(command);
2739
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags);
2740
+ const sessions = await listSessionsForAgent(agent.agentCommand);
2741
+ printSessionsByFormat(sessions, globalFlags.format);
2742
+ }
2743
+ async function handleSessionsClose(explicitAgentName, sessionName, command) {
2744
+ const globalFlags = resolveGlobalFlags(command);
2745
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags);
2746
+ const record = await findSession({
2747
+ agentCommand: agent.agentCommand,
2748
+ cwd: agent.cwd,
2749
+ name: sessionName
2750
+ });
2751
+ if (!record) {
2752
+ if (sessionName) {
2753
+ throw new Error(
2754
+ `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}`
1417
2755
  );
1418
- return;
1419
2756
  }
1420
- if (flags.format === "quiet") {
2757
+ throw new Error(`No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
2758
+ }
2759
+ const closed = await closeSession(record.id);
2760
+ printClosedSessionByFormat(closed, globalFlags.format);
2761
+ }
2762
+ function registerSessionsCommand(parent, explicitAgentName) {
2763
+ const sessionsCommand = parent.command("sessions").description("List or close sessions for this agent");
2764
+ sessionsCommand.action(async function() {
2765
+ await handleSessionsList(explicitAgentName, this);
2766
+ });
2767
+ sessionsCommand.command("list").description("List sessions").action(async function() {
2768
+ await handleSessionsList(explicitAgentName, this);
2769
+ });
2770
+ sessionsCommand.command("close").description("Close session for current cwd").argument("[name]", "Session name", parseSessionName).action(async function(name) {
2771
+ await handleSessionsClose(explicitAgentName, name, this);
2772
+ });
2773
+ }
2774
+ function registerAgentCommand(program, agentName) {
2775
+ const agentCommand = program.command(agentName).description(`Use ${agentName} agent`).argument("[prompt...]", "Prompt text").showHelpAfterError();
2776
+ addSessionOption(agentCommand);
2777
+ agentCommand.action(async function(promptParts, flags) {
2778
+ await handlePrompt(agentName, promptParts, flags, this);
2779
+ });
2780
+ const promptCommand = agentCommand.command("prompt").description("Prompt using persistent session").argument("[prompt...]", "Prompt text").showHelpAfterError();
2781
+ addSessionOption(promptCommand);
2782
+ promptCommand.action(async function(promptParts, flags) {
2783
+ await handlePrompt(agentName, promptParts, flags, this);
2784
+ });
2785
+ agentCommand.command("exec").description("One-shot prompt without saved session").argument("[prompt...]", "Prompt text").showHelpAfterError().action(async function(promptParts) {
2786
+ await handleExec(agentName, promptParts, this);
2787
+ });
2788
+ registerSessionsCommand(agentCommand, agentName);
2789
+ }
2790
+ function registerDefaultCommands(program) {
2791
+ const promptCommand = program.command("prompt").description(`Prompt using ${DEFAULT_AGENT_NAME} by default`).argument("[prompt...]", "Prompt text").showHelpAfterError();
2792
+ addSessionOption(promptCommand);
2793
+ promptCommand.action(async function(promptParts, flags) {
2794
+ await handlePrompt(void 0, promptParts, flags, this);
2795
+ });
2796
+ program.command("exec").description(`One-shot prompt using ${DEFAULT_AGENT_NAME} by default`).argument("[prompt...]", "Prompt text").showHelpAfterError().action(async function(promptParts) {
2797
+ await handleExec(void 0, promptParts, this);
2798
+ });
2799
+ registerSessionsCommand(program, void 0);
2800
+ }
2801
+ function detectAgentToken(argv) {
2802
+ let hasAgentOverride = false;
2803
+ for (let index = 0; index < argv.length; index += 1) {
2804
+ const token = argv[index];
2805
+ if (token === "--") {
2806
+ break;
2807
+ }
2808
+ if (!token.startsWith("-") || token === "-") {
2809
+ return { token, hasAgentOverride };
2810
+ }
2811
+ if (token === "--agent") {
2812
+ hasAgentOverride = true;
2813
+ index += 1;
2814
+ continue;
2815
+ }
2816
+ if (token.startsWith("--agent=")) {
2817
+ hasAgentOverride = true;
2818
+ continue;
2819
+ }
2820
+ if (token === "--cwd" || token === "--format" || token === "--timeout") {
2821
+ index += 1;
2822
+ continue;
2823
+ }
2824
+ if (token.startsWith("--cwd=") || token.startsWith("--format=") || token.startsWith("--timeout=")) {
2825
+ continue;
2826
+ }
2827
+ if (token === "--approve-all" || token === "--approve-reads" || token === "--deny-all" || token === "--verbose") {
2828
+ continue;
2829
+ }
2830
+ return { hasAgentOverride };
2831
+ }
2832
+ return { hasAgentOverride };
2833
+ }
2834
+ async function main() {
2835
+ const program = new Command();
2836
+ program.name("acpx").description("Headless CLI client for the Agent Client Protocol").showHelpAfterError();
2837
+ addGlobalFlags(program);
2838
+ const builtInAgents = listBuiltInAgents();
2839
+ for (const agentName of builtInAgents) {
2840
+ registerAgentCommand(program, agentName);
2841
+ }
2842
+ registerDefaultCommands(program);
2843
+ const scan = detectAgentToken(process.argv.slice(2));
2844
+ if (!scan.hasAgentOverride && scan.token && !TOP_LEVEL_VERBS.has(scan.token) && !builtInAgents.includes(scan.token)) {
2845
+ registerAgentCommand(program, scan.token);
2846
+ }
2847
+ program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
2848
+ if (promptParts.length === 0 && process.stdin.isTTY) {
2849
+ this.outputHelp();
1421
2850
  return;
1422
2851
  }
1423
- process.stdout.write(`${record.id}
1424
- `);
2852
+ await handlePrompt(void 0, promptParts, {}, this);
1425
2853
  });
2854
+ program.addHelpText(
2855
+ "after",
2856
+ `
2857
+ Examples:
2858
+ acpx codex "fix the tests"
2859
+ acpx codex prompt "fix the tests"
2860
+ acpx codex --no-wait "queue follow-up task"
2861
+ acpx codex exec "what does this repo do"
2862
+ acpx codex -s backend "fix the API"
2863
+ acpx codex sessions
2864
+ acpx codex sessions close backend
2865
+ acpx claude "refactor auth"
2866
+ acpx gemini "add logging"
2867
+ acpx --agent ./my-custom-server "do something"`
2868
+ );
1426
2869
  program.exitOverride((error) => {
1427
2870
  throw error;
1428
2871
  });