acpx 0.9.0 → 0.11.0
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/README.md +23 -19
- package/dist/{cli-Bf3yjqzE.js → cli-CC2w0U-A.js} +4 -4
- package/dist/{cli-Bf3yjqzE.js.map → cli-CC2w0U-A.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +685 -67
- package/dist/cli.js.map +1 -1
- package/dist/{client-BssohYqM.d.ts → client-j3sLnpcM.d.ts} +27 -4
- package/dist/client-j3sLnpcM.d.ts.map +1 -0
- package/dist/{flags-C-rwARqg.js → flags-BKjjl3tF.js} +4 -4
- package/dist/flags-BKjjl3tF.js.map +1 -0
- package/dist/{flows-WLs26_5Y.js → flows-BabqiU0u.js} +5 -4
- package/dist/flows-BabqiU0u.js.map +1 -0
- package/dist/flows.d.ts +1 -1
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-D5d-K9s1.js → live-checkpoint-BZrk9Mjz.js} +894 -384
- package/dist/live-checkpoint-BZrk9Mjz.js.map +1 -0
- package/dist/{output-DPg20dvn.js → output-D_BGt1YI.js} +180 -98
- package/dist/output-D_BGt1YI.js.map +1 -0
- package/dist/runtime.d.ts +71 -5
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +188 -32
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-CFudjdkU.d.ts → session-options-Co1oGEK8.d.ts} +22 -2
- package/dist/{session-options-CFudjdkU.d.ts.map → session-options-Co1oGEK8.d.ts.map} +1 -1
- package/package.json +23 -17
- package/skills/acpx/SKILL.md +66 -5
- package/dist/client-BssohYqM.d.ts.map +0 -1
- package/dist/flags-C-rwARqg.js.map +0 -1
- package/dist/flows-WLs26_5Y.js.map +0 -1
- package/dist/live-checkpoint-D5d-K9s1.js.map +0 -1
- package/dist/output-DPg20dvn.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runSessionQueueOwner, c as buildQueueOwnerArgOverride,
|
|
3
|
-
import {
|
|
4
|
-
import { _ as
|
|
5
|
-
import {
|
|
2
|
+
import { a as runSessionQueueOwner, c as buildQueueOwnerArgOverride, g as __exportAll, h as isProcessAlive, l as flushPerfMetricsCapture, n as getTextErrorRemediationHints, o as runOnce, p as probeQueueOwnerHealth, t as createOutputFormatter, u as installPerfMetricsCapture } from "./output-D_BGt1YI.js";
|
|
3
|
+
import { B as listSessions, Bt as OUTPUT_FORMATS, Et as normalizeAgentName$1, G as writeSessionRecord, Gt as AgentSpawnError, H as normalizeName, I as findGitRepositoryRoot, It as EXIT_CODES, L as findSession, M as getAcpxVersion, Ot as exitCodeForOutputErrorCode, R as findSessionByDirectoryWalk, Tt as listBuiltInAgents, Wt as AcpxOperationalError, _t as parsePromptSource, at as defaultSessionEventLog, bt as InterruptedError, ct as sessionEventLockPath, dt as isAcpJsonRpcMessage, gt as mergePromptSourceWithText, jt as normalizeOutputError, lt as sessionEventSegmentPath, mt as PromptInputValidationError, nt as serializeSessionRecordForDisk, rt as normalizeRuntimeSessionId, st as sessionEventActivePath, tt as parseSessionRecord, wt as DEFAULT_AGENT_NAME, xt as TimeoutError, yt as textPrompt } from "./live-checkpoint-BZrk9Mjz.js";
|
|
4
|
+
import { _ as resolveGlobalFlags, b as resolveSessionNameFromFlags, c as parseHistoryLimit, d as parseOutputFormat$1, f as parsePruneBeforeDate, g as resolveAgentInvocation, h as parseTtlSeconds, i as addSessionOption, l as parseMaxTurns, m as parseTimeoutSeconds, n as addPromptInputOption, o as parseAllowedTools, p as parseSessionName, r as addSessionNameOption, s as parseDaysOlderThan, t as addGlobalFlags, u as parseNonEmptyValue, v as resolveOutputPolicy, x as loadPermissionPolicySpec, y as resolvePermissionMode } from "./flags-BKjjl3tF.js";
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
8
|
+
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
9
9
|
import fs$1 from "node:fs/promises";
|
|
10
10
|
import os from "node:os";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { ZodError, z } from "zod";
|
|
13
|
+
import { performance } from "node:perf_hooks";
|
|
11
14
|
//#region src/cli-public.ts
|
|
12
15
|
function configurePublicCli(options) {
|
|
13
16
|
const builtInAgents = options.listBuiltInAgents(options.config.agents);
|
|
@@ -53,6 +56,319 @@ function isLegacyZedCodexAcpInvocation(agentCommand) {
|
|
|
53
56
|
return /@zed-industries\/codex-acp\b/u.test(agentCommand);
|
|
54
57
|
}
|
|
55
58
|
//#endregion
|
|
59
|
+
//#region src/session/export.ts
|
|
60
|
+
var SessionExportError = class extends AcpxOperationalError {
|
|
61
|
+
code;
|
|
62
|
+
exitCode = 2;
|
|
63
|
+
constructor(message, code) {
|
|
64
|
+
super(message, {
|
|
65
|
+
outputCode: "USAGE",
|
|
66
|
+
detailCode: code,
|
|
67
|
+
origin: "cli"
|
|
68
|
+
});
|
|
69
|
+
this.code = code;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function sessionLookupError(message, code) {
|
|
73
|
+
return new SessionExportError(message, code);
|
|
74
|
+
}
|
|
75
|
+
async function loadSessionRecord(sessionLookup) {
|
|
76
|
+
const cwd = path.resolve(sessionLookup.cwd ?? process.cwd());
|
|
77
|
+
const name = normalizeName(sessionLookup.name);
|
|
78
|
+
if (sessionLookup.agentCommand) {
|
|
79
|
+
const active = await findSession({
|
|
80
|
+
agentCommand: sessionLookup.agentCommand,
|
|
81
|
+
cwd,
|
|
82
|
+
name
|
|
83
|
+
});
|
|
84
|
+
if (active) return active;
|
|
85
|
+
return (await listSessions()).find((session) => {
|
|
86
|
+
if (session.agentCommand !== sessionLookup.agentCommand || session.cwd !== cwd) return false;
|
|
87
|
+
if (name == null) return session.name == null;
|
|
88
|
+
return session.name === name;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const matches = (await listSessions()).filter((session) => {
|
|
92
|
+
if (session.cwd !== cwd) return false;
|
|
93
|
+
if (name == null) return session.name == null;
|
|
94
|
+
return session.name === name;
|
|
95
|
+
});
|
|
96
|
+
if (matches.length > 1) throw sessionLookupError("multiple sessions match export lookup", "ambiguous-session");
|
|
97
|
+
return matches[0];
|
|
98
|
+
}
|
|
99
|
+
function parseEventLockPayload(raw) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
103
|
+
const record = parsed;
|
|
104
|
+
return { pid: typeof record.pid === "number" ? record.pid : void 0 };
|
|
105
|
+
} catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function hasLiveEventLock(recordId) {
|
|
110
|
+
try {
|
|
111
|
+
return isProcessAlive(parseEventLockPayload(await fs$1.readFile(sessionEventLockPath(recordId), "utf8")).pid);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error.code === "ENOENT") return false;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function isSessionActive(record) {
|
|
118
|
+
if (record.closed) return false;
|
|
119
|
+
return isProcessAlive(record.pid) || await hasLiveEventLock(record.acpxRecordId);
|
|
120
|
+
}
|
|
121
|
+
async function readHistoryFile(filePath) {
|
|
122
|
+
const payload = await fs$1.readFile(filePath, "utf8").catch((error) => {
|
|
123
|
+
if (error.code === "ENOENT") return "";
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
126
|
+
const history = [];
|
|
127
|
+
for (const line of payload.split("\n").filter((entry) => entry.trim().length > 0)) try {
|
|
128
|
+
const parsed = JSON.parse(line);
|
|
129
|
+
if (isAcpJsonRpcMessage(parsed)) history.push(parsed);
|
|
130
|
+
} catch {}
|
|
131
|
+
return history;
|
|
132
|
+
}
|
|
133
|
+
async function readSessionHistory(record) {
|
|
134
|
+
const history = [];
|
|
135
|
+
const maxSegments = Number.isInteger(record.eventLog.max_segments) ? record.eventLog.max_segments : 0;
|
|
136
|
+
for (let segment = maxSegments; segment >= 1; segment -= 1) history.push(...await readHistoryFile(sessionEventSegmentPath(record.acpxRecordId, segment)));
|
|
137
|
+
history.push(...await readHistoryFile(sessionEventActivePath(record.acpxRecordId)));
|
|
138
|
+
return history;
|
|
139
|
+
}
|
|
140
|
+
function cwdRelativeToHome(cwd, home) {
|
|
141
|
+
const relative = path.relative(home, cwd);
|
|
142
|
+
if (relative.length === 0) return ".";
|
|
143
|
+
if (relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative)) return relative;
|
|
144
|
+
return cwd;
|
|
145
|
+
}
|
|
146
|
+
function serializeSessionRecordForArchive(record, cwdRelative) {
|
|
147
|
+
const state = serializeSessionRecordForDisk(record);
|
|
148
|
+
state.cwd = cwdRelative;
|
|
149
|
+
if (state.event_log && typeof state.event_log === "object" && !Array.isArray(state.event_log)) state.event_log = {
|
|
150
|
+
...state.event_log,
|
|
151
|
+
active_path: ".stream.ndjson"
|
|
152
|
+
};
|
|
153
|
+
return state;
|
|
154
|
+
}
|
|
155
|
+
async function exportSession(sessionLookup, outputPath) {
|
|
156
|
+
const record = await loadSessionRecord(sessionLookup);
|
|
157
|
+
if (!record) throw sessionLookupError("session not found", "not-found");
|
|
158
|
+
if (await isSessionActive(record)) throw sessionLookupError("session is currently locked by a running queue owner; close it first with `acpx sessions close`", "session-locked");
|
|
159
|
+
const home = os.homedir();
|
|
160
|
+
const cwdRelative = cwdRelativeToHome(record.cwd, home);
|
|
161
|
+
const exported = {
|
|
162
|
+
format_version: 1,
|
|
163
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
164
|
+
exported_by: "acpx",
|
|
165
|
+
session: {
|
|
166
|
+
record_id: record.acpxRecordId,
|
|
167
|
+
name: record.name ?? null,
|
|
168
|
+
agent: record.agentCommand,
|
|
169
|
+
agent_name: normalizeAgentName(sessionLookup.agentName),
|
|
170
|
+
cwd_relative: cwdRelative,
|
|
171
|
+
cwd_original: cwdRelative,
|
|
172
|
+
created_at: record.createdAt,
|
|
173
|
+
updated_at: record.lastUsedAt,
|
|
174
|
+
state: serializeSessionRecordForArchive(record, cwdRelative)
|
|
175
|
+
},
|
|
176
|
+
history: await readSessionHistory(record)
|
|
177
|
+
};
|
|
178
|
+
await fs$1.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
|
|
179
|
+
await fs$1.writeFile(outputPath, `${JSON.stringify(exported, null, 2)}\n`, "utf8");
|
|
180
|
+
}
|
|
181
|
+
function normalizeAgentName(agentName) {
|
|
182
|
+
const normalized = agentName?.trim().toLowerCase();
|
|
183
|
+
if (!normalized || normalized.length === 0) return;
|
|
184
|
+
return normalized === "factory-droid" || normalized === "factorydroid" ? "droid" : normalized;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/session/import.ts
|
|
188
|
+
const SUPPORTED_FORMAT_VERSION = 1;
|
|
189
|
+
const exportedSessionSchema = z.object({
|
|
190
|
+
format_version: z.literal(SUPPORTED_FORMAT_VERSION),
|
|
191
|
+
exported_at: z.string(),
|
|
192
|
+
exported_by: z.string(),
|
|
193
|
+
session: z.object({
|
|
194
|
+
record_id: z.string(),
|
|
195
|
+
name: z.string().nullable(),
|
|
196
|
+
agent: z.string(),
|
|
197
|
+
agent_name: z.string().optional(),
|
|
198
|
+
cwd_relative: z.string(),
|
|
199
|
+
cwd_original: z.string().optional(),
|
|
200
|
+
cwd_absolute_original: z.string().optional(),
|
|
201
|
+
created_at: z.string(),
|
|
202
|
+
updated_at: z.string(),
|
|
203
|
+
state: z.unknown()
|
|
204
|
+
}),
|
|
205
|
+
history: z.array(z.unknown())
|
|
206
|
+
});
|
|
207
|
+
var SessionImportError = class extends AcpxOperationalError {
|
|
208
|
+
code;
|
|
209
|
+
exitCode = 2;
|
|
210
|
+
constructor(message, code) {
|
|
211
|
+
super(message, {
|
|
212
|
+
outputCode: "USAGE",
|
|
213
|
+
detailCode: code,
|
|
214
|
+
origin: "cli"
|
|
215
|
+
});
|
|
216
|
+
this.code = code;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
function importError(message, code) {
|
|
220
|
+
return new SessionImportError(message, code);
|
|
221
|
+
}
|
|
222
|
+
function parseArchiveJson(raw) {
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(raw);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw importError(`Invalid session export archive JSON: ${error instanceof Error ? error.message : String(error)}`, "invalid-archive");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function assertSupportedFormatVersion(parsed) {
|
|
230
|
+
const record = parsed && typeof parsed === "object" ? parsed : {};
|
|
231
|
+
if (record.format_version !== SUPPORTED_FORMAT_VERSION) throw importError(`Unsupported session export format_version ${String(record.format_version)}; supported version is ${SUPPORTED_FORMAT_VERSION}`, "unsupported-format-version");
|
|
232
|
+
}
|
|
233
|
+
function parseArchive(raw) {
|
|
234
|
+
const parsed = parseArchiveJson(raw);
|
|
235
|
+
assertSupportedFormatVersion(parsed);
|
|
236
|
+
try {
|
|
237
|
+
return exportedSessionSchema.parse(parsed);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error instanceof ZodError) throw importError(`Invalid session export archive: ${error.issues[0]?.message}`, "invalid-archive");
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function generateRecordId(sessionsDir) {
|
|
244
|
+
for (;;) {
|
|
245
|
+
const recordId = randomUUID();
|
|
246
|
+
const filePath = path.join(sessionsDir, `${encodeURIComponent(recordId)}.json`);
|
|
247
|
+
try {
|
|
248
|
+
await fs$1.access(filePath);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error.code === "ENOENT") return recordId;
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function resolveImportedCwd(cwdRelative, newCwd) {
|
|
256
|
+
if (newCwd) return path.resolve(newCwd);
|
|
257
|
+
if (path.isAbsolute(cwdRelative)) return cwdRelative;
|
|
258
|
+
return path.join(os.homedir(), cwdRelative);
|
|
259
|
+
}
|
|
260
|
+
function resolveImportedName(parsed, requestedName) {
|
|
261
|
+
return requestedName ?? parsed.session.name ?? void 0;
|
|
262
|
+
}
|
|
263
|
+
function assertExpectedAgentCommand(parsed, sourceRecord, options) {
|
|
264
|
+
const expectedAgentCommand = options.expectedAgentCommand;
|
|
265
|
+
if (!expectedAgentCommand) return;
|
|
266
|
+
const expectedAgentName = normalizeAgentIdentity(options.expectedAgentName);
|
|
267
|
+
const archiveAgentName = normalizeAgentIdentity(parsed.session.agent_name);
|
|
268
|
+
const archiveCommandMatches = agentCommandMatchesExpected(parsed.session.agent, expectedAgentCommand, expectedAgentName);
|
|
269
|
+
const stateCommandMatches = agentCommandMatchesExpected(sourceRecord.agentCommand, expectedAgentCommand, expectedAgentName);
|
|
270
|
+
if (archiveCommandMatches && stateCommandMatches && archiveAgentNameMatches({
|
|
271
|
+
archiveAgentName,
|
|
272
|
+
expectedAgentName,
|
|
273
|
+
archiveCommand: parsed.session.agent,
|
|
274
|
+
stateCommand: sourceRecord.agentCommand,
|
|
275
|
+
expectedAgentCommand
|
|
276
|
+
})) {
|
|
277
|
+
sourceRecord.agentCommand = expectedAgentCommand;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
throw importError("Session export archive agent does not match the requested agent", "agent-mismatch");
|
|
281
|
+
}
|
|
282
|
+
function archiveAgentNameMatches(params) {
|
|
283
|
+
if (params.archiveCommand === params.expectedAgentCommand && params.stateCommand === params.expectedAgentCommand) return true;
|
|
284
|
+
return params.archiveAgentName == null || params.expectedAgentName == null || params.archiveAgentName === params.expectedAgentName;
|
|
285
|
+
}
|
|
286
|
+
function normalizeAgentIdentity(agentName) {
|
|
287
|
+
const normalized = agentName?.trim().toLowerCase();
|
|
288
|
+
if (!normalized || normalized.length === 0) return;
|
|
289
|
+
return normalized === "factory-droid" || normalized === "factorydroid" ? "droid" : normalized;
|
|
290
|
+
}
|
|
291
|
+
function agentCommandMatchesExpected(archivedCommand, expectedAgentCommand, expectedAgentName) {
|
|
292
|
+
if (archivedCommand === expectedAgentCommand) return true;
|
|
293
|
+
return expectedAgentName ? commandLooksLikeBuiltInAgent(archivedCommand, expectedAgentName) : false;
|
|
294
|
+
}
|
|
295
|
+
function commandLooksLikeBuiltInAgent(command, agentName) {
|
|
296
|
+
const normalized = command.trim();
|
|
297
|
+
switch (agentName) {
|
|
298
|
+
case "pi": return /(?:^|\s)pi-acp(?:@|\s|$)/.test(normalized);
|
|
299
|
+
case "codex": return /(?:^|\s)@agentclientprotocol\/codex-acp(?:@|\s|$)/.test(normalized);
|
|
300
|
+
case "claude": return /(?:^|\s)@agentclientprotocol\/claude-agent-acp(?:@|\s|$)/.test(normalized);
|
|
301
|
+
default: return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function assertDestinationScopeAvailable(record) {
|
|
305
|
+
if (!await findSession({
|
|
306
|
+
agentCommand: record.agentCommand,
|
|
307
|
+
cwd: record.cwd,
|
|
308
|
+
name: record.name
|
|
309
|
+
})) return;
|
|
310
|
+
throw importError("A session already exists for the import destination scope; pass --name or --cwd to import a separate copy", "session-scope-exists");
|
|
311
|
+
}
|
|
312
|
+
async function assertProviderSessionAvailable(record) {
|
|
313
|
+
if (!(await listSessions()).find((session) => session.acpSessionId === record.acpSessionId)) return;
|
|
314
|
+
throw importError("A local session already uses this provider session id; prune or remove the existing record before importing this archive", "session-provider-exists");
|
|
315
|
+
}
|
|
316
|
+
function buildImportedRecord(parsed, sourceRecord, options) {
|
|
317
|
+
const eventLog = {
|
|
318
|
+
...defaultSessionEventLog(options.newRecordId),
|
|
319
|
+
max_segment_bytes: sourceRecord.eventLog.max_segment_bytes,
|
|
320
|
+
max_segments: sourceRecord.eventLog.max_segments,
|
|
321
|
+
segment_count: parsed.history.length > 0 ? 1 : sourceRecord.eventLog.segment_count
|
|
322
|
+
};
|
|
323
|
+
return {
|
|
324
|
+
...sourceRecord,
|
|
325
|
+
acpxRecordId: options.newRecordId,
|
|
326
|
+
cwd: options.cwd,
|
|
327
|
+
name: resolveImportedName(parsed, options.name),
|
|
328
|
+
closed: false,
|
|
329
|
+
closedAt: void 0,
|
|
330
|
+
pid: void 0,
|
|
331
|
+
agentStartedAt: void 0,
|
|
332
|
+
lastAgentExitCode: void 0,
|
|
333
|
+
lastAgentExitSignal: void 0,
|
|
334
|
+
lastAgentExitAt: void 0,
|
|
335
|
+
lastAgentDisconnectReason: void 0,
|
|
336
|
+
eventLog,
|
|
337
|
+
importedFrom: {
|
|
338
|
+
recordId: parsed.session.record_id,
|
|
339
|
+
cwdOriginal: parsed.session.cwd_original ?? parsed.session.cwd_relative,
|
|
340
|
+
exportedBy: parsed.exported_by,
|
|
341
|
+
exportedAt: parsed.exported_at
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
async function importSession(archivePath, options = {}) {
|
|
346
|
+
const parsed = parseArchive(await fs$1.readFile(archivePath, "utf8"));
|
|
347
|
+
const sourceRecord = parseSessionRecord(parsed.session.state);
|
|
348
|
+
if (!sourceRecord) throw importError("Invalid session export archive: session.state is not a session record", "invalid-archive");
|
|
349
|
+
assertExpectedAgentCommand(parsed, sourceRecord, options);
|
|
350
|
+
const sessionsDir = path.join(os.homedir(), ".acpx", "sessions");
|
|
351
|
+
await fs$1.mkdir(sessionsDir, { recursive: true });
|
|
352
|
+
const cwd = resolveImportedCwd(parsed.session.cwd_relative, options.newCwd);
|
|
353
|
+
const newRecordId = await generateRecordId(sessionsDir);
|
|
354
|
+
const newRecord = buildImportedRecord(parsed, sourceRecord, {
|
|
355
|
+
newRecordId,
|
|
356
|
+
cwd,
|
|
357
|
+
name: options.name
|
|
358
|
+
});
|
|
359
|
+
await assertDestinationScopeAvailable(newRecord);
|
|
360
|
+
await assertProviderSessionAvailable(newRecord);
|
|
361
|
+
await writeSessionRecord(newRecord);
|
|
362
|
+
if (parsed.history.length > 0) {
|
|
363
|
+
const history = parsed.history;
|
|
364
|
+
await fs$1.writeFile(sessionEventActivePath(newRecordId), `${history.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
record_id: newRecordId,
|
|
368
|
+
cwd
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
56
372
|
//#region src/cli/output/json-output.ts
|
|
57
373
|
function emitJsonResult(format, payload) {
|
|
58
374
|
if (format !== "json") return false;
|
|
@@ -71,11 +387,11 @@ let sessionModulePromise;
|
|
|
71
387
|
let outputModulePromise;
|
|
72
388
|
let outputRenderModulePromise;
|
|
73
389
|
function loadSessionModule() {
|
|
74
|
-
sessionModulePromise ??= import("./output-
|
|
390
|
+
sessionModulePromise ??= import("./output-D_BGt1YI.js").then((n) => n.i);
|
|
75
391
|
return sessionModulePromise;
|
|
76
392
|
}
|
|
77
393
|
function loadOutputModule() {
|
|
78
|
-
outputModulePromise ??= import("./output-
|
|
394
|
+
outputModulePromise ??= import("./output-D_BGt1YI.js").then((n) => n.r);
|
|
79
395
|
return outputModulePromise;
|
|
80
396
|
}
|
|
81
397
|
function loadOutputRenderModule() {
|
|
@@ -121,7 +437,7 @@ function resolveRequestedOutputPolicy(globalFlags) {
|
|
|
121
437
|
suppressReads: globalFlags.suppressReads === true
|
|
122
438
|
};
|
|
123
439
|
}
|
|
124
|
-
function sessionOptionsFromGlobalFlags(globalFlags) {
|
|
440
|
+
function sessionOptionsFromGlobalFlags$1(globalFlags) {
|
|
125
441
|
return {
|
|
126
442
|
model: globalFlags.model,
|
|
127
443
|
allowedTools: globalFlags.allowedTools,
|
|
@@ -129,7 +445,7 @@ function sessionOptionsFromGlobalFlags(globalFlags) {
|
|
|
129
445
|
systemPrompt: globalFlags.systemPrompt
|
|
130
446
|
};
|
|
131
447
|
}
|
|
132
|
-
async function resolvePermissionPolicyFromFlags(globalFlags) {
|
|
448
|
+
async function resolvePermissionPolicyFromFlags$1(globalFlags) {
|
|
133
449
|
try {
|
|
134
450
|
return await loadPermissionPolicySpec(globalFlags.permissionPolicy, globalFlags.cwd);
|
|
135
451
|
} catch (error) {
|
|
@@ -151,7 +467,8 @@ function buildSessionStartOptions(params) {
|
|
|
151
467
|
terminal: params.globalFlags.terminal,
|
|
152
468
|
timeoutMs: params.globalFlags.timeout,
|
|
153
469
|
verbose: params.globalFlags.verbose,
|
|
154
|
-
sessionOptions: sessionOptionsFromGlobalFlags(params.globalFlags)
|
|
470
|
+
sessionOptions: sessionOptionsFromGlobalFlags$1(params.globalFlags),
|
|
471
|
+
onModelWarning: params.globalFlags.jsonStrict ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
|
|
155
472
|
};
|
|
156
473
|
}
|
|
157
474
|
function resolveSessionListFilterCwd(flags, agentCwd) {
|
|
@@ -190,7 +507,7 @@ async function handlePrompt(explicitAgentName, promptParts, flags, command, conf
|
|
|
190
507
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
191
508
|
const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
|
|
192
509
|
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
|
|
193
|
-
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
510
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags$1(globalFlags);
|
|
194
511
|
const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
|
|
195
512
|
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
196
513
|
const [{ createOutputFormatter }, { printPromptSessionBanner, printQueuedPromptByFormat }, { sendSession }] = await Promise.all([
|
|
@@ -198,7 +515,7 @@ async function handlePrompt(explicitAgentName, promptParts, flags, command, conf
|
|
|
198
515
|
loadOutputRenderModule(),
|
|
199
516
|
loadSessionModule()
|
|
200
517
|
]);
|
|
201
|
-
const record = await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, flags
|
|
518
|
+
const record = await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command));
|
|
202
519
|
const outputFormatter = createOutputFormatter(outputPolicy.format, {
|
|
203
520
|
jsonContext: { sessionId: record.acpxRecordId },
|
|
204
521
|
suppressReads: outputPolicy.suppressReads
|
|
@@ -254,7 +571,7 @@ async function handleExec(explicitAgentName, promptParts, flags, command, config
|
|
|
254
571
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
255
572
|
const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
|
|
256
573
|
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
|
|
257
|
-
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
574
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags$1(globalFlags);
|
|
258
575
|
const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
|
|
259
576
|
const [{ createOutputFormatter }, { runOnce }] = await Promise.all([loadOutputModule(), loadSessionModule()]);
|
|
260
577
|
const outputFormatter = createOutputFormatter(outputPolicy.format, { suppressReads: outputPolicy.suppressReads });
|
|
@@ -411,7 +728,7 @@ async function handleSetConfigOption(explicitAgentName, configId, value, flags,
|
|
|
411
728
|
}
|
|
412
729
|
async function tryListAgentSessions(agent, flags, globalFlags, config) {
|
|
413
730
|
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
|
|
414
|
-
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
731
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags$1(globalFlags);
|
|
415
732
|
const { listAgentSessions } = await loadSessionModule();
|
|
416
733
|
try {
|
|
417
734
|
return await listAgentSessions({
|
|
@@ -466,7 +783,7 @@ async function handleSessionsClose(explicitAgentName, sessionName, command, conf
|
|
|
466
783
|
async function handleSessionsNew(explicitAgentName, flags, command, config) {
|
|
467
784
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
468
785
|
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
|
|
469
|
-
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
786
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags$1(globalFlags);
|
|
470
787
|
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
471
788
|
const [{ createSession, closeSession }, { printCreatedSessionBanner, printNewSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
|
|
472
789
|
const replaced = await findSession({
|
|
@@ -496,7 +813,7 @@ async function handleSessionsNew(explicitAgentName, flags, command, config) {
|
|
|
496
813
|
async function handleSessionsEnsure(explicitAgentName, flags, command, config) {
|
|
497
814
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
498
815
|
const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
|
|
499
|
-
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
816
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags$1(globalFlags);
|
|
500
817
|
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
501
818
|
const [{ ensureSession }, { printCreatedSessionBanner, printEnsuredSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
|
|
502
819
|
const result = await ensureSession(buildSessionStartOptions({
|
|
@@ -618,6 +935,46 @@ async function handleSessionsHistory(explicitAgentName, sessionName, flags, comm
|
|
|
618
935
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
619
936
|
printSessionHistoryByFormat(await findScopedSessionOrThrow(resolveAgentInvocation(explicitAgentName, globalFlags, config), sessionName), flags.limit, globalFlags.format);
|
|
620
937
|
}
|
|
938
|
+
async function handleSessionsExport(explicitAgentName, sessionName, flags, command, config) {
|
|
939
|
+
const globalFlags = resolveGlobalFlags(command, config);
|
|
940
|
+
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
941
|
+
const cwd = flags.sourceCwd ? path.resolve(agent.cwd, flags.sourceCwd) : agent.cwd;
|
|
942
|
+
await exportSession({
|
|
943
|
+
agentName: globalFlags.agent ? void 0 : agent.agentName,
|
|
944
|
+
agentCommand: agent.agentCommand,
|
|
945
|
+
cwd,
|
|
946
|
+
name: sessionName
|
|
947
|
+
}, flags.output);
|
|
948
|
+
if (emitJsonResult(globalFlags.format, {
|
|
949
|
+
action: "session_exported",
|
|
950
|
+
output: flags.output
|
|
951
|
+
})) return;
|
|
952
|
+
if (globalFlags.format === "quiet") {
|
|
953
|
+
process.stdout.write(`${flags.output}\n`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
process.stdout.write(`exported session to ${flags.output}\n`);
|
|
957
|
+
}
|
|
958
|
+
async function handleSessionsImport(explicitAgentName, archivePath, flags, command, config) {
|
|
959
|
+
const globalFlags = resolveGlobalFlags(command, config);
|
|
960
|
+
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
961
|
+
const result = await importSession(archivePath, {
|
|
962
|
+
name: flags.name,
|
|
963
|
+
newCwd: flags.destinationCwd ? path.resolve(globalFlags.cwd, flags.destinationCwd) : void 0,
|
|
964
|
+
expectedAgentName: globalFlags.agent ? void 0 : agent.agentName,
|
|
965
|
+
expectedAgentCommand: agent.agentCommand
|
|
966
|
+
});
|
|
967
|
+
if (emitJsonResult(globalFlags.format, {
|
|
968
|
+
action: "session_imported",
|
|
969
|
+
record_id: result.record_id,
|
|
970
|
+
cwd: result.cwd
|
|
971
|
+
})) return;
|
|
972
|
+
if (globalFlags.format === "quiet") {
|
|
973
|
+
process.stdout.write(`${result.record_id}\n`);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
process.stdout.write(`imported session ${result.record_id} at ${result.cwd}\n`);
|
|
977
|
+
}
|
|
621
978
|
async function handleSessionsPrune(explicitAgentName, flags, command, config) {
|
|
622
979
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
623
980
|
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
|
|
@@ -632,6 +989,285 @@ async function handleSessionsPrune(explicitAgentName, flags, command, config) {
|
|
|
632
989
|
}), globalFlags.format);
|
|
633
990
|
}
|
|
634
991
|
//#endregion
|
|
992
|
+
//#region src/cli/compare-command.ts
|
|
993
|
+
const DEFAULT_COMPARE_TIMEOUT_MS = 3e5;
|
|
994
|
+
const FINAL_MESSAGE_PREVIEW_CHARS = 200;
|
|
995
|
+
var CaptureFormatter = class {
|
|
996
|
+
setContext(_context) {}
|
|
997
|
+
onAcpMessage(_message) {}
|
|
998
|
+
onError(params) {}
|
|
999
|
+
onPermissionEscalation(_event) {}
|
|
1000
|
+
flush() {}
|
|
1001
|
+
};
|
|
1002
|
+
function asRecord$2(value) {
|
|
1003
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1004
|
+
}
|
|
1005
|
+
function numberField(source, keys) {
|
|
1006
|
+
if (!source) return null;
|
|
1007
|
+
for (const key of keys) {
|
|
1008
|
+
const value = source[key];
|
|
1009
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1010
|
+
}
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
function collapseWhitespace(value) {
|
|
1014
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1015
|
+
}
|
|
1016
|
+
function truncate(value, maxChars) {
|
|
1017
|
+
if (value.length <= maxChars) return value;
|
|
1018
|
+
return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
1019
|
+
}
|
|
1020
|
+
async function readStdin() {
|
|
1021
|
+
let data = "";
|
|
1022
|
+
for await (const chunk of process.stdin) data += String(chunk);
|
|
1023
|
+
return data;
|
|
1024
|
+
}
|
|
1025
|
+
async function readPromptFile(filePath, promptText, cwd) {
|
|
1026
|
+
const prompt = mergePromptSourceWithText(filePath === "-" ? await readStdin() : await fs$1.readFile(path.resolve(cwd, filePath), "utf8"), promptText);
|
|
1027
|
+
if (prompt.length === 0) throw new InvalidArgumentError("Prompt from --file is empty");
|
|
1028
|
+
return prompt;
|
|
1029
|
+
}
|
|
1030
|
+
async function readPromptFromStdin() {
|
|
1031
|
+
if (process.stdin.isTTY) throw new InvalidArgumentError("Prompt is required (pass as final argument, --file, or pipe via stdin)");
|
|
1032
|
+
const prompt = parsePromptSource(await readStdin());
|
|
1033
|
+
if (prompt.length === 0) throw new InvalidArgumentError("Prompt from stdin is empty");
|
|
1034
|
+
return prompt;
|
|
1035
|
+
}
|
|
1036
|
+
async function readPromptInput(filePath, promptText, cwd) {
|
|
1037
|
+
try {
|
|
1038
|
+
if (filePath) return await readPromptFile(filePath, promptText, cwd);
|
|
1039
|
+
const joined = promptText.trim();
|
|
1040
|
+
if (joined.length > 0) return textPrompt(joined);
|
|
1041
|
+
return await readPromptFromStdin();
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
if (error instanceof PromptInputValidationError) throw new InvalidArgumentError(error.message);
|
|
1044
|
+
throw error;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function promptTokensAfterDoubleDash(command) {
|
|
1048
|
+
const commandName = command.name();
|
|
1049
|
+
const commandIndex = process.argv.findIndex((token, index) => index >= 2 && token === commandName);
|
|
1050
|
+
if (commandIndex < 0) return [];
|
|
1051
|
+
const delimiterIndex = process.argv.findIndex((token, index) => index > commandIndex && token === "--");
|
|
1052
|
+
return delimiterIndex < 0 ? [] : process.argv.slice(delimiterIndex + 1);
|
|
1053
|
+
}
|
|
1054
|
+
function splitCompareArgs(args, filePath, command) {
|
|
1055
|
+
if (filePath) {
|
|
1056
|
+
if (args.length === 0) throw new InvalidArgumentError("At least one agent is required");
|
|
1057
|
+
return {
|
|
1058
|
+
agents: args,
|
|
1059
|
+
promptText: ""
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
const promptTokens = promptTokensAfterDoubleDash(command);
|
|
1063
|
+
if (promptTokens.length > 0) {
|
|
1064
|
+
const agents = args.slice(0, -promptTokens.length);
|
|
1065
|
+
if (agents.length === 0) throw new InvalidArgumentError("At least one agent is required");
|
|
1066
|
+
return {
|
|
1067
|
+
agents,
|
|
1068
|
+
promptText: promptTokens.join(" ")
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
if (args.length < 2) throw new InvalidArgumentError("Usage: acpx compare <agent>... '<prompt>'");
|
|
1072
|
+
return {
|
|
1073
|
+
agents: args.slice(0, -1),
|
|
1074
|
+
promptText: args[args.length - 1] ?? ""
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function captureUsage(update, capture) {
|
|
1078
|
+
const source = asRecord$2(asRecord$2(update._meta)?.usage) ?? update;
|
|
1079
|
+
capture.usage = {
|
|
1080
|
+
input_tokens: numberField(source, ["input_tokens", "inputTokens"]) ?? void 0,
|
|
1081
|
+
output_tokens: numberField(source, ["output_tokens", "outputTokens"]) ?? void 0,
|
|
1082
|
+
total_tokens: numberField(source, [
|
|
1083
|
+
"total_tokens",
|
|
1084
|
+
"totalTokens",
|
|
1085
|
+
"size",
|
|
1086
|
+
"used"
|
|
1087
|
+
]) ?? void 0
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function captureSessionUpdate(notification, capture) {
|
|
1091
|
+
const update = asRecord$2(notification.update);
|
|
1092
|
+
if (!update) return;
|
|
1093
|
+
if (update.sessionUpdate === "agent_message_chunk") {
|
|
1094
|
+
const content = asRecord$2(update.content);
|
|
1095
|
+
if (content?.type === "text" && typeof content.text === "string") capture.finalMessage += content.text;
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (update.sessionUpdate === "usage_update") captureUsage(update, capture);
|
|
1099
|
+
}
|
|
1100
|
+
function rowStatusFromPermissionStats(stats) {
|
|
1101
|
+
return stats.denied + stats.cancelled > 0 ? "permission_denied" : "ok";
|
|
1102
|
+
}
|
|
1103
|
+
function sessionOptionsFromGlobalFlags(globalFlags) {
|
|
1104
|
+
return {
|
|
1105
|
+
model: globalFlags.model,
|
|
1106
|
+
allowedTools: globalFlags.allowedTools,
|
|
1107
|
+
maxTurns: globalFlags.maxTurns,
|
|
1108
|
+
systemPrompt: globalFlags.systemPrompt
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
async function resolvePermissionPolicyFromFlags(globalFlags) {
|
|
1112
|
+
try {
|
|
1113
|
+
return await loadPermissionPolicySpec(globalFlags.permissionPolicy, globalFlags.cwd);
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
throw new InvalidArgumentError(`Invalid permission policy: ${error instanceof Error ? error.message : String(error)}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
function buildSuccessRow(agentName, result, capture, startedAt) {
|
|
1119
|
+
const permissionStats = result.permissionStats;
|
|
1120
|
+
return {
|
|
1121
|
+
agent: agentName,
|
|
1122
|
+
status: result.stopReason === "cancelled" ? "cancelled" : rowStatusFromPermissionStats(permissionStats),
|
|
1123
|
+
stop_reason: result.stopReason,
|
|
1124
|
+
wall_ms: Math.round(performance.now() - startedAt),
|
|
1125
|
+
input_tokens: capture.usage.input_tokens ?? null,
|
|
1126
|
+
output_tokens: capture.usage.output_tokens ?? null,
|
|
1127
|
+
total_tokens: capture.usage.total_tokens ?? null,
|
|
1128
|
+
final_message: truncate(collapseWhitespace(capture.finalMessage), FINAL_MESSAGE_PREVIEW_CHARS),
|
|
1129
|
+
error: null,
|
|
1130
|
+
permission_requests: permissionStats.requested,
|
|
1131
|
+
permission_denied: permissionStats.denied + permissionStats.cancelled
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function buildErrorRow(agentName, caught, capture, startedAt) {
|
|
1135
|
+
return {
|
|
1136
|
+
agent: agentName,
|
|
1137
|
+
status: caught instanceof TimeoutError ? "cancelled" : "error",
|
|
1138
|
+
stop_reason: null,
|
|
1139
|
+
wall_ms: Math.round(performance.now() - startedAt),
|
|
1140
|
+
input_tokens: capture.usage.input_tokens ?? null,
|
|
1141
|
+
output_tokens: capture.usage.output_tokens ?? null,
|
|
1142
|
+
total_tokens: capture.usage.total_tokens ?? null,
|
|
1143
|
+
final_message: truncate(collapseWhitespace(capture.finalMessage), FINAL_MESSAGE_PREVIEW_CHARS),
|
|
1144
|
+
error: truncate(collapseWhitespace(caught instanceof Error ? caught.message : String(caught)), FINAL_MESSAGE_PREVIEW_CHARS),
|
|
1145
|
+
permission_requests: 0,
|
|
1146
|
+
permission_denied: 0
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
async function runAgentForCompare(params) {
|
|
1150
|
+
const capture = {
|
|
1151
|
+
finalMessage: "",
|
|
1152
|
+
usage: {},
|
|
1153
|
+
errors: []
|
|
1154
|
+
};
|
|
1155
|
+
const formatter = new CaptureFormatter();
|
|
1156
|
+
const t0 = performance.now();
|
|
1157
|
+
try {
|
|
1158
|
+
const agent = resolveAgentInvocation(params.agentName, params.globalFlags, params.config);
|
|
1159
|
+
const result = await runOnce({
|
|
1160
|
+
agentCommand: agent.agentCommand,
|
|
1161
|
+
cwd: agent.cwd,
|
|
1162
|
+
prompt: params.prompt,
|
|
1163
|
+
mcpServers: params.config.mcpServers,
|
|
1164
|
+
permissionMode: resolvePermissionMode(params.globalFlags, params.config.defaultPermissions),
|
|
1165
|
+
nonInteractivePermissions: params.globalFlags.nonInteractivePermissions,
|
|
1166
|
+
permissionPolicy: params.permissionPolicy,
|
|
1167
|
+
authCredentials: params.config.auth,
|
|
1168
|
+
authPolicy: params.globalFlags.authPolicy,
|
|
1169
|
+
terminal: params.globalFlags.terminal,
|
|
1170
|
+
outputFormatter: formatter,
|
|
1171
|
+
suppressSdkConsoleErrors: true,
|
|
1172
|
+
timeoutMs: params.globalFlags.timeout ?? DEFAULT_COMPARE_TIMEOUT_MS,
|
|
1173
|
+
verbose: params.globalFlags.verbose,
|
|
1174
|
+
promptRetries: params.globalFlags.promptRetries,
|
|
1175
|
+
sessionOptions: sessionOptionsFromGlobalFlags(params.globalFlags),
|
|
1176
|
+
onSessionUpdate: (notification) => captureSessionUpdate(notification, capture)
|
|
1177
|
+
});
|
|
1178
|
+
return buildSuccessRow(params.agentName, result, capture, t0);
|
|
1179
|
+
} catch (caught) {
|
|
1180
|
+
return buildErrorRow(params.agentName, caught, capture, t0);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function formatCell(value) {
|
|
1184
|
+
if (value == null || value === "") return "-";
|
|
1185
|
+
if (typeof value === "string") return collapseWhitespace(value);
|
|
1186
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1187
|
+
return collapseWhitespace(JSON.stringify(value));
|
|
1188
|
+
}
|
|
1189
|
+
function renderTable(rows) {
|
|
1190
|
+
const headers = [
|
|
1191
|
+
"agent",
|
|
1192
|
+
"status",
|
|
1193
|
+
"wall_ms",
|
|
1194
|
+
"input",
|
|
1195
|
+
"output",
|
|
1196
|
+
"total",
|
|
1197
|
+
"permissions",
|
|
1198
|
+
"stop_reason",
|
|
1199
|
+
"final_message",
|
|
1200
|
+
"error"
|
|
1201
|
+
];
|
|
1202
|
+
const body = rows.map((row) => [
|
|
1203
|
+
row.agent,
|
|
1204
|
+
row.status,
|
|
1205
|
+
row.wall_ms,
|
|
1206
|
+
row.input_tokens,
|
|
1207
|
+
row.output_tokens,
|
|
1208
|
+
row.total_tokens,
|
|
1209
|
+
`${row.permission_denied}/${row.permission_requests}`,
|
|
1210
|
+
row.stop_reason,
|
|
1211
|
+
row.final_message,
|
|
1212
|
+
row.error
|
|
1213
|
+
]);
|
|
1214
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...body.map((cells) => formatCell(cells[index]).length)));
|
|
1215
|
+
const formatRow = (cells) => cells.map((cell, index) => truncate(formatCell(cell), widths[index] ?? 24).padEnd(widths[index] ?? 24)).join(" ").trimEnd();
|
|
1216
|
+
return [
|
|
1217
|
+
formatRow(headers),
|
|
1218
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
1219
|
+
...body.map(formatRow)
|
|
1220
|
+
].join("\n");
|
|
1221
|
+
}
|
|
1222
|
+
function printRows(rows, format) {
|
|
1223
|
+
if (format === "json") {
|
|
1224
|
+
process.stdout.write(`${JSON.stringify(rows)}\n`);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (format === "quiet") {
|
|
1228
|
+
for (const row of rows) process.stdout.write(`${row.agent}\t${row.status}\n`);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
process.stdout.write(`${renderTable(rows)}\n`);
|
|
1232
|
+
}
|
|
1233
|
+
function updateCompareExitCode(rows) {
|
|
1234
|
+
if (rows.some((row) => row.status === "error")) {
|
|
1235
|
+
process.exitCode = EXIT_CODES.ERROR;
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (rows.some((row) => row.status === "permission_denied")) {
|
|
1239
|
+
process.exitCode = EXIT_CODES.PERMISSION_DENIED;
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (rows.some((row) => row.status === "cancelled")) process.exitCode = EXIT_CODES.TIMEOUT;
|
|
1243
|
+
}
|
|
1244
|
+
function resolvePromptFile(flags) {
|
|
1245
|
+
if (flags.file && flags.promptFile && flags.file !== flags.promptFile) throw new InvalidArgumentError("Use only one prompt file flag: --file or --prompt-file");
|
|
1246
|
+
return flags.file ?? flags.promptFile;
|
|
1247
|
+
}
|
|
1248
|
+
function registerCompareCommand(program, config) {
|
|
1249
|
+
program.command("compare").description("Run one prompt across multiple agents and summarize the results").argument("<args...>", "Agents followed by prompt text, or agents with --file").option("--cwd <dir>", "Target workspace").option("--approve-all", "Auto-approve all permission requests").option("--approve-reads", "Auto-approve read/search requests and prompt for writes").option("--deny-all", "Deny all permission requests").option("--timeout <seconds>", "Per-agent timeout in seconds", parseTimeoutSeconds).option("--format <fmt>", "Output format: text, json, quiet", parseOutputFormat$1).option("--json", "Alias for --format json").option("-f, --file <path>", "Read prompt text from file path (use - for stdin)", (value) => parseNonEmptyValue("Prompt file", value)).option("--prompt-file <path>", "Alias for --file", (value) => parseNonEmptyValue("Prompt file", value)).action(async function(args, flags) {
|
|
1250
|
+
if (config.disableExec) throw new Error("compare subcommand is disabled by configuration (disableExec: true)");
|
|
1251
|
+
const globalFlags = resolveGlobalFlags(this, config);
|
|
1252
|
+
if (globalFlags.agent) throw new InvalidArgumentError("Do not combine compare with --agent; pass agent names");
|
|
1253
|
+
const outputPolicy = resolveOutputPolicy(flags.json === true ? "json" : globalFlags.format, globalFlags.jsonStrict === true);
|
|
1254
|
+
const promptFile = resolvePromptFile(flags);
|
|
1255
|
+
const { agents, promptText } = splitCompareArgs(args, promptFile, this);
|
|
1256
|
+
const prompt = await readPromptInput(promptFile, promptText, globalFlags.cwd);
|
|
1257
|
+
const permissionPolicy = await resolvePermissionPolicyFromFlags(globalFlags);
|
|
1258
|
+
const rows = [];
|
|
1259
|
+
for (const agentName of agents) rows.push(await runAgentForCompare({
|
|
1260
|
+
agentName,
|
|
1261
|
+
prompt,
|
|
1262
|
+
config,
|
|
1263
|
+
globalFlags,
|
|
1264
|
+
permissionPolicy
|
|
1265
|
+
}));
|
|
1266
|
+
printRows(rows, outputPolicy.format);
|
|
1267
|
+
updateCompareExitCode(rows);
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
//#endregion
|
|
635
1271
|
//#region src/mcp-servers.ts
|
|
636
1272
|
function asRecord$1(value) {
|
|
637
1273
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -735,7 +1371,6 @@ function parseOptionalMcpServers(value, sourcePath, fieldName = "mcpServers") {
|
|
|
735
1371
|
}
|
|
736
1372
|
//#endregion
|
|
737
1373
|
//#region src/cli/config.ts
|
|
738
|
-
const DEFAULT_TIMEOUT_MS = void 0;
|
|
739
1374
|
const DEFAULT_TTL_MS = 3e5;
|
|
740
1375
|
const DEFAULT_PERMISSION_MODE = "approve-reads";
|
|
741
1376
|
const DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY = "deny";
|
|
@@ -802,7 +1437,7 @@ function parseOutputFormat(value, sourcePath) {
|
|
|
802
1437
|
function parseDefaultAgent(value, sourcePath) {
|
|
803
1438
|
if (value == null) return;
|
|
804
1439
|
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid config defaultAgent in ${sourcePath}: expected non-empty string`);
|
|
805
|
-
return normalizeAgentName(value);
|
|
1440
|
+
return normalizeAgentName$1(value);
|
|
806
1441
|
}
|
|
807
1442
|
function parseAgents(value, sourcePath) {
|
|
808
1443
|
if (value == null) return;
|
|
@@ -813,7 +1448,7 @@ function parseAgents(value, sourcePath) {
|
|
|
813
1448
|
const command = raw.command;
|
|
814
1449
|
if (typeof command !== "string" || command.trim().length === 0) throw new Error(`Invalid config agents.${name}.command in ${sourcePath}: expected non-empty string`);
|
|
815
1450
|
const args = parseAgentArgs(raw.args, name, sourcePath);
|
|
816
|
-
parsed[normalizeAgentName(name)] = args.length > 0 ? `${command.trim()} ${args.map(quoteCommandArg).join(" ")}` : command.trim();
|
|
1451
|
+
parsed[normalizeAgentName$1(name)] = args.length > 0 ? `${command.trim()} ${args.map(quoteCommandArg).join(" ")}` : command.trim();
|
|
817
1452
|
}
|
|
818
1453
|
return parsed;
|
|
819
1454
|
}
|
|
@@ -937,7 +1572,6 @@ function hasConfigKey(config, key) {
|
|
|
937
1572
|
function resolveTimeoutMs(projectConfig, projectPath, globalConfig, globalPath) {
|
|
938
1573
|
if (hasConfigKey(projectConfig, "timeout")) return parseTimeoutMs(projectConfig?.timeout, projectPath);
|
|
939
1574
|
if (hasConfigKey(globalConfig, "timeout")) return parseTimeoutMs(globalConfig?.timeout, globalPath);
|
|
940
|
-
return DEFAULT_TIMEOUT_MS;
|
|
941
1575
|
}
|
|
942
1576
|
function resolveMcpServers(projectConfig, projectPath, globalConfig, globalPath) {
|
|
943
1577
|
if (hasConfigKey(projectConfig, "mcpServers")) return parseMcpServers(projectConfig?.mcpServers, projectPath);
|
|
@@ -1409,6 +2043,16 @@ function registerStatusCommand(parent, explicitAgentName, config, description) {
|
|
|
1409
2043
|
}
|
|
1410
2044
|
//#endregion
|
|
1411
2045
|
//#region src/cli/command-registration.ts
|
|
2046
|
+
var LocalAttributeOption = class extends Option {
|
|
2047
|
+
localAttributeName;
|
|
2048
|
+
constructor(flags, description, localAttributeName) {
|
|
2049
|
+
super(flags, description);
|
|
2050
|
+
this.localAttributeName = localAttributeName;
|
|
2051
|
+
}
|
|
2052
|
+
attributeName() {
|
|
2053
|
+
return this.localAttributeName;
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
1412
2056
|
function addSessionsListOptions(command) {
|
|
1413
2057
|
return command.option("--local", "List local acpx session records instead of agent protocol sessions").option("--cursor <cursor>", "Opaque ACP session/list cursor", (value) => parseNonEmptyValue("Cursor", value)).option("--filter-cwd <dir>", "Filter agent sessions by working directory", (value) => parseNonEmptyValue("Filter cwd", value));
|
|
1414
2058
|
}
|
|
@@ -1439,6 +2083,12 @@ function registerSessionsCommand(parent, explicitAgentName, config) {
|
|
|
1439
2083
|
sessionsCommand.command("read").description("Read full session history").argument("[name]", "Session name", parseSessionName).option("--tail <count>", "Show only the last N entries instead of all history", parseHistoryLimit).action(async function(name, flags) {
|
|
1440
2084
|
await handleSessionsHistory(explicitAgentName, name, { limit: flags.tail ?? 0 }, this, config);
|
|
1441
2085
|
});
|
|
2086
|
+
sessionsCommand.command("export").description("Export a portable session archive").argument("[name]", "Session name", parseSessionName).requiredOption("--output <path>", "Output archive path", (value) => parseNonEmptyValue("Output path", value)).addOption(new LocalAttributeOption("--cwd <cwd>", "Session cwd to export", "sourceCwd").argParser((value) => parseNonEmptyValue("Session cwd", value))).action(async function(name, flags) {
|
|
2087
|
+
await handleSessionsExport(explicitAgentName, name, flags, this, config);
|
|
2088
|
+
});
|
|
2089
|
+
sessionsCommand.command("import").description("Import a portable session archive").argument("<archive-path>", "Archive path", (value) => parseNonEmptyValue("Archive path", value)).option("--name <name>", "Imported session name", parseSessionName).addOption(new LocalAttributeOption("--cwd <cwd>", "Imported session cwd", "destinationCwd").argParser((value) => parseNonEmptyValue("Imported session cwd", value))).action(async function(archivePath, flags) {
|
|
2090
|
+
await handleSessionsImport(explicitAgentName, archivePath, flags, this, config);
|
|
2091
|
+
});
|
|
1442
2092
|
sessionsCommand.command("prune").description("Delete closed sessions and free disk space").option("--dry-run", "Preview what would be pruned without deleting anything").option("--before <date>", "Prune sessions closed before this date", parsePruneBeforeDate).option("--older-than <days>", "Prune sessions closed more than N days ago", parseDaysOlderThan).option("--include-history", "Also delete event stream files (.stream.ndjson)").action(async function(flags) {
|
|
1443
2093
|
await handleSessionsPrune(explicitAgentName, flags, this, config);
|
|
1444
2094
|
});
|
|
@@ -1491,7 +2141,7 @@ function registerAgentCommand(program, agentName, config) {
|
|
|
1491
2141
|
}
|
|
1492
2142
|
function registerFlowCommand(program, config) {
|
|
1493
2143
|
program.command("flow").description("Run multi-step ACP workflows from flow files").command("run").description("Run a flow file").argument("<file>", "Flow module path").option("--input-json <json>", "Flow input as JSON").option("--input-file <path>", "Read flow input JSON from file").option("--default-agent <name>", "Default agent profile for ACP nodes without profile", (value) => parseNonEmptyValue("Default agent", value)).action(async function(file, flags) {
|
|
1494
|
-
const { handleFlowRun } = await import("./cli-
|
|
2144
|
+
const { handleFlowRun } = await import("./cli-CC2w0U-A.js");
|
|
1495
2145
|
await handleFlowRun(file, flags, this, config);
|
|
1496
2146
|
});
|
|
1497
2147
|
}
|
|
@@ -1506,6 +2156,7 @@ function registerDefaultCommands(program, config) {
|
|
|
1506
2156
|
});
|
|
1507
2157
|
registerSessionsCommand(program, void 0, config);
|
|
1508
2158
|
registerConfigCommand(program, config);
|
|
2159
|
+
registerCompareCommand(program, config);
|
|
1509
2160
|
registerFlowCommand(program, config);
|
|
1510
2161
|
}
|
|
1511
2162
|
//#endregion
|
|
@@ -1587,54 +2238,12 @@ async function runQueueOwnerFromEnv(env) {
|
|
|
1587
2238
|
await runSessionQueueOwner(parseQueueOwnerPayload(payload));
|
|
1588
2239
|
}
|
|
1589
2240
|
//#endregion
|
|
1590
|
-
//#region src/version.ts
|
|
1591
|
-
const UNKNOWN_VERSION = "0.0.0-unknown";
|
|
1592
|
-
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
1593
|
-
let cachedVersion = null;
|
|
1594
|
-
function parseVersion(value) {
|
|
1595
|
-
if (typeof value !== "string") return null;
|
|
1596
|
-
const trimmed = value.trim();
|
|
1597
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
1598
|
-
}
|
|
1599
|
-
function readPackageVersion(packageJsonPath) {
|
|
1600
|
-
try {
|
|
1601
|
-
return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
|
|
1602
|
-
} catch {
|
|
1603
|
-
return null;
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
function resolveVersionFromAncestors(startDir) {
|
|
1607
|
-
let current = startDir;
|
|
1608
|
-
while (true) {
|
|
1609
|
-
const packageVersion = readPackageVersion(path.join(current, "package.json"));
|
|
1610
|
-
if (packageVersion) return packageVersion;
|
|
1611
|
-
const parent = path.dirname(current);
|
|
1612
|
-
if (parent === current) return null;
|
|
1613
|
-
current = parent;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
function resolveAcpxVersion(params) {
|
|
1617
|
-
const envVersion = resolvePackageEnvVersion(params?.env ?? process.env);
|
|
1618
|
-
if (envVersion) return envVersion;
|
|
1619
|
-
if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
|
|
1620
|
-
return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
|
|
1621
|
-
}
|
|
1622
|
-
function resolvePackageEnvVersion(env) {
|
|
1623
|
-
const envPackageName = parseVersion(env.npm_package_name);
|
|
1624
|
-
const envVersion = parseVersion(env.npm_package_version);
|
|
1625
|
-
return envPackageName === "acpx" ? envVersion : null;
|
|
1626
|
-
}
|
|
1627
|
-
function getAcpxVersion() {
|
|
1628
|
-
if (cachedVersion) return cachedVersion;
|
|
1629
|
-
cachedVersion = resolveAcpxVersion();
|
|
1630
|
-
return cachedVersion;
|
|
1631
|
-
}
|
|
1632
|
-
//#endregion
|
|
1633
2241
|
//#region src/cli-core.ts
|
|
1634
2242
|
const TOP_LEVEL_VERBS = new Set([
|
|
1635
2243
|
"prompt",
|
|
1636
2244
|
"exec",
|
|
1637
2245
|
"cancel",
|
|
2246
|
+
"compare",
|
|
1638
2247
|
"flow",
|
|
1639
2248
|
"set-mode",
|
|
1640
2249
|
"set",
|
|
@@ -1872,13 +2481,18 @@ async function handleProgramParseError(error, requestedOutputPolicy) {
|
|
|
1872
2481
|
process.exit(exitCodeForOutputErrorCode(normalized.code));
|
|
1873
2482
|
}
|
|
1874
2483
|
async function main(argv = process.argv) {
|
|
1875
|
-
const rawArgs = argv.slice(2);
|
|
2484
|
+
const rawArgs = normalizeLifecycleScriptArgs(argv.slice(2));
|
|
2485
|
+
const normalizedArgv = [
|
|
2486
|
+
argv[0] ?? "node",
|
|
2487
|
+
argv[1] ?? "acpx",
|
|
2488
|
+
...rawArgs
|
|
2489
|
+
];
|
|
1876
2490
|
if (await handleQueueOwnerCommand(argv)) return;
|
|
1877
2491
|
if (isTopLevelVersionRequest(rawArgs)) {
|
|
1878
2492
|
process.stdout.write(`${getAcpxVersion()}\n`);
|
|
1879
2493
|
return;
|
|
1880
2494
|
}
|
|
1881
|
-
await maybeHandleSkillflag(
|
|
2495
|
+
await maybeHandleSkillflag(normalizedArgv);
|
|
1882
2496
|
const config = await loadResolvedConfig(detectInitialCwd(rawArgs));
|
|
1883
2497
|
const requestedJsonStrict = detectJsonStrict(rawArgs);
|
|
1884
2498
|
const requestedOutputPolicy = {
|
|
@@ -1907,7 +2521,7 @@ async function main(argv = process.argv) {
|
|
|
1907
2521
|
try {
|
|
1908
2522
|
await runWithOutputPolicy(requestedOutputPolicy, async () => {
|
|
1909
2523
|
try {
|
|
1910
|
-
await program.parseAsync(
|
|
2524
|
+
await program.parseAsync(normalizedArgv);
|
|
1911
2525
|
} catch (error) {
|
|
1912
2526
|
await handleProgramParseError(error, requestedOutputPolicy);
|
|
1913
2527
|
}
|
|
@@ -1916,6 +2530,10 @@ async function main(argv = process.argv) {
|
|
|
1916
2530
|
flushPerfMetricsCapture();
|
|
1917
2531
|
}
|
|
1918
2532
|
}
|
|
2533
|
+
function normalizeLifecycleScriptArgs(rawArgs) {
|
|
2534
|
+
if (rawArgs[0] === "--" && (process.env.npm_lifecycle_event || process.env.npm_lifecycle_script)) return rawArgs.slice(1);
|
|
2535
|
+
return rawArgs;
|
|
2536
|
+
}
|
|
1919
2537
|
//#endregion
|
|
1920
2538
|
//#region src/cli.ts
|
|
1921
2539
|
function installBrokenPipeHandler(stream) {
|