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.
- package/LICENSE +18 -12
- package/README.md +123 -61
- package/dist/cli.js +1665 -222
- 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.
|
|
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.
|
|
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.
|
|
37
|
-
[tool] ${update.title} (${asStatus(update.status)})
|
|
38
|
-
`);
|
|
383
|
+
this.renderToolUpdate(update);
|
|
39
384
|
return;
|
|
40
385
|
}
|
|
41
386
|
case "tool_call_update": {
|
|
42
|
-
|
|
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.
|
|
391
|
+
this.beginSection("plan");
|
|
392
|
+
this.writeLine(this.bold("[plan]"));
|
|
50
393
|
for (const entry of update.entries) {
|
|
51
|
-
this.
|
|
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.
|
|
62
|
-
|
|
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", "
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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:
|
|
1366
|
-
verbose:
|
|
2690
|
+
timeoutMs: globalFlags.timeout,
|
|
2691
|
+
verbose: globalFlags.verbose
|
|
1367
2692
|
});
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|