acpx 0.4.1 → 0.5.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.
Files changed (47) hide show
  1. package/dist/agent-registry-DGw0-3Tc.js +54 -0
  2. package/dist/agent-registry-DGw0-3Tc.js.map +1 -0
  3. package/dist/{cli-idpWyCOs.js → cli-CLRrs6eQ.js} +8 -12
  4. package/dist/cli-CLRrs6eQ.js.map +1 -0
  5. package/dist/cli.d.ts +2 -2
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +1018 -1009
  8. package/dist/cli.js.map +1 -1
  9. package/dist/client-DLTWuu4w.d.ts +116 -0
  10. package/dist/client-DLTWuu4w.d.ts.map +1 -0
  11. package/dist/{flags-CCcX9fZj.js → flags-BmubjvOw.js} +5 -55
  12. package/dist/flags-BmubjvOw.js.map +1 -0
  13. package/dist/{flows-BL1tSvZT.js → flows-CR7xCmkR.js} +471 -281
  14. package/dist/flows-CR7xCmkR.js.map +1 -0
  15. package/dist/flows.d.ts +5 -9
  16. package/dist/flows.d.ts.map +1 -1
  17. package/dist/flows.js +1 -1
  18. package/dist/{queue-ipc-CE8_QGX3.js → ipc-DN6M4Ui9.js} +12 -571
  19. package/dist/ipc-DN6M4Ui9.js.map +1 -0
  20. package/dist/{acp-jsonrpc-BbBgC5gO.js → jsonrpc-M3y-qzy8.js} +2 -2
  21. package/dist/jsonrpc-M3y-qzy8.js.map +1 -0
  22. package/dist/{output-Du3m6oPQ.js → output-Di0M9Et8.js} +6 -6
  23. package/dist/output-Di0M9Et8.js.map +1 -0
  24. package/dist/perf-metrics-D9QC81lB.js +568 -0
  25. package/dist/perf-metrics-D9QC81lB.js.map +1 -0
  26. package/dist/{session-RO_LZUnv.js → prompt-turn-Bt8T3SRR.js} +2304 -3632
  27. package/dist/prompt-turn-Bt8T3SRR.js.map +1 -0
  28. package/dist/{output-render-Bz58qaQn.js → render-BL5ynRkN.js} +7 -6
  29. package/dist/render-BL5ynRkN.js.map +1 -0
  30. package/dist/runtime.d.ts +266 -0
  31. package/dist/runtime.d.ts.map +1 -0
  32. package/dist/runtime.js +984 -0
  33. package/dist/runtime.js.map +1 -0
  34. package/dist/session-BbN0SBgf.js +1488 -0
  35. package/dist/session-BbN0SBgf.js.map +1 -0
  36. package/dist/{types-CeRKmEQ1.d.ts → types-DXxLBQc3.d.ts} +40 -3
  37. package/dist/types-DXxLBQc3.d.ts.map +1 -0
  38. package/package.json +5 -3
  39. package/dist/acp-jsonrpc-BbBgC5gO.js.map +0 -1
  40. package/dist/cli-idpWyCOs.js.map +0 -1
  41. package/dist/flags-CCcX9fZj.js.map +0 -1
  42. package/dist/flows-BL1tSvZT.js.map +0 -1
  43. package/dist/output-Du3m6oPQ.js.map +0 -1
  44. package/dist/output-render-Bz58qaQn.js.map +0 -1
  45. package/dist/queue-ipc-CE8_QGX3.js.map +0 -1
  46. package/dist/session-RO_LZUnv.js.map +0 -1
  47. package/dist/types-CeRKmEQ1.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -1,1083 +1,1015 @@
1
1
  #!/usr/bin/env node
2
- import { _ as DEFAULT_AGENT_NAME, c as parseMaxTurns, d as parseTtlSeconds, f as resolveAgentInvocation, g as resolveSessionNameFromFlags, h as resolvePermissionMode, i as addSessionOption, l as parseNonEmptyValue, m as resolveOutputPolicy, n as addPromptInputOption, o as parseAllowedTools, p as resolveGlobalFlags, r as addSessionNameOption, s as parseHistoryLimit, t as addGlobalFlags, u as parseSessionName, v as listBuiltInAgents, y as normalizeAgentName } from "./flags-CCcX9fZj.js";
3
- import { A as OUTPUT_FORMATS, E as normalizeOutputError, F as mergePromptSourceWithText, I as parsePromptSource, O as EXIT_CODES, P as PromptInputValidationError, R as textPrompt, S as exitCodeForOutputErrorCode, s as probeQueueOwnerHealth } from "./queue-ipc-CE8_QGX3.js";
4
- import { S as InterruptedError, b as flushPerfMetricsCapture, c as buildQueueOwnerArgOverride, d as findSession, f as findSessionByDirectoryWalk, o as runSessionQueueOwner, u as findGitRepositoryRoot, x as installPerfMetricsCapture } from "./session-RO_LZUnv.js";
5
- import { i as emitJsonResult, n as formatPromptSessionBannerLine, t as agentSessionIdPayload } from "./output-render-Bz58qaQn.js";
2
+ import { T as EXIT_CODES, _ as exitCodeForOutputErrorCode, d as PromptInputValidationError, g as textPrompt, k as OUTPUT_FORMATS, m as parsePromptSource, p as mergePromptSourceWithText, x as normalizeOutputError } from "./perf-metrics-D9QC81lB.js";
3
+ import { n as listBuiltInAgents, r as normalizeAgentName, t as DEFAULT_AGENT_NAME } from "./agent-registry-DGw0-3Tc.js";
4
+ import { C as findGitRepositoryRoot, T as findSessionByDirectoryWalk, V as InterruptedError, w as findSession } from "./prompt-turn-Bt8T3SRR.js";
5
+ import { a as buildQueueOwnerArgOverride, n as runSessionQueueOwner, o as flushPerfMetricsCapture, s as installPerfMetricsCapture } from "./session-BbN0SBgf.js";
6
+ import { s as probeQueueOwnerHealth } from "./ipc-DN6M4Ui9.js";
7
+ import { c as parseMaxTurns, d as parseTtlSeconds, f as resolveAgentInvocation, g as resolveSessionNameFromFlags, h as resolvePermissionMode, i as addSessionOption, l as parseNonEmptyValue, m as resolveOutputPolicy, n as addPromptInputOption, o as parseAllowedTools, p as resolveGlobalFlags, r as addSessionNameOption, s as parseHistoryLimit, t as addGlobalFlags, u as parseSessionName } from "./flags-BmubjvOw.js";
8
+ import { i as emitJsonResult, n as formatPromptSessionBannerLine, t as agentSessionIdPayload } from "./render-BL5ynRkN.js";
9
+ import { t as createOutputFormatter } from "./output-Di0M9Et8.js";
6
10
  import { readFileSync, realpathSync } from "node:fs";
7
11
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import fs$1 from "node:fs/promises";
9
12
  import path from "node:path";
10
13
  import { Command, CommanderError, InvalidArgumentError } from "commander";
14
+ import fs$1 from "node:fs/promises";
11
15
  import os from "node:os";
12
- //#region src/mcp-servers.ts
13
- function asRecord$1(value) {
14
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
15
- return value;
16
+ //#region src/cli-public.ts
17
+ function configurePublicCli(options) {
18
+ const builtInAgents = options.listBuiltInAgents(options.config.agents);
19
+ for (const agentName of builtInAgents) options.registerAgentCommand(options.program, agentName, options.config);
20
+ options.registerDefaultCommands(options.program, options.config);
21
+ const scan = options.detectAgentToken(options.argv);
22
+ if (!scan.hasAgentOverride && scan.token && !options.topLevelVerbs.has(scan.token) && !builtInAgents.includes(scan.token)) options.registerAgentCommand(options.program, scan.token, options.config);
23
+ options.program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
24
+ if (promptParts.length === 0 && process.stdin.isTTY) {
25
+ if (options.requestedJsonStrict) throw new InvalidArgumentError("Prompt is required (pass as argument, --file, or pipe via stdin)");
26
+ this.outputHelp();
27
+ return;
28
+ }
29
+ await options.handlePromptAction(this, promptParts);
30
+ });
31
+ options.program.addHelpText("after", `
32
+ Examples:
33
+ acpx pi "review recent changes"
34
+ acpx openclaw exec "summarize active session state"
35
+ acpx codex sessions new
36
+ acpx codex "fix the tests"
37
+ acpx codex prompt "fix the tests"
38
+ acpx codex --no-wait "queue follow-up task"
39
+ acpx codex exec "what does this repo do"
40
+ acpx codex cancel
41
+ acpx codex set-mode plan
42
+ acpx codex set thought_level high
43
+ acpx codex -s backend "fix the API"
44
+ acpx codex sessions
45
+ acpx codex sessions new --name backend
46
+ acpx codex sessions ensure --name backend
47
+ acpx codex sessions close backend
48
+ acpx codex status
49
+ acpx config show
50
+ acpx config init
51
+ acpx --ttl 30 codex "investigate flaky tests"
52
+ acpx claude "refactor auth"
53
+ acpx --agent ./my-custom-server "do something"`);
16
54
  }
17
- function parseNonEmptyString(value, path) {
18
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid ${path}: expected non-empty string`);
19
- return value.trim();
55
+ //#endregion
56
+ //#region src/acp/codex-compat.ts
57
+ function isCodexInvocation(agentName, agentCommand) {
58
+ if (agentName === "codex") return true;
59
+ return /\bcodex-acp\b/u.test(agentCommand);
20
60
  }
21
- function parseHeaders(value, path) {
22
- if (value == null) return [];
23
- if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
24
- const headers = [];
25
- for (const [index, rawHeader] of value.entries()) {
26
- const headerRecord = asRecord$1(rawHeader);
27
- if (!headerRecord) throw new Error(`Invalid ${path}[${index}]: expected object`);
28
- const name = parseNonEmptyString(headerRecord.name, `${path}[${index}].name`);
29
- const headerValue = parseNonEmptyString(headerRecord.value, `${path}[${index}].value`);
30
- headers.push({
31
- name,
32
- value: headerValue
33
- });
61
+ //#endregion
62
+ //#region src/cli/command-handlers.ts
63
+ var NoSessionError = class extends Error {
64
+ constructor(message) {
65
+ super(message);
66
+ this.name = "NoSessionError";
34
67
  }
35
- return headers;
68
+ };
69
+ let sessionModulePromise;
70
+ let outputModulePromise;
71
+ let outputRenderModulePromise;
72
+ function loadSessionModule() {
73
+ sessionModulePromise ??= import("./session-BbN0SBgf.js").then((n) => n.t);
74
+ return sessionModulePromise;
36
75
  }
37
- function parseArgs(value, path) {
38
- if (value == null) return [];
39
- if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
40
- const args = [];
41
- for (const [index, rawArg] of value.entries()) {
42
- if (typeof rawArg !== "string") throw new Error(`Invalid ${path}[${index}]: expected string`);
43
- args.push(rawArg);
44
- }
45
- return args;
76
+ function loadOutputModule() {
77
+ outputModulePromise ??= import("./output-Di0M9Et8.js").then((n) => n.n);
78
+ return outputModulePromise;
46
79
  }
47
- function parseEnv(value, path) {
48
- if (value == null) return [];
49
- if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
50
- const env = [];
51
- for (const [index, rawEntry] of value.entries()) {
52
- const entry = asRecord$1(rawEntry);
53
- if (!entry) throw new Error(`Invalid ${path}[${index}]: expected object`);
54
- const name = parseNonEmptyString(entry.name, `${path}[${index}].name`);
55
- const envValue = parseNonEmptyString(entry.value, `${path}[${index}].value`);
56
- env.push({
57
- name,
58
- value: envValue
59
- });
60
- }
61
- return env;
80
+ function loadOutputRenderModule() {
81
+ outputRenderModulePromise ??= import("./render-BL5ynRkN.js").then((n) => n.r);
82
+ return outputRenderModulePromise;
62
83
  }
63
- function parseMeta(value, path) {
64
- if (value === void 0) return;
65
- if (value === null) return null;
66
- if (!asRecord$1(value)) throw new Error(`Invalid ${path}: expected object or null`);
67
- return value;
84
+ async function readPromptInputFromStdin() {
85
+ let data = "";
86
+ for await (const chunk of process.stdin) data += String(chunk);
87
+ return data;
68
88
  }
69
- function parseServer(rawServer, path) {
70
- const serverRecord = asRecord$1(rawServer);
71
- if (!serverRecord) throw new Error(`Invalid ${path}: expected object`);
72
- const name = parseNonEmptyString(serverRecord.name, `${path}.name`);
73
- const _meta = parseMeta(serverRecord._meta, `${path}._meta`);
74
- const rawType = serverRecord.type;
75
- let typeValue;
76
- if (rawType === void 0) typeValue = "stdio";
77
- else {
78
- const parsedType = parseNonEmptyString(rawType, `${path}.type`);
79
- if (parsedType !== "http" && parsedType !== "sse" && parsedType !== "stdio") throw new Error(`Invalid ${path}.type: expected http, sse, or stdio`);
80
- typeValue = parsedType;
81
- }
82
- if (typeValue === "http" || typeValue === "sse") {
83
- const url = parseNonEmptyString(serverRecord.url, `${path}.url`);
84
- const headers = parseHeaders(serverRecord.headers, `${path}.headers`);
85
- return {
86
- type: typeValue,
87
- name,
88
- url,
89
- headers,
90
- _meta
91
- };
89
+ async function readPrompt(promptParts, filePath, cwd) {
90
+ try {
91
+ if (filePath) {
92
+ const prompt = mergePromptSourceWithText(filePath === "-" ? await readPromptInputFromStdin() : await fs$1.readFile(path.resolve(cwd, filePath), "utf8"), promptParts.join(" "));
93
+ if (prompt.length === 0) throw new InvalidArgumentError("Prompt from --file is empty");
94
+ return prompt;
95
+ }
96
+ const joined = promptParts.join(" ").trim();
97
+ if (joined.length > 0) return textPrompt(joined);
98
+ if (process.stdin.isTTY) throw new InvalidArgumentError("Prompt is required (pass as argument, --file, or pipe via stdin)");
99
+ const prompt = parsePromptSource(await readPromptInputFromStdin());
100
+ if (prompt.length === 0) throw new InvalidArgumentError("Prompt from stdin is empty");
101
+ return prompt;
102
+ } catch (error) {
103
+ if (error instanceof PromptInputValidationError) throw new InvalidArgumentError(error.message);
104
+ throw error;
92
105
  }
93
- if (typeValue === "stdio") return {
94
- name,
95
- command: parseNonEmptyString(serverRecord.command, `${path}.command`),
96
- args: parseArgs(serverRecord.args, `${path}.args`),
97
- env: parseEnv(serverRecord.env, `${path}.env`),
98
- _meta
99
- };
100
- throw new Error(`Invalid ${path}.type: expected http, sse, or stdio`);
101
- }
102
- function parseMcpServers(value, sourcePath, fieldName = "mcpServers") {
103
- const fieldPath = `${fieldName} in ${sourcePath}`;
104
- if (!Array.isArray(value)) throw new Error(`Invalid ${fieldPath}: expected array`);
105
- const parsed = [];
106
- for (const [index, rawServer] of value.entries()) parsed.push(parseServer(rawServer, `${fieldName}[${index}] in ${sourcePath}`));
107
- return parsed;
108
106
  }
109
- function parseOptionalMcpServers(value, sourcePath, fieldName = "mcpServers") {
110
- if (value === void 0) return;
111
- return parseMcpServers(value, sourcePath, fieldName);
107
+ function applyPermissionExitCode(result) {
108
+ const stats = result.permissionStats;
109
+ const deniedOrCancelled = stats.denied + stats.cancelled;
110
+ if (stats.requested > 0 && stats.approved === 0 && deniedOrCancelled > 0) process.exitCode = EXIT_CODES.PERMISSION_DENIED;
112
111
  }
113
- //#endregion
114
- //#region src/config.ts
115
- const DEFAULT_TIMEOUT_MS = void 0;
116
- const DEFAULT_TTL_MS = 3e5;
117
- const DEFAULT_PERMISSION_MODE = "approve-reads";
118
- const DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY = "deny";
119
- const DEFAULT_AUTH_POLICY = "skip";
120
- const DEFAULT_OUTPUT_FORMAT = "text";
121
- const DEFAULT_QUEUE_MAX_DEPTH = 16;
122
- const DEFAULT_DISABLE_EXEC = false;
123
- const VALID_PERMISSION_MODES = new Set([
124
- "approve-all",
125
- "approve-reads",
126
- "deny-all"
127
- ]);
128
- const VALID_NON_INTERACTIVE_PERMISSION_POLICIES = new Set(["deny", "fail"]);
129
- const VALID_AUTH_POLICIES = new Set(["skip", "fail"]);
130
- const VALID_OUTPUT_FORMATS = new Set([
131
- "text",
132
- "json",
133
- "quiet"
134
- ]);
135
- function defaultGlobalConfigPath() {
136
- return path.join(os.homedir(), ".acpx", "config.json");
112
+ function resolveCompatibleConfigId(agent, configId) {
113
+ if (isCodexInvocation(agent.agentName, agent.agentCommand) && configId === "thought_level") return "reasoning_effort";
114
+ return configId;
137
115
  }
138
- function projectConfigPath(cwd) {
139
- return path.join(path.resolve(cwd), ".acpxrc.json");
116
+ function resolveRequestedOutputPolicy(globalFlags) {
117
+ return {
118
+ ...resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true),
119
+ suppressReads: globalFlags.suppressReads === true
120
+ };
140
121
  }
141
- function isObject(value) {
142
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
122
+ async function findRoutedSessionOrThrow(agentCommand, agentName, cwd, sessionName) {
123
+ const walkBoundary = findGitRepositoryRoot(cwd) ?? cwd;
124
+ const record = await findSessionByDirectoryWalk({
125
+ agentCommand,
126
+ cwd,
127
+ name: sessionName,
128
+ boundary: walkBoundary
129
+ });
130
+ if (record) return record;
131
+ throw new NoSessionError(`⚠ No acpx session found (searched up to ${walkBoundary}).\nCreate one: ${sessionName ? `acpx ${agentName} sessions new --name ${sessionName}` : `acpx ${agentName} sessions new`}`);
143
132
  }
144
- function parseTtlMs(value, sourcePath) {
145
- if (value == null) return;
146
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`Invalid config ttl in ${sourcePath}: expected non-negative seconds`);
147
- return Math.round(value * 1e3);
133
+ async function handlePrompt(explicitAgentName, promptParts, flags, command, config) {
134
+ const globalFlags = resolveGlobalFlags(command, config);
135
+ const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
136
+ const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
137
+ const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
138
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
139
+ const [{ createOutputFormatter }, { printPromptSessionBanner, printQueuedPromptByFormat }, { sendSession }] = await Promise.all([
140
+ loadOutputModule(),
141
+ loadOutputRenderModule(),
142
+ loadSessionModule()
143
+ ]);
144
+ const record = await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, flags.session);
145
+ const outputFormatter = createOutputFormatter(outputPolicy.format, {
146
+ jsonContext: { sessionId: record.acpxRecordId },
147
+ suppressReads: outputPolicy.suppressReads
148
+ });
149
+ await printPromptSessionBanner(record, agent.cwd, outputPolicy.format, outputPolicy.jsonStrict);
150
+ const result = await sendSession({
151
+ sessionId: record.acpxRecordId,
152
+ prompt,
153
+ mcpServers: config.mcpServers,
154
+ permissionMode,
155
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
156
+ authCredentials: config.auth,
157
+ authPolicy: globalFlags.authPolicy,
158
+ outputFormatter,
159
+ errorEmissionPolicy: { queueErrorAlreadyEmitted: outputPolicy.queueErrorAlreadyEmitted },
160
+ suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
161
+ timeoutMs: globalFlags.timeout,
162
+ ttlMs: globalFlags.ttl,
163
+ maxQueueDepth: config.queueMaxDepth,
164
+ promptRetries: globalFlags.promptRetries,
165
+ verbose: globalFlags.verbose,
166
+ waitForCompletion: flags.wait !== false
167
+ });
168
+ if ("queued" in result) {
169
+ printQueuedPromptByFormat(result, outputPolicy.format);
170
+ return;
171
+ }
172
+ applyPermissionExitCode(result);
173
+ if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
148
174
  }
149
- function parseTimeoutMs(value, sourcePath) {
150
- if (value == null) return;
151
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new Error(`Invalid config timeout in ${sourcePath}: expected positive seconds or null`);
152
- return Math.round(value * 1e3);
153
- }
154
- function parseQueueMaxDepth(value, sourcePath) {
155
- if (value == null) return;
156
- if (!Number.isInteger(value) || value <= 0) throw new Error(`Invalid config queueMaxDepth in ${sourcePath}: expected positive integer`);
157
- return value;
158
- }
159
- function parsePermissionMode(value, sourcePath) {
160
- if (value == null) return;
161
- if (typeof value !== "string" || !VALID_PERMISSION_MODES.has(value)) throw new Error(`Invalid config defaultPermissions in ${sourcePath}: expected approve-all, approve-reads, or deny-all`);
162
- return value;
175
+ async function handleExec(explicitAgentName, promptParts, flags, command, config) {
176
+ if (config.disableExec) {
177
+ if (resolveRequestedOutputPolicy(resolveGlobalFlags(command, config)).format === "json") process.stdout.write(`${JSON.stringify({
178
+ jsonrpc: "2.0",
179
+ error: {
180
+ code: -32603,
181
+ message: "exec subcommand is disabled by configuration (disableExec: true)",
182
+ data: { acpxCode: "EXEC_DISABLED" }
183
+ }
184
+ })}\n`);
185
+ else process.stderr.write("Error: exec subcommand is disabled by configuration (disableExec: true)\n");
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ const globalFlags = resolveGlobalFlags(command, config);
190
+ const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
191
+ const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
192
+ const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
193
+ const [{ createOutputFormatter }, { runOnce }] = await Promise.all([loadOutputModule(), loadSessionModule()]);
194
+ const outputFormatter = createOutputFormatter(outputPolicy.format, { suppressReads: outputPolicy.suppressReads });
195
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
196
+ applyPermissionExitCode(await runOnce({
197
+ agentCommand: agent.agentCommand,
198
+ cwd: agent.cwd,
199
+ prompt,
200
+ mcpServers: config.mcpServers,
201
+ permissionMode,
202
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
203
+ authCredentials: config.auth,
204
+ authPolicy: globalFlags.authPolicy,
205
+ outputFormatter,
206
+ suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
207
+ timeoutMs: globalFlags.timeout,
208
+ verbose: globalFlags.verbose,
209
+ promptRetries: globalFlags.promptRetries,
210
+ sessionOptions: {
211
+ model: globalFlags.model,
212
+ allowedTools: globalFlags.allowedTools,
213
+ maxTurns: globalFlags.maxTurns
214
+ }
215
+ }));
163
216
  }
164
- function parseNonInteractivePermissionPolicy(value, sourcePath) {
165
- if (value == null) return;
166
- if (typeof value !== "string" || !VALID_NON_INTERACTIVE_PERMISSION_POLICIES.has(value)) throw new Error(`Invalid config nonInteractivePermissions in ${sourcePath}: expected deny or fail`);
167
- return value;
217
+ function printCancelResultByFormat(result, format) {
218
+ if (emitJsonResult(format, {
219
+ action: "cancel_result",
220
+ acpxRecordId: result.sessionId || "unknown",
221
+ cancelled: result.cancelled
222
+ })) return;
223
+ process.stdout.write(result.cancelled ? "cancel requested\n" : "nothing to cancel\n");
168
224
  }
169
- function parseAuthPolicy(value, sourcePath) {
170
- if (value == null) return;
171
- if (typeof value !== "string" || !VALID_AUTH_POLICIES.has(value)) throw new Error(`Invalid config authPolicy in ${sourcePath}: expected skip or fail`);
172
- return value;
225
+ function printSetModeResultByFormat(modeId, result, format) {
226
+ if (emitJsonResult(format, {
227
+ action: "mode_set",
228
+ modeId,
229
+ resumed: result.resumed,
230
+ acpxRecordId: result.record.acpxRecordId,
231
+ acpxSessionId: result.record.acpSessionId,
232
+ agentSessionId: result.record.agentSessionId
233
+ })) return;
234
+ process.stdout.write(format === "quiet" ? `${modeId}\n` : `mode set: ${modeId}\n`);
173
235
  }
174
- function parseOutputFormat(value, sourcePath) {
175
- if (value == null) return;
176
- if (typeof value !== "string" || !VALID_OUTPUT_FORMATS.has(value)) throw new Error(`Invalid config format in ${sourcePath}: expected text, json, or quiet`);
177
- return value;
236
+ function printSetModelResultByFormat(modelId, result, format) {
237
+ if (emitJsonResult(format, {
238
+ action: "model_set",
239
+ modelId,
240
+ resumed: result.resumed,
241
+ acpxRecordId: result.record.acpxRecordId,
242
+ acpxSessionId: result.record.acpSessionId,
243
+ agentSessionId: result.record.agentSessionId
244
+ })) return;
245
+ process.stdout.write(format === "quiet" ? `${modelId}\n` : `model set: ${modelId}\n`);
178
246
  }
179
- function parseDefaultAgent(value, sourcePath) {
180
- if (value == null) return;
181
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid config defaultAgent in ${sourcePath}: expected non-empty string`);
182
- return normalizeAgentName(value);
247
+ function printSetConfigOptionResultByFormat(configId, value, result, format) {
248
+ if (emitJsonResult(format, {
249
+ action: "config_set",
250
+ configId,
251
+ value,
252
+ resumed: result.resumed,
253
+ configOptions: result.response.configOptions,
254
+ acpxRecordId: result.record.acpxRecordId,
255
+ acpxSessionId: result.record.acpSessionId,
256
+ agentSessionId: result.record.agentSessionId
257
+ })) return;
258
+ process.stdout.write(format === "quiet" ? `${value}\n` : `config set: ${configId}=${value} (${result.response.configOptions.length} options)\n`);
183
259
  }
184
- function parseAgents(value, sourcePath) {
185
- if (value == null) return;
186
- if (!isObject(value)) throw new Error(`Invalid config agents in ${sourcePath}: expected object`);
187
- const parsed = {};
188
- for (const [name, raw] of Object.entries(value)) {
189
- if (!isObject(raw)) throw new Error(`Invalid config agents.${name} in ${sourcePath}: expected object with command`);
190
- const command = raw.command;
191
- if (typeof command !== "string" || command.trim().length === 0) throw new Error(`Invalid config agents.${name}.command in ${sourcePath}: expected non-empty string`);
192
- parsed[normalizeAgentName(name)] = command.trim();
260
+ async function handleCancel(explicitAgentName, flags, command, config) {
261
+ const globalFlags = resolveGlobalFlags(command, config);
262
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
263
+ const { cancelSessionPrompt } = await loadSessionModule();
264
+ const walkBoundary = findGitRepositoryRoot(agent.cwd) ?? agent.cwd;
265
+ const record = await findSessionByDirectoryWalk({
266
+ agentCommand: agent.agentCommand,
267
+ cwd: agent.cwd,
268
+ name: resolveSessionNameFromFlags(flags, command),
269
+ boundary: walkBoundary
270
+ });
271
+ if (!record) {
272
+ printCancelResultByFormat({
273
+ sessionId: "",
274
+ cancelled: false
275
+ }, globalFlags.format);
276
+ return;
193
277
  }
194
- return parsed;
278
+ printCancelResultByFormat(await cancelSessionPrompt({
279
+ sessionId: record.acpxRecordId,
280
+ verbose: globalFlags.verbose
281
+ }), globalFlags.format);
195
282
  }
196
- function parseAuth(value, sourcePath) {
197
- if (value == null) return;
198
- if (!isObject(value)) throw new Error(`Invalid config auth in ${sourcePath}: expected object`);
199
- const parsed = {};
200
- for (const [methodId, rawCredential] of Object.entries(value)) {
201
- if (typeof rawCredential !== "string" || rawCredential.trim().length === 0) throw new Error(`Invalid config auth.${methodId} in ${sourcePath}: expected non-empty string`);
202
- parsed[methodId] = rawCredential;
203
- }
204
- return parsed;
283
+ async function handleSetMode(explicitAgentName, modeId, flags, command, config) {
284
+ const globalFlags = resolveGlobalFlags(command, config);
285
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
286
+ const { setSessionMode } = await loadSessionModule();
287
+ const result = await setSessionMode({
288
+ sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
289
+ modeId,
290
+ mcpServers: config.mcpServers,
291
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
292
+ authCredentials: config.auth,
293
+ authPolicy: globalFlags.authPolicy,
294
+ timeoutMs: globalFlags.timeout,
295
+ verbose: globalFlags.verbose
296
+ });
297
+ if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
298
+ printSetModeResultByFormat(modeId, result, globalFlags.format);
205
299
  }
206
- function parseDisableExec(value, sourcePath) {
207
- if (value == null) return;
208
- if (typeof value !== "boolean") throw new Error(`Invalid config disableExec in ${sourcePath}: expected boolean`);
209
- return value;
300
+ async function handleSetModel(explicitAgentName, modelId, flags, command, config) {
301
+ const globalFlags = resolveGlobalFlags(command, config);
302
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
303
+ const { setSessionModel } = await loadSessionModule();
304
+ const result = await setSessionModel({
305
+ sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
306
+ modelId,
307
+ mcpServers: config.mcpServers,
308
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
309
+ authCredentials: config.auth,
310
+ authPolicy: globalFlags.authPolicy,
311
+ timeoutMs: globalFlags.timeout,
312
+ verbose: globalFlags.verbose
313
+ });
314
+ if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
315
+ printSetModelResultByFormat(modelId, result, globalFlags.format);
210
316
  }
211
- async function readConfigFile(filePath) {
212
- try {
213
- const payload = await fs$1.readFile(filePath, "utf8");
214
- let parsed;
215
- try {
216
- parsed = JSON.parse(payload);
217
- } catch (error) {
218
- const reason = error instanceof Error ? error.message : String(error);
219
- throw new Error(`Invalid JSON in ${filePath}: ${reason}`, { cause: error });
220
- }
221
- if (!isObject(parsed)) throw new Error(`Invalid config in ${filePath}: expected top-level JSON object`);
222
- return {
223
- config: parsed,
224
- exists: true
225
- };
226
- } catch (error) {
227
- if (error.code === "ENOENT") return { exists: false };
228
- throw error;
317
+ async function handleSetConfigOption(explicitAgentName, configId, value, flags, command, config) {
318
+ const globalFlags = resolveGlobalFlags(command, config);
319
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
320
+ if (configId === "model") {
321
+ await handleSetModel(explicitAgentName, value, flags, command, config);
322
+ return;
229
323
  }
324
+ const resolvedConfigId = resolveCompatibleConfigId(agent, configId);
325
+ const { setSessionConfigOption } = await loadSessionModule();
326
+ const result = await setSessionConfigOption({
327
+ sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
328
+ configId: resolvedConfigId,
329
+ value,
330
+ mcpServers: config.mcpServers,
331
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
332
+ authCredentials: config.auth,
333
+ authPolicy: globalFlags.authPolicy,
334
+ timeoutMs: globalFlags.timeout,
335
+ verbose: globalFlags.verbose
336
+ });
337
+ if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
338
+ printSetConfigOptionResultByFormat(configId, value, result, globalFlags.format);
230
339
  }
231
- function mergeAgents(globalAgents, projectAgents) {
232
- return {
233
- ...globalAgents,
234
- ...projectAgents
235
- };
340
+ async function handleSessionsList(explicitAgentName, command, config) {
341
+ const globalFlags = resolveGlobalFlags(command, config);
342
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
343
+ const [{ listSessionsForAgent }, { printSessionsByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
344
+ printSessionsByFormat(await listSessionsForAgent(agent.agentCommand), globalFlags.format);
236
345
  }
237
- function mergeAuth(globalAuth, projectAuth) {
238
- return {
239
- ...globalAuth,
240
- ...projectAuth
241
- };
346
+ async function handleSessionsClose(explicitAgentName, sessionName, command, config) {
347
+ const globalFlags = resolveGlobalFlags(command, config);
348
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
349
+ const [{ closeSession }, { printClosedSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
350
+ const record = await findSession({
351
+ agentCommand: agent.agentCommand,
352
+ cwd: agent.cwd,
353
+ name: sessionName
354
+ });
355
+ if (!record) throw new Error(sessionName ? `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}` : `No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
356
+ printClosedSessionByFormat(await closeSession(record.acpxRecordId), globalFlags.format);
242
357
  }
243
- async function loadResolvedConfig(cwd) {
244
- const globalPath = defaultGlobalConfigPath();
245
- const projectPath = projectConfigPath(cwd);
246
- const [globalResult, projectResult] = await Promise.all([readConfigFile(globalPath), readConfigFile(projectPath)]);
247
- const globalConfig = globalResult.config;
248
- const projectConfig = projectResult.config;
249
- const defaultAgent = parseDefaultAgent(projectConfig?.defaultAgent, projectPath) ?? parseDefaultAgent(globalConfig?.defaultAgent, globalPath) ?? "codex";
250
- const defaultPermissions = parsePermissionMode(projectConfig?.defaultPermissions, projectPath) ?? parsePermissionMode(globalConfig?.defaultPermissions, globalPath) ?? DEFAULT_PERMISSION_MODE;
251
- const nonInteractivePermissions = parseNonInteractivePermissionPolicy(projectConfig?.nonInteractivePermissions, projectPath) ?? parseNonInteractivePermissionPolicy(globalConfig?.nonInteractivePermissions, globalPath) ?? DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY;
252
- const authPolicy = parseAuthPolicy(projectConfig?.authPolicy, projectPath) ?? parseAuthPolicy(globalConfig?.authPolicy, globalPath) ?? DEFAULT_AUTH_POLICY;
253
- const ttlMs = parseTtlMs(projectConfig?.ttl, projectPath) ?? parseTtlMs(globalConfig?.ttl, globalPath) ?? DEFAULT_TTL_MS;
254
- const timeoutConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "timeout");
255
- const timeoutConfiguredInGlobal = globalConfig != null && Object.prototype.hasOwnProperty.call(globalConfig, "timeout");
256
- let timeoutMs = DEFAULT_TIMEOUT_MS;
257
- if (timeoutConfiguredInProject) timeoutMs = parseTimeoutMs(projectConfig?.timeout, projectPath);
258
- else if (timeoutConfiguredInGlobal) timeoutMs = parseTimeoutMs(globalConfig?.timeout, globalPath);
259
- const format = parseOutputFormat(projectConfig?.format, projectPath) ?? parseOutputFormat(globalConfig?.format, globalPath) ?? DEFAULT_OUTPUT_FORMAT;
260
- const queueMaxDepth = parseQueueMaxDepth(projectConfig?.queueMaxDepth, projectPath) ?? parseQueueMaxDepth(globalConfig?.queueMaxDepth, globalPath) ?? DEFAULT_QUEUE_MAX_DEPTH;
261
- const agents = mergeAgents(parseAgents(globalConfig?.agents, globalPath), parseAgents(projectConfig?.agents, projectPath));
262
- const auth = mergeAuth(parseAuth(globalConfig?.auth, globalPath), parseAuth(projectConfig?.auth, projectPath));
263
- const mcpServersConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "mcpServers");
264
- const mcpServersConfiguredInGlobal = globalConfig != null && Object.prototype.hasOwnProperty.call(globalConfig, "mcpServers");
265
- let mcpServers = [];
266
- if (mcpServersConfiguredInProject) mcpServers = parseMcpServers(projectConfig?.mcpServers, projectPath);
267
- else if (mcpServersConfiguredInGlobal) mcpServers = parseMcpServers(globalConfig?.mcpServers, globalPath);
268
- const disableExec = parseDisableExec(projectConfig?.disableExec, projectPath) ?? parseDisableExec(globalConfig?.disableExec, globalPath) ?? DEFAULT_DISABLE_EXEC;
269
- return {
270
- defaultAgent,
271
- defaultPermissions,
272
- nonInteractivePermissions,
273
- authPolicy,
274
- ttlMs,
275
- timeoutMs,
276
- queueMaxDepth,
277
- format,
278
- agents,
279
- auth,
280
- disableExec,
281
- mcpServers,
282
- globalPath,
283
- projectPath,
284
- hasGlobalConfig: globalResult.exists,
285
- hasProjectConfig: projectResult.exists
286
- };
358
+ async function handleSessionsNew(explicitAgentName, flags, command, config) {
359
+ const globalFlags = resolveGlobalFlags(command, config);
360
+ const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
361
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
362
+ const [{ createSession, closeSession }, { printCreatedSessionBanner, printNewSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
363
+ const replaced = await findSession({
364
+ agentCommand: agent.agentCommand,
365
+ cwd: agent.cwd,
366
+ name: flags.name
367
+ });
368
+ if (replaced) {
369
+ await closeSession(replaced.acpxRecordId);
370
+ if (globalFlags.verbose) process.stderr.write(`[acpx] soft-closed prior session: ${replaced.acpxRecordId}\n`);
371
+ }
372
+ const created = await createSession({
373
+ agentCommand: agent.agentCommand,
374
+ cwd: agent.cwd,
375
+ name: flags.name,
376
+ resumeSessionId: flags.resumeSession,
377
+ mcpServers: config.mcpServers,
378
+ permissionMode,
379
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
380
+ authCredentials: config.auth,
381
+ authPolicy: globalFlags.authPolicy,
382
+ timeoutMs: globalFlags.timeout,
383
+ verbose: globalFlags.verbose,
384
+ sessionOptions: {
385
+ model: globalFlags.model,
386
+ allowedTools: globalFlags.allowedTools,
387
+ maxTurns: globalFlags.maxTurns
388
+ }
389
+ });
390
+ printCreatedSessionBanner(created, agent.agentName, globalFlags.format, globalFlags.jsonStrict);
391
+ if (globalFlags.verbose) {
392
+ const scope = flags.name ? `named session "${flags.name}"` : "cwd session";
393
+ process.stderr.write(`[acpx] created ${scope}: ${created.acpxRecordId}\n`);
394
+ }
395
+ printNewSessionByFormat(created, replaced, globalFlags.format);
287
396
  }
288
- function toConfigDisplay(config) {
289
- const agents = {};
290
- for (const [name, command] of Object.entries(config.agents)) agents[name] = { command };
291
- return {
292
- defaultAgent: config.defaultAgent,
293
- defaultPermissions: config.defaultPermissions,
294
- nonInteractivePermissions: config.nonInteractivePermissions,
295
- authPolicy: config.authPolicy,
296
- ttl: Math.round(config.ttlMs / 1e3),
297
- timeout: config.timeoutMs == null ? null : config.timeoutMs / 1e3,
298
- queueMaxDepth: config.queueMaxDepth,
299
- format: config.format,
300
- agents,
301
- authMethods: Object.keys(config.auth).toSorted(),
302
- disableExec: config.disableExec
303
- };
397
+ async function handleSessionsEnsure(explicitAgentName, flags, command, config) {
398
+ const globalFlags = resolveGlobalFlags(command, config);
399
+ const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
400
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
401
+ const [{ ensureSession }, { printCreatedSessionBanner, printEnsuredSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
402
+ const result = await ensureSession({
403
+ agentCommand: agent.agentCommand,
404
+ cwd: agent.cwd,
405
+ name: flags.name,
406
+ resumeSessionId: flags.resumeSession,
407
+ mcpServers: config.mcpServers,
408
+ permissionMode,
409
+ nonInteractivePermissions: globalFlags.nonInteractivePermissions,
410
+ authCredentials: config.auth,
411
+ authPolicy: globalFlags.authPolicy,
412
+ timeoutMs: globalFlags.timeout,
413
+ verbose: globalFlags.verbose,
414
+ sessionOptions: {
415
+ model: globalFlags.model,
416
+ allowedTools: globalFlags.allowedTools,
417
+ maxTurns: globalFlags.maxTurns
418
+ }
419
+ });
420
+ if (result.created) printCreatedSessionBanner(result.record, agent.agentName, globalFlags.format, globalFlags.jsonStrict);
421
+ printEnsuredSessionByFormat(result.record, result.created, globalFlags.format);
304
422
  }
305
- async function initGlobalConfigFile() {
306
- const configPath = defaultGlobalConfigPath();
307
- await fs$1.mkdir(path.dirname(configPath), { recursive: true });
308
- try {
309
- await fs$1.access(configPath);
310
- return {
311
- path: configPath,
312
- created: false
313
- };
314
- } catch {}
315
- const payload = {
316
- defaultAgent: DEFAULT_AGENT_NAME,
317
- defaultPermissions: "approve-all",
318
- nonInteractivePermissions: "deny",
319
- authPolicy: "skip",
320
- ttl: 300,
321
- timeout: null,
322
- queueMaxDepth: DEFAULT_QUEUE_MAX_DEPTH,
323
- format: "text",
324
- agents: {},
325
- auth: {}
326
- };
327
- await fs$1.writeFile(configPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
328
- return {
329
- path: configPath,
330
- created: true
331
- };
423
+ function userContentToText(content) {
424
+ if ("Text" in content) return content.Text;
425
+ if ("Mention" in content) return content.Mention.content;
426
+ if ("Image" in content) return content.Image.source || "[image]";
427
+ return "";
332
428
  }
333
- //#endregion
334
- //#region src/cli/config-command.ts
335
- async function handleConfigShow(command, config) {
336
- const globalFlags = resolveGlobalFlags(command, config);
337
- const payload = {
338
- ...toConfigDisplay(config),
339
- paths: {
340
- global: config.globalPath,
341
- project: config.projectPath
342
- },
343
- loaded: {
344
- global: config.hasGlobalConfig,
345
- project: config.hasProjectConfig
429
+ function agentContentToText(content) {
430
+ if ("Text" in content) return content.Text;
431
+ if ("Thinking" in content) return content.Thinking.text;
432
+ if ("RedactedThinking" in content) return "[redacted_thinking]";
433
+ if ("ToolUse" in content) return `[tool:${content.ToolUse.name}]`;
434
+ return "";
435
+ }
436
+ function conversationHistoryEntries(record) {
437
+ const entries = [];
438
+ for (const message of record.messages) {
439
+ if (message === "Resume") continue;
440
+ if ("User" in message) {
441
+ const text = message.User.content.map((entry) => userContentToText(entry)).join(" ").trim();
442
+ if (!text) continue;
443
+ entries.push({
444
+ role: "user",
445
+ timestamp: record.updated_at,
446
+ textPreview: text
447
+ });
448
+ continue;
346
449
  }
347
- };
348
- if (globalFlags.format === "json") {
349
- process.stdout.write(`${JSON.stringify(payload)}\n`);
450
+ if ("Agent" in message) {
451
+ const text = message.Agent.content.map((entry) => agentContentToText(entry)).join(" ").trim();
452
+ if (!text) continue;
453
+ entries.push({
454
+ role: "assistant",
455
+ timestamp: record.updated_at,
456
+ textPreview: text
457
+ });
458
+ }
459
+ }
460
+ return entries;
461
+ }
462
+ function printSessionDetailsByFormat(record, format) {
463
+ if (format === "json") {
464
+ process.stdout.write(`${JSON.stringify(record)}\n`);
350
465
  return;
351
466
  }
352
- process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
467
+ if (format === "quiet") {
468
+ process.stdout.write(`${record.acpxRecordId}\n`);
469
+ return;
470
+ }
471
+ process.stdout.write(`id: ${record.acpxRecordId}\n`);
472
+ process.stdout.write(`sessionId: ${record.acpSessionId}\n`);
473
+ process.stdout.write(`agentSessionId: ${record.agentSessionId ?? "-"}\n`);
474
+ process.stdout.write(`agent: ${record.agentCommand}\n`);
475
+ process.stdout.write(`cwd: ${record.cwd}\n`);
476
+ process.stdout.write(`name: ${record.name ?? "-"}\n`);
477
+ process.stdout.write(`created: ${record.createdAt}\n`);
478
+ process.stdout.write(`lastActivity: ${record.lastUsedAt}\n`);
479
+ process.stdout.write(`lastPrompt: ${record.lastPromptAt ?? "-"}\n`);
480
+ process.stdout.write(`closed: ${record.closed ? "yes" : "no"}\n`);
481
+ process.stdout.write(`closedAt: ${record.closedAt ?? "-"}\n`);
482
+ process.stdout.write(`pid: ${record.pid ?? "-"}\n`);
483
+ process.stdout.write(`agentStartedAt: ${record.agentStartedAt ?? "-"}\n`);
484
+ process.stdout.write(`lastExitCode: ${record.lastAgentExitCode ?? "-"}\n`);
485
+ process.stdout.write(`lastExitSignal: ${record.lastAgentExitSignal ?? "-"}\n`);
486
+ process.stdout.write(`lastExitAt: ${record.lastAgentExitAt ?? "-"}\n`);
487
+ process.stdout.write(`disconnectReason: ${record.lastAgentDisconnectReason ?? "-"}\n`);
488
+ process.stdout.write(`historyEntries: ${conversationHistoryEntries(record).length}\n`);
353
489
  }
354
- async function handleConfigInit(command, config) {
355
- const globalFlags = resolveGlobalFlags(command, config);
356
- const result = await initGlobalConfigFile();
357
- if (globalFlags.format === "json") {
490
+ function printSessionHistoryByFormat(record, limit, format) {
491
+ const history = conversationHistoryEntries(record);
492
+ const visible = limit === 0 ? history : history.slice(Math.max(0, history.length - limit));
493
+ if (format === "json") {
358
494
  process.stdout.write(`${JSON.stringify({
359
- path: result.path,
360
- created: result.created
495
+ id: record.acpxRecordId,
496
+ sessionId: record.acpSessionId,
497
+ limit,
498
+ count: visible.length,
499
+ entries: visible
361
500
  })}\n`);
362
501
  return;
363
502
  }
364
- if (globalFlags.format === "quiet") {
365
- process.stdout.write(`${result.path}\n`);
503
+ if (format === "quiet") {
504
+ for (const entry of visible) process.stdout.write(`${entry.textPreview}\n`);
366
505
  return;
367
506
  }
368
- if (result.created) {
369
- process.stdout.write(`Created ${result.path}\n`);
507
+ process.stdout.write(`session: ${record.acpxRecordId} (${visible.length}/${history.length} shown)\n`);
508
+ if (visible.length === 0) {
509
+ process.stdout.write("No history\n");
370
510
  return;
371
511
  }
372
- process.stdout.write(`Config already exists: ${result.path}\n`);
512
+ for (const entry of visible) process.stdout.write(`${entry.timestamp}\t${entry.role}\t${entry.textPreview}\n`);
373
513
  }
374
- function registerConfigCommand(program, config) {
375
- const configCommand = program.command("config").description("Inspect and initialize acpx configuration");
376
- configCommand.command("show").description("Show resolved config").action(async function() {
377
- await handleConfigShow(this, config);
514
+ async function handleSessionsShow(explicitAgentName, sessionName, command, config) {
515
+ const globalFlags = resolveGlobalFlags(command, config);
516
+ const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
517
+ const record = await findSession({
518
+ agentCommand: agent.agentCommand,
519
+ cwd: agent.cwd,
520
+ name: sessionName,
521
+ includeClosed: true
378
522
  });
379
- configCommand.command("init").description("Create global config template").action(async function() {
380
- await handleConfigInit(this, config);
381
- });
382
- configCommand.action(async function() {
383
- await handleConfigShow(this, config);
384
- });
385
- }
386
- //#endregion
387
- //#region src/cli/status-command.ts
388
- function formatUptime(startedAt) {
389
- if (!startedAt) return;
390
- const startedMs = Date.parse(startedAt);
391
- if (!Number.isFinite(startedMs)) return;
392
- const elapsedMs = Math.max(0, Date.now() - startedMs);
393
- const seconds = Math.floor(elapsedMs / 1e3);
394
- const hours = Math.floor(seconds / 3600);
395
- const minutes = Math.floor(seconds % 3600 / 60);
396
- const remSeconds = seconds % 60;
397
- return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${remSeconds.toString().padStart(2, "0")}`;
523
+ if (!record) throw new Error(sessionName ? `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}` : `No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
524
+ printSessionDetailsByFormat(record, globalFlags.format);
398
525
  }
399
- async function handleStatus(explicitAgentName, flags, command, config) {
526
+ async function handleSessionsHistory(explicitAgentName, sessionName, flags, command, config) {
400
527
  const globalFlags = resolveGlobalFlags(command, config);
401
528
  const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
402
529
  const record = await findSession({
403
530
  agentCommand: agent.agentCommand,
404
531
  cwd: agent.cwd,
405
- name: resolveSessionNameFromFlags(flags, command)
406
- });
407
- if (!record) {
408
- if (emitJsonResult(globalFlags.format, {
409
- action: "status_snapshot",
410
- status: "no-session",
411
- summary: "no active session"
412
- })) return;
413
- if (globalFlags.format === "quiet") {
414
- process.stdout.write("no-session\n");
415
- return;
416
- }
417
- process.stdout.write("session: -\n");
418
- process.stdout.write(`agent: ${agent.agentCommand}\n`);
419
- process.stdout.write("pid: -\n");
420
- process.stdout.write("status: no-session\n");
421
- process.stdout.write("model: -\n");
422
- process.stdout.write("mode: -\n");
423
- process.stdout.write("uptime: -\n");
424
- process.stdout.write("lastPromptTime: -\n");
425
- return;
426
- }
427
- const health = await probeQueueOwnerHealth(record.acpxRecordId);
428
- const running = health.healthy;
429
- const payload = {
430
- sessionId: record.acpxRecordId,
431
- agentCommand: record.agentCommand,
432
- pid: health.pid ?? record.pid ?? null,
433
- status: running ? "running" : "dead",
434
- model: record.acpx?.current_model_id ?? null,
435
- mode: record.acpx?.current_mode_id ?? null,
436
- availableModels: record.acpx?.available_models ?? null,
437
- uptime: running ? formatUptime(record.agentStartedAt) ?? null : null,
438
- lastPromptTime: record.lastPromptAt ?? null,
439
- exitCode: running ? null : record.lastAgentExitCode ?? null,
440
- signal: running ? null : record.lastAgentExitSignal ?? null,
441
- ...agentSessionIdPayload(record.agentSessionId)
442
- };
443
- if (emitJsonResult(globalFlags.format, {
444
- action: "status_snapshot",
445
- status: running ? "alive" : "dead",
446
- pid: payload.pid ?? void 0,
447
- summary: running ? "queue owner healthy" : "queue owner unavailable",
448
- model: payload.model ?? void 0,
449
- mode: payload.mode ?? void 0,
450
- availableModels: payload.availableModels ?? void 0,
451
- uptime: payload.uptime ?? void 0,
452
- lastPromptTime: payload.lastPromptTime ?? void 0,
453
- exitCode: payload.exitCode ?? void 0,
454
- signal: payload.signal ?? void 0,
455
- acpxRecordId: record.acpxRecordId,
456
- acpxSessionId: record.acpSessionId,
457
- agentSessionId: record.agentSessionId
458
- })) return;
459
- if (globalFlags.format === "quiet") {
460
- process.stdout.write(`${payload.status}\n`);
461
- return;
462
- }
463
- process.stdout.write(`session: ${payload.sessionId}\n`);
464
- if ("agentSessionId" in payload) process.stdout.write(`agentSessionId: ${payload.agentSessionId}\n`);
465
- process.stdout.write(`agent: ${payload.agentCommand}\n`);
466
- process.stdout.write(`pid: ${payload.pid ?? "-"}\n`);
467
- process.stdout.write(`status: ${payload.status}\n`);
468
- process.stdout.write(`model: ${payload.model ?? "-"}\n`);
469
- process.stdout.write(`mode: ${payload.mode ?? "-"}\n`);
470
- process.stdout.write(`uptime: ${payload.uptime ?? "-"}\n`);
471
- process.stdout.write(`lastPromptTime: ${payload.lastPromptTime ?? "-"}\n`);
472
- if (payload.status === "dead") {
473
- process.stdout.write(`exitCode: ${payload.exitCode ?? "-"}\n`);
474
- process.stdout.write(`signal: ${payload.signal ?? "-"}\n`);
475
- }
476
- }
477
- function registerStatusCommand(parent, explicitAgentName, config, description) {
478
- const statusCommand = parent.command("status").description(description);
479
- addSessionNameOption(statusCommand);
480
- statusCommand.action(async function(flags) {
481
- await handleStatus(explicitAgentName, flags, this, config);
532
+ name: sessionName,
533
+ includeClosed: true
482
534
  });
535
+ if (!record) throw new Error(sessionName ? `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}` : `No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
536
+ printSessionHistoryByFormat(record, flags.limit, globalFlags.format);
483
537
  }
484
538
  //#endregion
485
- //#region src/codex-compat.ts
486
- function isCodexInvocation(agentName, agentCommand) {
487
- if (agentName === "codex") return true;
488
- return /\bcodex-acp\b/u.test(agentCommand);
489
- }
490
- //#endregion
491
- //#region src/queue-owner-env.ts
492
- function asRecord(value) {
539
+ //#region src/mcp-servers.ts
540
+ function asRecord$1(value) {
493
541
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
494
542
  return value;
495
543
  }
496
- function parseQueueOwnerPayload(raw) {
497
- const record = asRecord(JSON.parse(raw));
498
- if (!record) throw new Error("queue owner payload must be an object");
499
- if (typeof record.sessionId !== "string" || record.sessionId.trim().length === 0) throw new Error("queue owner payload missing sessionId");
500
- if (record.permissionMode !== "approve-all" && record.permissionMode !== "approve-reads" && record.permissionMode !== "deny-all") throw new Error("queue owner payload has invalid permissionMode");
501
- const options = {
502
- sessionId: record.sessionId,
503
- permissionMode: record.permissionMode
504
- };
505
- const parsedMcpServers = parseOptionalMcpServers(record.mcpServers, "queue owner payload");
506
- if (parsedMcpServers) options.mcpServers = parsedMcpServers;
507
- if (typeof record.nonInteractivePermissions === "string") options.nonInteractivePermissions = record.nonInteractivePermissions === "deny" || record.nonInteractivePermissions === "fail" ? record.nonInteractivePermissions : void 0;
508
- if (record.authCredentials && typeof record.authCredentials === "object") {
509
- const entries = Object.entries(record.authCredentials).filter(([, value]) => typeof value === "string");
510
- options.authCredentials = Object.fromEntries(entries);
511
- }
512
- if (record.authPolicy === "skip" || record.authPolicy === "fail") options.authPolicy = record.authPolicy;
513
- if (typeof record.suppressSdkConsoleErrors === "boolean") options.suppressSdkConsoleErrors = record.suppressSdkConsoleErrors;
514
- if (typeof record.verbose === "boolean") options.verbose = record.verbose;
515
- if (typeof record.ttlMs === "number" && Number.isFinite(record.ttlMs)) options.ttlMs = record.ttlMs;
516
- if (typeof record.maxQueueDepth === "number" && Number.isFinite(record.maxQueueDepth)) options.maxQueueDepth = Math.max(1, Math.round(record.maxQueueDepth));
517
- if (typeof record.promptRetries === "number" && Number.isFinite(record.promptRetries)) options.promptRetries = Math.max(0, Math.round(record.promptRetries));
518
- return options;
519
- }
520
- async function runQueueOwnerFromEnv(env) {
521
- const payload = env.ACPX_QUEUE_OWNER_PAYLOAD;
522
- if (!payload) throw new Error("missing ACPX_QUEUE_OWNER_PAYLOAD");
523
- await runSessionQueueOwner(parseQueueOwnerPayload(payload));
524
- }
525
- //#endregion
526
- //#region src/version.ts
527
- const UNKNOWN_VERSION = "0.0.0-unknown";
528
- const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
529
- let cachedVersion = null;
530
- function parseVersion(value) {
531
- if (typeof value !== "string") return null;
532
- const trimmed = value.trim();
533
- return trimmed.length > 0 ? trimmed : null;
544
+ function parseNonEmptyString(value, path) {
545
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid ${path}: expected non-empty string`);
546
+ return value.trim();
534
547
  }
535
- function readPackageVersion(packageJsonPath) {
536
- try {
537
- return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
538
- } catch {
539
- return null;
548
+ function parseHeaders(value, path) {
549
+ if (value == null) return [];
550
+ if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
551
+ const headers = [];
552
+ for (const [index, rawHeader] of value.entries()) {
553
+ const headerRecord = asRecord$1(rawHeader);
554
+ if (!headerRecord) throw new Error(`Invalid ${path}[${index}]: expected object`);
555
+ const name = parseNonEmptyString(headerRecord.name, `${path}[${index}].name`);
556
+ const headerValue = parseNonEmptyString(headerRecord.value, `${path}[${index}].value`);
557
+ headers.push({
558
+ name,
559
+ value: headerValue
560
+ });
540
561
  }
562
+ return headers;
541
563
  }
542
- function resolveVersionFromAncestors(startDir) {
543
- let current = startDir;
544
- while (true) {
545
- const packageVersion = readPackageVersion(path.join(current, "package.json"));
546
- if (packageVersion) return packageVersion;
547
- const parent = path.dirname(current);
548
- if (parent === current) return null;
549
- current = parent;
564
+ function parseArgs(value, path) {
565
+ if (value == null) return [];
566
+ if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
567
+ const args = [];
568
+ for (const [index, rawArg] of value.entries()) {
569
+ if (typeof rawArg !== "string") throw new Error(`Invalid ${path}[${index}]: expected string`);
570
+ args.push(rawArg);
550
571
  }
572
+ return args;
551
573
  }
552
- function resolveAcpxVersion(params) {
553
- const env = params?.env ?? process.env;
554
- const envPackageName = parseVersion(env.npm_package_name);
555
- const envVersion = parseVersion(env.npm_package_version);
556
- if (envPackageName === "acpx" && envVersion) return envVersion;
557
- if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
558
- return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
574
+ function parseEnv(value, path) {
575
+ if (value == null) return [];
576
+ if (!Array.isArray(value)) throw new Error(`Invalid ${path}: expected array`);
577
+ const env = [];
578
+ for (const [index, rawEntry] of value.entries()) {
579
+ const entry = asRecord$1(rawEntry);
580
+ if (!entry) throw new Error(`Invalid ${path}[${index}]: expected object`);
581
+ const name = parseNonEmptyString(entry.name, `${path}[${index}].name`);
582
+ const envValue = parseNonEmptyString(entry.value, `${path}[${index}].value`);
583
+ env.push({
584
+ name,
585
+ value: envValue
586
+ });
587
+ }
588
+ return env;
559
589
  }
560
- function getAcpxVersion() {
561
- if (cachedVersion) return cachedVersion;
562
- cachedVersion = resolveAcpxVersion();
563
- return cachedVersion;
590
+ function parseMeta(value, path) {
591
+ if (value === void 0) return;
592
+ if (value === null) return null;
593
+ if (!asRecord$1(value)) throw new Error(`Invalid ${path}: expected object or null`);
594
+ return value;
564
595
  }
565
- //#endregion
566
- //#region src/cli-core.ts
567
- var NoSessionError = class extends Error {
568
- constructor(message) {
569
- super(message);
570
- this.name = "NoSessionError";
596
+ function parseServer(rawServer, path) {
597
+ const serverRecord = asRecord$1(rawServer);
598
+ if (!serverRecord) throw new Error(`Invalid ${path}: expected object`);
599
+ const name = parseNonEmptyString(serverRecord.name, `${path}.name`);
600
+ const _meta = parseMeta(serverRecord._meta, `${path}._meta`);
601
+ const rawType = serverRecord.type;
602
+ let typeValue;
603
+ if (rawType === void 0) typeValue = "stdio";
604
+ else {
605
+ const parsedType = parseNonEmptyString(rawType, `${path}.type`);
606
+ if (parsedType !== "http" && parsedType !== "sse" && parsedType !== "stdio") throw new Error(`Invalid ${path}.type: expected http, sse, or stdio`);
607
+ typeValue = parsedType;
571
608
  }
572
- };
573
- const TOP_LEVEL_VERBS = new Set([
574
- "prompt",
575
- "exec",
576
- "cancel",
577
- "flow",
578
- "set-mode",
579
- "set",
580
- "sessions",
581
- "status",
582
- "config",
583
- "help"
584
- ]);
585
- async function readPromptInputFromStdin() {
586
- let data = "";
587
- for await (const chunk of process.stdin) data += String(chunk);
588
- return data;
589
- }
590
- async function readPrompt(promptParts, filePath, cwd) {
591
- try {
592
- if (filePath) {
593
- const prompt = mergePromptSourceWithText(filePath === "-" ? await readPromptInputFromStdin() : await fs$1.readFile(path.resolve(cwd, filePath), "utf8"), promptParts.join(" "));
594
- if (prompt.length === 0) throw new InvalidArgumentError("Prompt from --file is empty");
595
- return prompt;
596
- }
597
- const joined = promptParts.join(" ").trim();
598
- if (joined.length > 0) return textPrompt(joined);
599
- if (process.stdin.isTTY) throw new InvalidArgumentError("Prompt is required (pass as argument, --file, or pipe via stdin)");
600
- const prompt = parsePromptSource(await readPromptInputFromStdin());
601
- if (prompt.length === 0) throw new InvalidArgumentError("Prompt from stdin is empty");
602
- return prompt;
603
- } catch (error) {
604
- if (error instanceof PromptInputValidationError) throw new InvalidArgumentError(error.message);
605
- throw error;
609
+ if (typeValue === "http" || typeValue === "sse") {
610
+ const url = parseNonEmptyString(serverRecord.url, `${path}.url`);
611
+ const headers = parseHeaders(serverRecord.headers, `${path}.headers`);
612
+ return {
613
+ type: typeValue,
614
+ name,
615
+ url,
616
+ headers,
617
+ _meta
618
+ };
606
619
  }
620
+ if (typeValue === "stdio") return {
621
+ name,
622
+ command: parseNonEmptyString(serverRecord.command, `${path}.command`),
623
+ args: parseArgs(serverRecord.args, `${path}.args`),
624
+ env: parseEnv(serverRecord.env, `${path}.env`),
625
+ _meta
626
+ };
627
+ throw new Error(`Invalid ${path}.type: expected http, sse, or stdio`);
607
628
  }
608
- function applyPermissionExitCode(result) {
609
- const stats = result.permissionStats;
610
- const deniedOrCancelled = stats.denied + stats.cancelled;
611
- if (stats.requested > 0 && stats.approved === 0 && deniedOrCancelled > 0) process.exitCode = EXIT_CODES.PERMISSION_DENIED;
629
+ function parseMcpServers(value, sourcePath, fieldName = "mcpServers") {
630
+ const fieldPath = `${fieldName} in ${sourcePath}`;
631
+ if (!Array.isArray(value)) throw new Error(`Invalid ${fieldPath}: expected array`);
632
+ const parsed = [];
633
+ for (const [index, rawServer] of value.entries()) parsed.push(parseServer(rawServer, `${fieldName}[${index}] in ${sourcePath}`));
634
+ return parsed;
612
635
  }
613
- function resolveCompatibleConfigId(agent, configId) {
614
- if (isCodexInvocation(agent.agentName, agent.agentCommand) && configId === "thought_level") return "reasoning_effort";
615
- return configId;
636
+ function parseOptionalMcpServers(value, sourcePath, fieldName = "mcpServers") {
637
+ if (value === void 0) return;
638
+ return parseMcpServers(value, sourcePath, fieldName);
616
639
  }
617
- function resolveRequestedOutputPolicy(globalFlags) {
618
- return {
619
- ...resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true),
620
- suppressReads: globalFlags.suppressReads === true
621
- };
640
+ //#endregion
641
+ //#region src/cli/config.ts
642
+ const DEFAULT_TIMEOUT_MS = void 0;
643
+ const DEFAULT_TTL_MS = 3e5;
644
+ const DEFAULT_PERMISSION_MODE = "approve-reads";
645
+ const DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY = "deny";
646
+ const DEFAULT_AUTH_POLICY = "skip";
647
+ const DEFAULT_OUTPUT_FORMAT = "text";
648
+ const DEFAULT_QUEUE_MAX_DEPTH = 16;
649
+ const DEFAULT_DISABLE_EXEC = false;
650
+ const VALID_PERMISSION_MODES = new Set([
651
+ "approve-all",
652
+ "approve-reads",
653
+ "deny-all"
654
+ ]);
655
+ const VALID_NON_INTERACTIVE_PERMISSION_POLICIES = new Set(["deny", "fail"]);
656
+ const VALID_AUTH_POLICIES = new Set(["skip", "fail"]);
657
+ const VALID_OUTPUT_FORMATS = new Set([
658
+ "text",
659
+ "json",
660
+ "quiet"
661
+ ]);
662
+ function defaultGlobalConfigPath() {
663
+ return path.join(os.homedir(), ".acpx", "config.json");
622
664
  }
623
- let sessionModulePromise;
624
- let outputModulePromise;
625
- let outputRenderModulePromise;
626
- let skillflagModulePromise;
627
- function loadSessionModule() {
628
- sessionModulePromise ??= import("./session-RO_LZUnv.js").then((n) => n.t);
629
- return sessionModulePromise;
665
+ function projectConfigPath(cwd) {
666
+ return path.join(path.resolve(cwd), ".acpxrc.json");
630
667
  }
631
- function loadOutputModule() {
632
- outputModulePromise ??= import("./output-Du3m6oPQ.js").then((n) => n.n);
633
- return outputModulePromise;
668
+ function isObject(value) {
669
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
634
670
  }
635
- function loadOutputRenderModule() {
636
- outputRenderModulePromise ??= import("./output-render-Bz58qaQn.js").then((n) => n.r);
637
- return outputRenderModulePromise;
671
+ function parseTtlMs(value, sourcePath) {
672
+ if (value == null) return;
673
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`Invalid config ttl in ${sourcePath}: expected non-negative seconds`);
674
+ return Math.round(value * 1e3);
638
675
  }
639
- function loadSkillflagModule() {
640
- skillflagModulePromise ??= import("skillflag");
641
- return skillflagModulePromise;
676
+ function parseTimeoutMs(value, sourcePath) {
677
+ if (value == null) return;
678
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new Error(`Invalid config timeout in ${sourcePath}: expected positive seconds or null`);
679
+ return Math.round(value * 1e3);
642
680
  }
643
- function shouldMaybeHandleSkillflag(argv) {
644
- return argv.some((token) => token === "--skill" || token.startsWith("--skill="));
681
+ function parseQueueMaxDepth(value, sourcePath) {
682
+ if (value == null) return;
683
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`Invalid config queueMaxDepth in ${sourcePath}: expected positive integer`);
684
+ return value;
645
685
  }
646
- async function findRoutedSessionOrThrow(agentCommand, agentName, cwd, sessionName) {
647
- const walkBoundary = findGitRepositoryRoot(cwd) ?? cwd;
648
- const record = await findSessionByDirectoryWalk({
649
- agentCommand,
650
- cwd,
651
- name: sessionName,
652
- boundary: walkBoundary
653
- });
654
- if (record) return record;
655
- throw new NoSessionError(`⚠ No acpx session found (searched up to ${walkBoundary}).\nCreate one: ${sessionName ? `acpx ${agentName} sessions new --name ${sessionName}` : `acpx ${agentName} sessions new`}`);
686
+ function parsePermissionMode(value, sourcePath) {
687
+ if (value == null) return;
688
+ if (typeof value !== "string" || !VALID_PERMISSION_MODES.has(value)) throw new Error(`Invalid config defaultPermissions in ${sourcePath}: expected approve-all, approve-reads, or deny-all`);
689
+ return value;
656
690
  }
657
- async function handlePrompt(explicitAgentName, promptParts, flags, command, config) {
658
- const globalFlags = resolveGlobalFlags(command, config);
659
- const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
660
- const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
661
- const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
662
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
663
- const [{ createOutputFormatter }, { printPromptSessionBanner, printQueuedPromptByFormat }, { sendSession }] = await Promise.all([
664
- loadOutputModule(),
665
- loadOutputRenderModule(),
666
- loadSessionModule()
667
- ]);
668
- const record = await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, flags.session);
669
- const outputFormatter = createOutputFormatter(outputPolicy.format, {
670
- jsonContext: { sessionId: record.acpxRecordId },
671
- suppressReads: outputPolicy.suppressReads
672
- });
673
- await printPromptSessionBanner(record, agent.cwd, outputPolicy.format, outputPolicy.jsonStrict);
674
- const result = await sendSession({
675
- sessionId: record.acpxRecordId,
676
- prompt,
677
- mcpServers: config.mcpServers,
678
- permissionMode,
679
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
680
- authCredentials: config.auth,
681
- authPolicy: globalFlags.authPolicy,
682
- outputFormatter,
683
- errorEmissionPolicy: { queueErrorAlreadyEmitted: outputPolicy.queueErrorAlreadyEmitted },
684
- suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
685
- timeoutMs: globalFlags.timeout,
686
- ttlMs: globalFlags.ttl,
687
- maxQueueDepth: config.queueMaxDepth,
688
- promptRetries: globalFlags.promptRetries,
689
- verbose: globalFlags.verbose,
690
- waitForCompletion: flags.wait !== false
691
- });
692
- if ("queued" in result) {
693
- printQueuedPromptByFormat(result, outputPolicy.format);
694
- return;
695
- }
696
- applyPermissionExitCode(result);
697
- if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
691
+ function parseNonInteractivePermissionPolicy(value, sourcePath) {
692
+ if (value == null) return;
693
+ if (typeof value !== "string" || !VALID_NON_INTERACTIVE_PERMISSION_POLICIES.has(value)) throw new Error(`Invalid config nonInteractivePermissions in ${sourcePath}: expected deny or fail`);
694
+ return value;
698
695
  }
699
- async function handleExec(explicitAgentName, promptParts, flags, command, config) {
700
- if (config.disableExec) {
701
- if (resolveRequestedOutputPolicy(resolveGlobalFlags(command, config)).format === "json") process.stdout.write(`${JSON.stringify({
702
- jsonrpc: "2.0",
703
- error: {
704
- code: -32603,
705
- message: "exec subcommand is disabled by configuration (disableExec: true)",
706
- data: { acpxCode: "EXEC_DISABLED" }
707
- }
708
- })}\n`);
709
- else process.stderr.write("Error: exec subcommand is disabled by configuration (disableExec: true)\n");
710
- process.exitCode = EXIT_CODES.ERROR;
711
- return;
712
- }
713
- const globalFlags = resolveGlobalFlags(command, config);
714
- const outputPolicy = resolveRequestedOutputPolicy(globalFlags);
715
- const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
716
- const prompt = await readPrompt(promptParts, flags.file, globalFlags.cwd);
717
- const [{ createOutputFormatter }, { runOnce }] = await Promise.all([loadOutputModule(), loadSessionModule()]);
718
- const outputFormatter = createOutputFormatter(outputPolicy.format, { suppressReads: outputPolicy.suppressReads });
719
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
720
- applyPermissionExitCode(await runOnce({
721
- agentCommand: agent.agentCommand,
722
- cwd: agent.cwd,
723
- prompt,
724
- mcpServers: config.mcpServers,
725
- permissionMode,
726
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
727
- authCredentials: config.auth,
728
- authPolicy: globalFlags.authPolicy,
729
- outputFormatter,
730
- suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
731
- timeoutMs: globalFlags.timeout,
732
- verbose: globalFlags.verbose,
733
- promptRetries: globalFlags.promptRetries,
734
- sessionOptions: {
735
- model: globalFlags.model,
736
- allowedTools: globalFlags.allowedTools,
737
- maxTurns: globalFlags.maxTurns
738
- }
739
- }));
696
+ function parseAuthPolicy(value, sourcePath) {
697
+ if (value == null) return;
698
+ if (typeof value !== "string" || !VALID_AUTH_POLICIES.has(value)) throw new Error(`Invalid config authPolicy in ${sourcePath}: expected skip or fail`);
699
+ return value;
740
700
  }
741
- function printCancelResultByFormat(result, format) {
742
- if (emitJsonResult(format, {
743
- action: "cancel_result",
744
- acpxRecordId: result.sessionId || "unknown",
745
- cancelled: result.cancelled
746
- })) return;
747
- if (result.cancelled) {
748
- process.stdout.write("cancel requested\n");
749
- return;
701
+ function parseOutputFormat(value, sourcePath) {
702
+ if (value == null) return;
703
+ if (typeof value !== "string" || !VALID_OUTPUT_FORMATS.has(value)) throw new Error(`Invalid config format in ${sourcePath}: expected text, json, or quiet`);
704
+ return value;
705
+ }
706
+ function parseDefaultAgent(value, sourcePath) {
707
+ if (value == null) return;
708
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Invalid config defaultAgent in ${sourcePath}: expected non-empty string`);
709
+ return normalizeAgentName(value);
710
+ }
711
+ function parseAgents(value, sourcePath) {
712
+ if (value == null) return;
713
+ if (!isObject(value)) throw new Error(`Invalid config agents in ${sourcePath}: expected object`);
714
+ const parsed = {};
715
+ for (const [name, raw] of Object.entries(value)) {
716
+ if (!isObject(raw)) throw new Error(`Invalid config agents.${name} in ${sourcePath}: expected object with command`);
717
+ const command = raw.command;
718
+ if (typeof command !== "string" || command.trim().length === 0) throw new Error(`Invalid config agents.${name}.command in ${sourcePath}: expected non-empty string`);
719
+ parsed[normalizeAgentName(name)] = command.trim();
750
720
  }
751
- process.stdout.write("nothing to cancel\n");
721
+ return parsed;
752
722
  }
753
- function printSetModeResultByFormat(modeId, result, format) {
754
- if (emitJsonResult(format, {
755
- action: "mode_set",
756
- modeId,
757
- resumed: result.resumed,
758
- acpxRecordId: result.record.acpxRecordId,
759
- acpxSessionId: result.record.acpSessionId,
760
- agentSessionId: result.record.agentSessionId
761
- })) return;
762
- if (format === "quiet") {
763
- process.stdout.write(`${modeId}\n`);
764
- return;
723
+ function parseAuth(value, sourcePath) {
724
+ if (value == null) return;
725
+ if (!isObject(value)) throw new Error(`Invalid config auth in ${sourcePath}: expected object`);
726
+ const parsed = {};
727
+ for (const [methodId, rawCredential] of Object.entries(value)) {
728
+ if (typeof rawCredential !== "string" || rawCredential.trim().length === 0) throw new Error(`Invalid config auth.${methodId} in ${sourcePath}: expected non-empty string`);
729
+ parsed[methodId] = rawCredential;
765
730
  }
766
- process.stdout.write(`mode set: ${modeId}\n`);
731
+ return parsed;
767
732
  }
768
- function printSetModelResultByFormat(modelId, result, format) {
769
- if (emitJsonResult(format, {
770
- action: "model_set",
771
- modelId,
772
- resumed: result.resumed,
773
- acpxRecordId: result.record.acpxRecordId,
774
- acpxSessionId: result.record.acpSessionId,
775
- agentSessionId: result.record.agentSessionId
776
- })) return;
777
- if (format === "quiet") {
778
- process.stdout.write(`${modelId}\n`);
779
- return;
780
- }
781
- process.stdout.write(`model set: ${modelId}\n`);
782
- }
783
- function printSetConfigOptionResultByFormat(configId, value, result, format) {
784
- if (emitJsonResult(format, {
785
- action: "config_set",
786
- configId,
787
- value,
788
- resumed: result.resumed,
789
- configOptions: result.response.configOptions,
790
- acpxRecordId: result.record.acpxRecordId,
791
- acpxSessionId: result.record.acpSessionId,
792
- agentSessionId: result.record.agentSessionId
793
- })) return;
794
- if (format === "quiet") {
795
- process.stdout.write(`${value}\n`);
796
- return;
797
- }
798
- process.stdout.write(`config set: ${configId}=${value} (${result.response.configOptions.length} options)\n`);
733
+ function parseDisableExec(value, sourcePath) {
734
+ if (value == null) return;
735
+ if (typeof value !== "boolean") throw new Error(`Invalid config disableExec in ${sourcePath}: expected boolean`);
736
+ return value;
799
737
  }
800
- async function handleCancel(explicitAgentName, flags, command, config) {
801
- const globalFlags = resolveGlobalFlags(command, config);
802
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
803
- const { cancelSessionPrompt } = await loadSessionModule();
804
- const walkBoundary = findGitRepositoryRoot(agent.cwd) ?? agent.cwd;
805
- const record = await findSessionByDirectoryWalk({
806
- agentCommand: agent.agentCommand,
807
- cwd: agent.cwd,
808
- name: resolveSessionNameFromFlags(flags, command),
809
- boundary: walkBoundary
810
- });
811
- if (!record) {
812
- printCancelResultByFormat({
813
- sessionId: "",
814
- cancelled: false
815
- }, globalFlags.format);
816
- return;
738
+ async function readConfigFile(filePath) {
739
+ try {
740
+ const payload = await fs$1.readFile(filePath, "utf8");
741
+ let parsed;
742
+ try {
743
+ parsed = JSON.parse(payload);
744
+ } catch (error) {
745
+ const reason = error instanceof Error ? error.message : String(error);
746
+ throw new Error(`Invalid JSON in ${filePath}: ${reason}`, { cause: error });
747
+ }
748
+ if (!isObject(parsed)) throw new Error(`Invalid config in ${filePath}: expected top-level JSON object`);
749
+ return {
750
+ config: parsed,
751
+ exists: true
752
+ };
753
+ } catch (error) {
754
+ if (error.code === "ENOENT") return { exists: false };
755
+ throw error;
817
756
  }
818
- printCancelResultByFormat(await cancelSessionPrompt({
819
- sessionId: record.acpxRecordId,
820
- verbose: globalFlags.verbose
821
- }), globalFlags.format);
822
757
  }
823
- async function handleSetMode(explicitAgentName, modeId, flags, command, config) {
824
- const globalFlags = resolveGlobalFlags(command, config);
825
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
826
- const { setSessionMode } = await loadSessionModule();
827
- const result = await setSessionMode({
828
- sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
829
- modeId,
830
- mcpServers: config.mcpServers,
831
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
832
- authCredentials: config.auth,
833
- authPolicy: globalFlags.authPolicy,
834
- timeoutMs: globalFlags.timeout,
835
- verbose: globalFlags.verbose
836
- });
837
- if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
838
- printSetModeResultByFormat(modeId, result, globalFlags.format);
839
- }
840
- async function handleSetModel(explicitAgentName, modelId, flags, command, config) {
841
- const globalFlags = resolveGlobalFlags(command, config);
842
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
843
- const { setSessionModel } = await loadSessionModule();
844
- const result = await setSessionModel({
845
- sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
846
- modelId,
847
- mcpServers: config.mcpServers,
848
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
849
- authCredentials: config.auth,
850
- authPolicy: globalFlags.authPolicy,
851
- timeoutMs: globalFlags.timeout,
852
- verbose: globalFlags.verbose
853
- });
854
- if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
855
- printSetModelResultByFormat(modelId, result, globalFlags.format);
758
+ function mergeAgents(globalAgents, projectAgents) {
759
+ return {
760
+ ...globalAgents,
761
+ ...projectAgents
762
+ };
856
763
  }
857
- async function handleSetConfigOption(explicitAgentName, configId, value, flags, command, config) {
858
- const globalFlags = resolveGlobalFlags(command, config);
859
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
860
- if (configId === "model") {
861
- await handleSetModel(explicitAgentName, value, flags, command, config);
862
- return;
863
- }
864
- const resolvedConfigId = resolveCompatibleConfigId(agent, configId);
865
- const { setSessionConfigOption } = await loadSessionModule();
866
- const result = await setSessionConfigOption({
867
- sessionId: (await findRoutedSessionOrThrow(agent.agentCommand, agent.agentName, agent.cwd, resolveSessionNameFromFlags(flags, command))).acpxRecordId,
868
- configId: resolvedConfigId,
869
- value,
870
- mcpServers: config.mcpServers,
871
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
872
- authCredentials: config.auth,
873
- authPolicy: globalFlags.authPolicy,
874
- timeoutMs: globalFlags.timeout,
875
- verbose: globalFlags.verbose
876
- });
877
- if (globalFlags.verbose && result.loadError) process.stderr.write(`[acpx] loadSession failed, started fresh session: ${result.loadError}\n`);
878
- printSetConfigOptionResultByFormat(configId, value, result, globalFlags.format);
764
+ function mergeAuth(globalAuth, projectAuth) {
765
+ return {
766
+ ...globalAuth,
767
+ ...projectAuth
768
+ };
879
769
  }
880
- async function handleSessionsList(explicitAgentName, command, config) {
881
- const globalFlags = resolveGlobalFlags(command, config);
882
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
883
- const [{ listSessionsForAgent }, { printSessionsByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
884
- printSessionsByFormat(await listSessionsForAgent(agent.agentCommand), globalFlags.format);
770
+ async function loadResolvedConfig(cwd) {
771
+ const globalPath = defaultGlobalConfigPath();
772
+ const projectPath = projectConfigPath(cwd);
773
+ const [globalResult, projectResult] = await Promise.all([readConfigFile(globalPath), readConfigFile(projectPath)]);
774
+ const globalConfig = globalResult.config;
775
+ const projectConfig = projectResult.config;
776
+ const defaultAgent = parseDefaultAgent(projectConfig?.defaultAgent, projectPath) ?? parseDefaultAgent(globalConfig?.defaultAgent, globalPath) ?? "codex";
777
+ const defaultPermissions = parsePermissionMode(projectConfig?.defaultPermissions, projectPath) ?? parsePermissionMode(globalConfig?.defaultPermissions, globalPath) ?? DEFAULT_PERMISSION_MODE;
778
+ const nonInteractivePermissions = parseNonInteractivePermissionPolicy(projectConfig?.nonInteractivePermissions, projectPath) ?? parseNonInteractivePermissionPolicy(globalConfig?.nonInteractivePermissions, globalPath) ?? DEFAULT_NON_INTERACTIVE_PERMISSION_POLICY;
779
+ const authPolicy = parseAuthPolicy(projectConfig?.authPolicy, projectPath) ?? parseAuthPolicy(globalConfig?.authPolicy, globalPath) ?? DEFAULT_AUTH_POLICY;
780
+ const ttlMs = parseTtlMs(projectConfig?.ttl, projectPath) ?? parseTtlMs(globalConfig?.ttl, globalPath) ?? DEFAULT_TTL_MS;
781
+ const timeoutConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "timeout");
782
+ const timeoutConfiguredInGlobal = globalConfig != null && Object.prototype.hasOwnProperty.call(globalConfig, "timeout");
783
+ let timeoutMs = DEFAULT_TIMEOUT_MS;
784
+ if (timeoutConfiguredInProject) timeoutMs = parseTimeoutMs(projectConfig?.timeout, projectPath);
785
+ else if (timeoutConfiguredInGlobal) timeoutMs = parseTimeoutMs(globalConfig?.timeout, globalPath);
786
+ const format = parseOutputFormat(projectConfig?.format, projectPath) ?? parseOutputFormat(globalConfig?.format, globalPath) ?? DEFAULT_OUTPUT_FORMAT;
787
+ const queueMaxDepth = parseQueueMaxDepth(projectConfig?.queueMaxDepth, projectPath) ?? parseQueueMaxDepth(globalConfig?.queueMaxDepth, globalPath) ?? DEFAULT_QUEUE_MAX_DEPTH;
788
+ const agents = mergeAgents(parseAgents(globalConfig?.agents, globalPath), parseAgents(projectConfig?.agents, projectPath));
789
+ const auth = mergeAuth(parseAuth(globalConfig?.auth, globalPath), parseAuth(projectConfig?.auth, projectPath));
790
+ const mcpServersConfiguredInProject = projectConfig != null && Object.prototype.hasOwnProperty.call(projectConfig, "mcpServers");
791
+ const mcpServersConfiguredInGlobal = globalConfig != null && Object.prototype.hasOwnProperty.call(globalConfig, "mcpServers");
792
+ let mcpServers = [];
793
+ if (mcpServersConfiguredInProject) mcpServers = parseMcpServers(projectConfig?.mcpServers, projectPath);
794
+ else if (mcpServersConfiguredInGlobal) mcpServers = parseMcpServers(globalConfig?.mcpServers, globalPath);
795
+ const disableExec = parseDisableExec(projectConfig?.disableExec, projectPath) ?? parseDisableExec(globalConfig?.disableExec, globalPath) ?? DEFAULT_DISABLE_EXEC;
796
+ return {
797
+ defaultAgent,
798
+ defaultPermissions,
799
+ nonInteractivePermissions,
800
+ authPolicy,
801
+ ttlMs,
802
+ timeoutMs,
803
+ queueMaxDepth,
804
+ format,
805
+ agents,
806
+ auth,
807
+ disableExec,
808
+ mcpServers,
809
+ globalPath,
810
+ projectPath,
811
+ hasGlobalConfig: globalResult.exists,
812
+ hasProjectConfig: projectResult.exists
813
+ };
885
814
  }
886
- async function handleSessionsClose(explicitAgentName, sessionName, command, config) {
887
- const globalFlags = resolveGlobalFlags(command, config);
888
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
889
- const [{ closeSession }, { printClosedSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
890
- const record = await findSession({
891
- agentCommand: agent.agentCommand,
892
- cwd: agent.cwd,
893
- name: sessionName
894
- });
895
- if (!record) {
896
- if (sessionName) throw new Error(`No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}`);
897
- throw new Error(`No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
898
- }
899
- printClosedSessionByFormat(await closeSession(record.acpxRecordId), globalFlags.format);
815
+ function toConfigDisplay(config) {
816
+ const agents = {};
817
+ for (const [name, command] of Object.entries(config.agents)) agents[name] = { command };
818
+ return {
819
+ defaultAgent: config.defaultAgent,
820
+ defaultPermissions: config.defaultPermissions,
821
+ nonInteractivePermissions: config.nonInteractivePermissions,
822
+ authPolicy: config.authPolicy,
823
+ ttl: Math.round(config.ttlMs / 1e3),
824
+ timeout: config.timeoutMs == null ? null : config.timeoutMs / 1e3,
825
+ queueMaxDepth: config.queueMaxDepth,
826
+ format: config.format,
827
+ agents,
828
+ authMethods: Object.keys(config.auth).toSorted(),
829
+ disableExec: config.disableExec
830
+ };
900
831
  }
901
- async function handleSessionsNew(explicitAgentName, flags, command, config) {
902
- const globalFlags = resolveGlobalFlags(command, config);
903
- const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
904
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
905
- const [{ createSession, closeSession }, { printCreatedSessionBanner, printNewSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
906
- const replaced = await findSession({
907
- agentCommand: agent.agentCommand,
908
- cwd: agent.cwd,
909
- name: flags.name
910
- });
911
- if (replaced) {
912
- await closeSession(replaced.acpxRecordId);
913
- if (globalFlags.verbose) process.stderr.write(`[acpx] soft-closed prior session: ${replaced.acpxRecordId}\n`);
914
- }
915
- const created = await createSession({
916
- agentCommand: agent.agentCommand,
917
- cwd: agent.cwd,
918
- name: flags.name,
919
- resumeSessionId: flags.resumeSession,
920
- mcpServers: config.mcpServers,
921
- permissionMode,
922
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
923
- authCredentials: config.auth,
924
- authPolicy: globalFlags.authPolicy,
925
- timeoutMs: globalFlags.timeout,
926
- verbose: globalFlags.verbose,
927
- sessionOptions: {
928
- model: globalFlags.model,
929
- allowedTools: globalFlags.allowedTools,
930
- maxTurns: globalFlags.maxTurns
931
- }
932
- });
933
- printCreatedSessionBanner(created, agent.agentName, globalFlags.format, globalFlags.jsonStrict);
934
- if (globalFlags.verbose) {
935
- const scope = flags.name ? `named session "${flags.name}"` : "cwd session";
936
- process.stderr.write(`[acpx] created ${scope}: ${created.acpxRecordId}\n`);
937
- }
938
- printNewSessionByFormat(created, replaced, globalFlags.format);
832
+ async function initGlobalConfigFile() {
833
+ const configPath = defaultGlobalConfigPath();
834
+ await fs$1.mkdir(path.dirname(configPath), { recursive: true });
835
+ try {
836
+ await fs$1.access(configPath);
837
+ return {
838
+ path: configPath,
839
+ created: false
840
+ };
841
+ } catch {}
842
+ const payload = {
843
+ defaultAgent: DEFAULT_AGENT_NAME,
844
+ defaultPermissions: "approve-all",
845
+ nonInteractivePermissions: "deny",
846
+ authPolicy: "skip",
847
+ ttl: 300,
848
+ timeout: null,
849
+ queueMaxDepth: DEFAULT_QUEUE_MAX_DEPTH,
850
+ format: "text",
851
+ agents: {},
852
+ auth: {}
853
+ };
854
+ await fs$1.writeFile(configPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
855
+ return {
856
+ path: configPath,
857
+ created: true
858
+ };
939
859
  }
940
- async function handleSessionsEnsure(explicitAgentName, flags, command, config) {
860
+ //#endregion
861
+ //#region src/cli/config-command.ts
862
+ async function handleConfigShow(command, config) {
941
863
  const globalFlags = resolveGlobalFlags(command, config);
942
- const permissionMode = resolvePermissionMode(globalFlags, config.defaultPermissions);
943
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
944
- const [{ ensureSession }, { printCreatedSessionBanner, printEnsuredSessionByFormat }] = await Promise.all([loadSessionModule(), loadOutputRenderModule()]);
945
- const result = await ensureSession({
946
- agentCommand: agent.agentCommand,
947
- cwd: agent.cwd,
948
- name: flags.name,
949
- resumeSessionId: flags.resumeSession,
950
- mcpServers: config.mcpServers,
951
- permissionMode,
952
- nonInteractivePermissions: globalFlags.nonInteractivePermissions,
953
- authCredentials: config.auth,
954
- authPolicy: globalFlags.authPolicy,
955
- timeoutMs: globalFlags.timeout,
956
- verbose: globalFlags.verbose,
957
- sessionOptions: {
958
- model: globalFlags.model,
959
- allowedTools: globalFlags.allowedTools,
960
- maxTurns: globalFlags.maxTurns
961
- }
962
- });
963
- if (result.created) printCreatedSessionBanner(result.record, agent.agentName, globalFlags.format, globalFlags.jsonStrict);
964
- printEnsuredSessionByFormat(result.record, result.created, globalFlags.format);
965
- }
966
- function userContentToText(content) {
967
- if ("Text" in content) return content.Text;
968
- if ("Mention" in content) return content.Mention.content;
969
- if ("Image" in content) return content.Image.source || "[image]";
970
- return "";
971
- }
972
- function agentContentToText(content) {
973
- if ("Text" in content) return content.Text;
974
- if ("Thinking" in content) return content.Thinking.text;
975
- if ("RedactedThinking" in content) return "[redacted_thinking]";
976
- if ("ToolUse" in content) return `[tool:${content.ToolUse.name}]`;
977
- return "";
978
- }
979
- function conversationHistoryEntries(record) {
980
- const entries = [];
981
- for (const message of record.messages) {
982
- if (message === "Resume") continue;
983
- if ("User" in message) {
984
- const text = message.User.content.map((entry) => userContentToText(entry)).join(" ").trim();
985
- if (!text) continue;
986
- entries.push({
987
- role: "user",
988
- timestamp: record.updated_at,
989
- textPreview: text
990
- });
991
- continue;
992
- }
993
- if ("Agent" in message) {
994
- const text = message.Agent.content.map((entry) => agentContentToText(entry)).join(" ").trim();
995
- if (!text) continue;
996
- entries.push({
997
- role: "assistant",
998
- timestamp: record.updated_at,
999
- textPreview: text
1000
- });
864
+ const payload = {
865
+ ...toConfigDisplay(config),
866
+ paths: {
867
+ global: config.globalPath,
868
+ project: config.projectPath
869
+ },
870
+ loaded: {
871
+ global: config.hasGlobalConfig,
872
+ project: config.hasProjectConfig
1001
873
  }
1002
- }
1003
- return entries;
1004
- }
1005
- function printSessionDetailsByFormat(record, format) {
1006
- if (format === "json") {
1007
- process.stdout.write(`${JSON.stringify(record)}\n`);
1008
- return;
1009
- }
1010
- if (format === "quiet") {
1011
- process.stdout.write(`${record.acpxRecordId}\n`);
874
+ };
875
+ if (globalFlags.format === "json") {
876
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
1012
877
  return;
1013
878
  }
1014
- process.stdout.write(`id: ${record.acpxRecordId}\n`);
1015
- process.stdout.write(`sessionId: ${record.acpSessionId}\n`);
1016
- process.stdout.write(`agentSessionId: ${record.agentSessionId ?? "-"}\n`);
1017
- process.stdout.write(`agent: ${record.agentCommand}\n`);
1018
- process.stdout.write(`cwd: ${record.cwd}\n`);
1019
- process.stdout.write(`name: ${record.name ?? "-"}\n`);
1020
- process.stdout.write(`created: ${record.createdAt}\n`);
1021
- process.stdout.write(`lastActivity: ${record.lastUsedAt}\n`);
1022
- process.stdout.write(`lastPrompt: ${record.lastPromptAt ?? "-"}\n`);
1023
- process.stdout.write(`closed: ${record.closed ? "yes" : "no"}\n`);
1024
- process.stdout.write(`closedAt: ${record.closedAt ?? "-"}\n`);
1025
- process.stdout.write(`pid: ${record.pid ?? "-"}\n`);
1026
- process.stdout.write(`agentStartedAt: ${record.agentStartedAt ?? "-"}\n`);
1027
- process.stdout.write(`lastExitCode: ${record.lastAgentExitCode ?? "-"}\n`);
1028
- process.stdout.write(`lastExitSignal: ${record.lastAgentExitSignal ?? "-"}\n`);
1029
- process.stdout.write(`lastExitAt: ${record.lastAgentExitAt ?? "-"}\n`);
1030
- process.stdout.write(`disconnectReason: ${record.lastAgentDisconnectReason ?? "-"}\n`);
1031
- process.stdout.write(`historyEntries: ${conversationHistoryEntries(record).length}\n`);
879
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1032
880
  }
1033
- function printSessionHistoryByFormat(record, limit, format) {
1034
- const history = conversationHistoryEntries(record);
1035
- const visible = limit === 0 ? history : history.slice(Math.max(0, history.length - limit));
1036
- if (format === "json") {
1037
- process.stdout.write(`${JSON.stringify({
1038
- id: record.acpxRecordId,
1039
- sessionId: record.acpSessionId,
1040
- limit,
1041
- count: visible.length,
1042
- entries: visible
881
+ async function handleConfigInit(command, config) {
882
+ const globalFlags = resolveGlobalFlags(command, config);
883
+ const result = await initGlobalConfigFile();
884
+ if (globalFlags.format === "json") {
885
+ process.stdout.write(`${JSON.stringify({
886
+ path: result.path,
887
+ created: result.created
1043
888
  })}\n`);
1044
889
  return;
1045
890
  }
1046
- if (format === "quiet") {
1047
- for (const entry of visible) process.stdout.write(`${entry.textPreview}\n`);
891
+ if (globalFlags.format === "quiet") {
892
+ process.stdout.write(`${result.path}\n`);
1048
893
  return;
1049
894
  }
1050
- process.stdout.write(`session: ${record.acpxRecordId} (${visible.length}/${history.length} shown)\n`);
1051
- if (visible.length === 0) {
1052
- process.stdout.write("No history\n");
895
+ if (result.created) {
896
+ process.stdout.write(`Created ${result.path}\n`);
1053
897
  return;
1054
898
  }
1055
- for (const entry of visible) process.stdout.write(`${entry.timestamp}\t${entry.role}\t${entry.textPreview}\n`);
899
+ process.stdout.write(`Config already exists: ${result.path}\n`);
1056
900
  }
1057
- async function handleSessionsShow(explicitAgentName, sessionName, command, config) {
1058
- const globalFlags = resolveGlobalFlags(command, config);
1059
- const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
1060
- const record = await findSession({
1061
- agentCommand: agent.agentCommand,
1062
- cwd: agent.cwd,
1063
- name: sessionName,
1064
- includeClosed: true
901
+ function registerConfigCommand(program, config) {
902
+ const configCommand = program.command("config").description("Inspect and initialize acpx configuration");
903
+ configCommand.command("show").description("Show resolved config").action(async function() {
904
+ await handleConfigShow(this, config);
905
+ });
906
+ configCommand.command("init").description("Create global config template").action(async function() {
907
+ await handleConfigInit(this, config);
908
+ });
909
+ configCommand.action(async function() {
910
+ await handleConfigShow(this, config);
1065
911
  });
1066
- if (!record) throw new Error(sessionName ? `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}` : `No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
1067
- printSessionDetailsByFormat(record, globalFlags.format);
1068
912
  }
1069
- async function handleSessionsHistory(explicitAgentName, sessionName, flags, command, config) {
913
+ //#endregion
914
+ //#region src/cli/status-command.ts
915
+ function formatUptime(startedAt) {
916
+ if (!startedAt) return;
917
+ const startedMs = Date.parse(startedAt);
918
+ if (!Number.isFinite(startedMs)) return;
919
+ const elapsedMs = Math.max(0, Date.now() - startedMs);
920
+ const seconds = Math.floor(elapsedMs / 1e3);
921
+ const hours = Math.floor(seconds / 3600);
922
+ const minutes = Math.floor(seconds % 3600 / 60);
923
+ const remSeconds = seconds % 60;
924
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${remSeconds.toString().padStart(2, "0")}`;
925
+ }
926
+ async function handleStatus(explicitAgentName, flags, command, config) {
1070
927
  const globalFlags = resolveGlobalFlags(command, config);
1071
928
  const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
1072
929
  const record = await findSession({
1073
930
  agentCommand: agent.agentCommand,
1074
931
  cwd: agent.cwd,
1075
- name: sessionName,
1076
- includeClosed: true
932
+ name: resolveSessionNameFromFlags(flags, command)
933
+ });
934
+ if (!record) {
935
+ if (emitJsonResult(globalFlags.format, {
936
+ action: "status_snapshot",
937
+ status: "no-session",
938
+ summary: "no active session"
939
+ })) return;
940
+ if (globalFlags.format === "quiet") {
941
+ process.stdout.write("no-session\n");
942
+ return;
943
+ }
944
+ process.stdout.write("session: -\n");
945
+ process.stdout.write(`agent: ${agent.agentCommand}\n`);
946
+ process.stdout.write("pid: -\n");
947
+ process.stdout.write("status: no-session\n");
948
+ process.stdout.write("model: -\n");
949
+ process.stdout.write("mode: -\n");
950
+ process.stdout.write("uptime: -\n");
951
+ process.stdout.write("lastPromptTime: -\n");
952
+ return;
953
+ }
954
+ const health = await probeQueueOwnerHealth(record.acpxRecordId);
955
+ const running = health.healthy;
956
+ const payload = {
957
+ sessionId: record.acpxRecordId,
958
+ agentCommand: record.agentCommand,
959
+ pid: health.pid ?? record.pid ?? null,
960
+ status: running ? "running" : "dead",
961
+ model: record.acpx?.current_model_id ?? null,
962
+ mode: record.acpx?.current_mode_id ?? null,
963
+ availableModels: record.acpx?.available_models ?? null,
964
+ uptime: running ? formatUptime(record.agentStartedAt) ?? null : null,
965
+ lastPromptTime: record.lastPromptAt ?? null,
966
+ exitCode: running ? null : record.lastAgentExitCode ?? null,
967
+ signal: running ? null : record.lastAgentExitSignal ?? null,
968
+ ...agentSessionIdPayload(record.agentSessionId)
969
+ };
970
+ if (emitJsonResult(globalFlags.format, {
971
+ action: "status_snapshot",
972
+ status: running ? "alive" : "dead",
973
+ pid: payload.pid ?? void 0,
974
+ summary: running ? "queue owner healthy" : "queue owner unavailable",
975
+ model: payload.model ?? void 0,
976
+ mode: payload.mode ?? void 0,
977
+ availableModels: payload.availableModels ?? void 0,
978
+ uptime: payload.uptime ?? void 0,
979
+ lastPromptTime: payload.lastPromptTime ?? void 0,
980
+ exitCode: payload.exitCode ?? void 0,
981
+ signal: payload.signal ?? void 0,
982
+ acpxRecordId: record.acpxRecordId,
983
+ acpxSessionId: record.acpSessionId,
984
+ agentSessionId: record.agentSessionId
985
+ })) return;
986
+ if (globalFlags.format === "quiet") {
987
+ process.stdout.write(`${payload.status}\n`);
988
+ return;
989
+ }
990
+ process.stdout.write(`session: ${payload.sessionId}\n`);
991
+ if ("agentSessionId" in payload) process.stdout.write(`agentSessionId: ${payload.agentSessionId}\n`);
992
+ process.stdout.write(`agent: ${payload.agentCommand}\n`);
993
+ process.stdout.write(`pid: ${payload.pid ?? "-"}\n`);
994
+ process.stdout.write(`status: ${payload.status}\n`);
995
+ process.stdout.write(`model: ${payload.model ?? "-"}\n`);
996
+ process.stdout.write(`mode: ${payload.mode ?? "-"}\n`);
997
+ process.stdout.write(`uptime: ${payload.uptime ?? "-"}\n`);
998
+ process.stdout.write(`lastPromptTime: ${payload.lastPromptTime ?? "-"}\n`);
999
+ if (payload.status === "dead") {
1000
+ process.stdout.write(`exitCode: ${payload.exitCode ?? "-"}\n`);
1001
+ process.stdout.write(`signal: ${payload.signal ?? "-"}\n`);
1002
+ }
1003
+ }
1004
+ function registerStatusCommand(parent, explicitAgentName, config, description) {
1005
+ const statusCommand = parent.command("status").description(description);
1006
+ addSessionNameOption(statusCommand);
1007
+ statusCommand.action(async function(flags) {
1008
+ await handleStatus(explicitAgentName, flags, this, config);
1077
1009
  });
1078
- if (!record) throw new Error(sessionName ? `No named session "${sessionName}" for cwd ${agent.cwd} and agent ${agent.agentName}` : `No cwd session for ${agent.cwd} and agent ${agent.agentName}`);
1079
- printSessionHistoryByFormat(record, flags.limit, globalFlags.format);
1080
1010
  }
1011
+ //#endregion
1012
+ //#region src/cli/command-registration.ts
1081
1013
  function registerSessionsCommand(parent, explicitAgentName, config) {
1082
1014
  const sessionsCommand = parent.command("sessions").description("List, ensure, create, or close sessions for this agent");
1083
1015
  sessionsCommand.action(async function() {
@@ -1098,7 +1030,7 @@ function registerSessionsCommand(parent, explicitAgentName, config) {
1098
1030
  sessionsCommand.command("show").description("Show session metadata for current cwd").argument("[name]", "Session name", parseSessionName).action(async function(name) {
1099
1031
  await handleSessionsShow(explicitAgentName, name, this, config);
1100
1032
  });
1101
- sessionsCommand.command("history").description("Show recent session history entries").argument("[name]", "Session name", parseSessionName).option("--limit <count>", "Maximum number of entries to show (default: 20)", parseHistoryLimit, 20).action(async function(name, flags) {
1033
+ sessionsCommand.command("history").description("Show recent session history entries").argument("[name]", "Session name", parseSessionName).option("--limit <count>", `Maximum number of entries to show (default: 20)`, parseHistoryLimit, 20).action(async function(name, flags) {
1102
1034
  await handleSessionsHistory(explicitAgentName, name, flags, this, config);
1103
1035
  });
1104
1036
  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) {
@@ -1153,7 +1085,7 @@ function registerAgentCommand(program, agentName, config) {
1153
1085
  }
1154
1086
  function registerFlowCommand(program, config) {
1155
1087
  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) {
1156
- const { handleFlowRun } = await import("./cli-idpWyCOs.js");
1088
+ const { handleFlowRun } = await import("./cli-CLRrs6eQ.js");
1157
1089
  await handleFlowRun(file, flags, this, config);
1158
1090
  });
1159
1091
  }
@@ -1170,6 +1102,103 @@ function registerDefaultCommands(program, config) {
1170
1102
  registerConfigCommand(program, config);
1171
1103
  registerFlowCommand(program, config);
1172
1104
  }
1105
+ //#endregion
1106
+ //#region src/cli/queue/owner-env.ts
1107
+ function asRecord(value) {
1108
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
1109
+ return value;
1110
+ }
1111
+ function parseQueueOwnerPayload(raw) {
1112
+ const record = asRecord(JSON.parse(raw));
1113
+ if (!record) throw new Error("queue owner payload must be an object");
1114
+ if (typeof record.sessionId !== "string" || record.sessionId.trim().length === 0) throw new Error("queue owner payload missing sessionId");
1115
+ if (record.permissionMode !== "approve-all" && record.permissionMode !== "approve-reads" && record.permissionMode !== "deny-all") throw new Error("queue owner payload has invalid permissionMode");
1116
+ const options = {
1117
+ sessionId: record.sessionId,
1118
+ permissionMode: record.permissionMode
1119
+ };
1120
+ const parsedMcpServers = parseOptionalMcpServers(record.mcpServers, "queue owner payload");
1121
+ if (parsedMcpServers) options.mcpServers = parsedMcpServers;
1122
+ if (typeof record.nonInteractivePermissions === "string") options.nonInteractivePermissions = record.nonInteractivePermissions === "deny" || record.nonInteractivePermissions === "fail" ? record.nonInteractivePermissions : void 0;
1123
+ if (record.authCredentials && typeof record.authCredentials === "object") {
1124
+ const entries = Object.entries(record.authCredentials).filter(([, value]) => typeof value === "string");
1125
+ options.authCredentials = Object.fromEntries(entries);
1126
+ }
1127
+ if (record.authPolicy === "skip" || record.authPolicy === "fail") options.authPolicy = record.authPolicy;
1128
+ if (typeof record.suppressSdkConsoleErrors === "boolean") options.suppressSdkConsoleErrors = record.suppressSdkConsoleErrors;
1129
+ if (typeof record.verbose === "boolean") options.verbose = record.verbose;
1130
+ if (typeof record.ttlMs === "number" && Number.isFinite(record.ttlMs)) options.ttlMs = record.ttlMs;
1131
+ if (typeof record.maxQueueDepth === "number" && Number.isFinite(record.maxQueueDepth)) options.maxQueueDepth = Math.max(1, Math.round(record.maxQueueDepth));
1132
+ if (typeof record.promptRetries === "number" && Number.isFinite(record.promptRetries)) options.promptRetries = Math.max(0, Math.round(record.promptRetries));
1133
+ return options;
1134
+ }
1135
+ async function runQueueOwnerFromEnv(env) {
1136
+ const payload = env.ACPX_QUEUE_OWNER_PAYLOAD;
1137
+ if (!payload) throw new Error("missing ACPX_QUEUE_OWNER_PAYLOAD");
1138
+ await runSessionQueueOwner(parseQueueOwnerPayload(payload));
1139
+ }
1140
+ //#endregion
1141
+ //#region src/version.ts
1142
+ const UNKNOWN_VERSION = "0.0.0-unknown";
1143
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
1144
+ let cachedVersion = null;
1145
+ function parseVersion(value) {
1146
+ if (typeof value !== "string") return null;
1147
+ const trimmed = value.trim();
1148
+ return trimmed.length > 0 ? trimmed : null;
1149
+ }
1150
+ function readPackageVersion(packageJsonPath) {
1151
+ try {
1152
+ return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
1153
+ } catch {
1154
+ return null;
1155
+ }
1156
+ }
1157
+ function resolveVersionFromAncestors(startDir) {
1158
+ let current = startDir;
1159
+ while (true) {
1160
+ const packageVersion = readPackageVersion(path.join(current, "package.json"));
1161
+ if (packageVersion) return packageVersion;
1162
+ const parent = path.dirname(current);
1163
+ if (parent === current) return null;
1164
+ current = parent;
1165
+ }
1166
+ }
1167
+ function resolveAcpxVersion(params) {
1168
+ const env = params?.env ?? process.env;
1169
+ const envPackageName = parseVersion(env.npm_package_name);
1170
+ const envVersion = parseVersion(env.npm_package_version);
1171
+ if (envPackageName === "acpx" && envVersion) return envVersion;
1172
+ if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
1173
+ return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
1174
+ }
1175
+ function getAcpxVersion() {
1176
+ if (cachedVersion) return cachedVersion;
1177
+ cachedVersion = resolveAcpxVersion();
1178
+ return cachedVersion;
1179
+ }
1180
+ //#endregion
1181
+ //#region src/cli-core.ts
1182
+ const TOP_LEVEL_VERBS = new Set([
1183
+ "prompt",
1184
+ "exec",
1185
+ "cancel",
1186
+ "flow",
1187
+ "set-mode",
1188
+ "set",
1189
+ "sessions",
1190
+ "status",
1191
+ "config",
1192
+ "help"
1193
+ ]);
1194
+ let skillflagModulePromise;
1195
+ function loadSkillflagModule() {
1196
+ skillflagModulePromise ??= import("skillflag");
1197
+ return skillflagModulePromise;
1198
+ }
1199
+ function shouldMaybeHandleSkillflag(argv) {
1200
+ return argv.some((token) => token === "--skill" || token.startsWith("--skill="));
1201
+ }
1173
1202
  function detectAgentToken(argv) {
1174
1203
  let hasAgentOverride = false;
1175
1204
  for (let index = 0; index < argv.length; index += 1) {
@@ -1193,7 +1222,7 @@ function detectAgentToken(argv) {
1193
1222
  continue;
1194
1223
  }
1195
1224
  if (token.startsWith("--cwd=") || token.startsWith("--auth-policy=") || token.startsWith("--non-interactive-permissions=") || token.startsWith("--format=") || token.startsWith("--model=") || token.startsWith("--allowed-tools=") || token.startsWith("--max-turns=") || token.startsWith("--json-strict=") || token.startsWith("--timeout=") || token.startsWith("--ttl=") || token.startsWith("--file=")) continue;
1196
- if (token === "--approve-all" || token === "--approve-reads" || token === "--deny-all" || token === "--json-strict" || token === "--verbose") continue;
1225
+ if (token === "--approve-all" || token === "--approve-reads" || token === "--deny-all" || token === "--json-strict" || token === "--verbose" || token === "--suppress-reads") continue;
1197
1226
  return { hasAgentOverride };
1198
1227
  }
1199
1228
  return { hasAgentOverride };
@@ -1237,13 +1266,11 @@ function detectJsonStrict(argv) {
1237
1266
  for (let index = 0; index < argv.length; index += 1) {
1238
1267
  const token = argv[index];
1239
1268
  if (token === "--") break;
1240
- if (token === "--json-strict") return true;
1241
- if (token.startsWith("--json-strict=")) return true;
1269
+ if (token === "--json-strict" || token.startsWith("--json-strict=")) return true;
1242
1270
  }
1243
1271
  return false;
1244
1272
  }
1245
1273
  async function emitJsonErrorEvent(error) {
1246
- const { createOutputFormatter } = await loadOutputModule();
1247
1274
  const formatter = createOutputFormatter("json", {
1248
1275
  jsonContext: { sessionId: "unknown" },
1249
1276
  suppressReads: false
@@ -1257,8 +1284,11 @@ function isOutputAlreadyEmitted(error) {
1257
1284
  }
1258
1285
  async function emitRequestedError(error, normalized, outputPolicy) {
1259
1286
  if (isOutputAlreadyEmitted(error)) return;
1260
- if (outputPolicy.format === "json") await emitJsonErrorEvent(normalized);
1261
- else if (!outputPolicy.suppressNonJsonStderr) process.stderr.write(`${normalized.message}\n`);
1287
+ if (outputPolicy.format === "json") {
1288
+ await emitJsonErrorEvent(normalized);
1289
+ return;
1290
+ }
1291
+ if (!outputPolicy.suppressNonJsonStderr) process.stderr.write(`${normalized.message}\n`);
1262
1292
  }
1263
1293
  async function runWithOutputPolicy(_outputPolicy, run) {
1264
1294
  return await run();
@@ -1287,13 +1317,13 @@ async function main(argv = process.argv) {
1287
1317
  includeBundledSkill: false
1288
1318
  });
1289
1319
  }
1290
- const config = await loadResolvedConfig(detectInitialCwd(argv.slice(2)));
1291
- const requestedJsonStrict = detectJsonStrict(argv.slice(2));
1320
+ const rawArgs = argv.slice(2);
1321
+ const config = await loadResolvedConfig(detectInitialCwd(rawArgs));
1322
+ const requestedJsonStrict = detectJsonStrict(rawArgs);
1292
1323
  const requestedOutputPolicy = {
1293
- ...resolveOutputPolicy(detectRequestedOutputFormat(argv.slice(2), config.format), requestedJsonStrict),
1294
- suppressReads: argv.slice(2).some((token) => token === "--suppress-reads")
1324
+ ...resolveOutputPolicy(detectRequestedOutputFormat(rawArgs, config.format), requestedJsonStrict),
1325
+ suppressReads: rawArgs.some((token) => token === "--suppress-reads")
1295
1326
  };
1296
- const builtInAgents = listBuiltInAgents(config.agents);
1297
1327
  const program = new Command();
1298
1328
  program.name("acpx").description("Headless CLI client for the Agent Client Protocol").version(getAcpxVersion()).enablePositionalOptions().showHelpAfterError();
1299
1329
  if (requestedJsonStrict) program.configureOutput({
@@ -1301,41 +1331,20 @@ async function main(argv = process.argv) {
1301
1331
  writeErr: () => {}
1302
1332
  });
1303
1333
  addGlobalFlags(program);
1304
- for (const agentName of builtInAgents) registerAgentCommand(program, agentName, config);
1305
- registerDefaultCommands(program, config);
1306
- const scan = detectAgentToken(argv.slice(2));
1307
- if (!scan.hasAgentOverride && scan.token && !TOP_LEVEL_VERBS.has(scan.token) && !builtInAgents.includes(scan.token)) registerAgentCommand(program, scan.token, config);
1308
- program.argument("[prompt...]", "Prompt text").action(async function(promptParts) {
1309
- if (promptParts.length === 0 && process.stdin.isTTY) {
1310
- if (requestedJsonStrict) throw new InvalidArgumentError("Prompt is required (pass as argument, --file, or pipe via stdin)");
1311
- this.outputHelp();
1312
- return;
1334
+ configurePublicCli({
1335
+ program,
1336
+ argv: rawArgs,
1337
+ config,
1338
+ requestedJsonStrict,
1339
+ topLevelVerbs: TOP_LEVEL_VERBS,
1340
+ listBuiltInAgents,
1341
+ detectAgentToken,
1342
+ registerAgentCommand,
1343
+ registerDefaultCommands,
1344
+ handlePromptAction: async (command, promptParts) => {
1345
+ await handlePrompt(void 0, promptParts, {}, command, config);
1313
1346
  }
1314
- await handlePrompt(void 0, promptParts, {}, this, config);
1315
1347
  });
1316
- program.addHelpText("after", `
1317
- Examples:
1318
- acpx pi "review recent changes"
1319
- acpx openclaw exec "summarize active session state"
1320
- acpx codex sessions new
1321
- acpx codex "fix the tests"
1322
- acpx codex prompt "fix the tests"
1323
- acpx codex --no-wait "queue follow-up task"
1324
- acpx codex exec "what does this repo do"
1325
- acpx codex cancel
1326
- acpx codex set-mode plan
1327
- acpx codex set thought_level high
1328
- acpx codex -s backend "fix the API"
1329
- acpx codex sessions
1330
- acpx codex sessions new --name backend
1331
- acpx codex sessions ensure --name backend
1332
- acpx codex sessions close backend
1333
- acpx codex status
1334
- acpx config show
1335
- acpx config init
1336
- acpx --ttl 30 codex "investigate flaky tests"
1337
- acpx claude "refactor auth"
1338
- acpx --agent ./my-custom-server "do something"`);
1339
1348
  program.exitOverride((error) => {
1340
1349
  throw error;
1341
1350
  });