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.
- package/dist/agent-registry-DGw0-3Tc.js +54 -0
- package/dist/agent-registry-DGw0-3Tc.js.map +1 -0
- package/dist/{cli-idpWyCOs.js → cli-CLRrs6eQ.js} +8 -12
- package/dist/cli-CLRrs6eQ.js.map +1 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1018 -1009
- package/dist/cli.js.map +1 -1
- package/dist/client-DLTWuu4w.d.ts +116 -0
- package/dist/client-DLTWuu4w.d.ts.map +1 -0
- package/dist/{flags-CCcX9fZj.js → flags-BmubjvOw.js} +5 -55
- package/dist/flags-BmubjvOw.js.map +1 -0
- package/dist/{flows-BL1tSvZT.js → flows-CR7xCmkR.js} +471 -281
- package/dist/flows-CR7xCmkR.js.map +1 -0
- package/dist/flows.d.ts +5 -9
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{queue-ipc-CE8_QGX3.js → ipc-DN6M4Ui9.js} +12 -571
- package/dist/ipc-DN6M4Ui9.js.map +1 -0
- package/dist/{acp-jsonrpc-BbBgC5gO.js → jsonrpc-M3y-qzy8.js} +2 -2
- package/dist/jsonrpc-M3y-qzy8.js.map +1 -0
- package/dist/{output-Du3m6oPQ.js → output-Di0M9Et8.js} +6 -6
- package/dist/output-Di0M9Et8.js.map +1 -0
- package/dist/perf-metrics-D9QC81lB.js +568 -0
- package/dist/perf-metrics-D9QC81lB.js.map +1 -0
- package/dist/{session-RO_LZUnv.js → prompt-turn-Bt8T3SRR.js} +2304 -3632
- package/dist/prompt-turn-Bt8T3SRR.js.map +1 -0
- package/dist/{output-render-Bz58qaQn.js → render-BL5ynRkN.js} +7 -6
- package/dist/render-BL5ynRkN.js.map +1 -0
- package/dist/runtime.d.ts +266 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +984 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-BbN0SBgf.js +1488 -0
- package/dist/session-BbN0SBgf.js.map +1 -0
- package/dist/{types-CeRKmEQ1.d.ts → types-DXxLBQc3.d.ts} +40 -3
- package/dist/types-DXxLBQc3.d.ts.map +1 -0
- package/package.json +5 -3
- package/dist/acp-jsonrpc-BbBgC5gO.js.map +0 -1
- package/dist/cli-idpWyCOs.js.map +0 -1
- package/dist/flags-CCcX9fZj.js.map +0 -1
- package/dist/flows-BL1tSvZT.js.map +0 -1
- package/dist/output-Du3m6oPQ.js.map +0 -1
- package/dist/output-render-Bz58qaQn.js.map +0 -1
- package/dist/queue-ipc-CE8_QGX3.js.map +0 -1
- package/dist/session-RO_LZUnv.js.map +0 -1
- 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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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/
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
139
|
-
return
|
|
116
|
+
function resolveRequestedOutputPolicy(globalFlags) {
|
|
117
|
+
return {
|
|
118
|
+
...resolveOutputPolicy(globalFlags.format, globalFlags.jsonStrict === true),
|
|
119
|
+
suppressReads: globalFlags.suppressReads === true
|
|
120
|
+
};
|
|
140
121
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
278
|
+
printCancelResultByFormat(await cancelSessionPrompt({
|
|
279
|
+
sessionId: record.acpxRecordId,
|
|
280
|
+
verbose: globalFlags.verbose
|
|
281
|
+
}), globalFlags.format);
|
|
195
282
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
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
|
-
|
|
360
|
-
|
|
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 (
|
|
365
|
-
process.stdout.write(`${
|
|
503
|
+
if (format === "quiet") {
|
|
504
|
+
for (const entry of visible) process.stdout.write(`${entry.textPreview}\n`);
|
|
366
505
|
return;
|
|
367
506
|
}
|
|
368
|
-
|
|
369
|
-
|
|
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(
|
|
512
|
+
for (const entry of visible) process.stdout.write(`${entry.timestamp}\t${entry.role}\t${entry.textPreview}\n`);
|
|
373
513
|
}
|
|
374
|
-
function
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
380
|
-
|
|
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
|
|
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:
|
|
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/
|
|
486
|
-
function
|
|
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
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
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
|
|
614
|
-
if (
|
|
615
|
-
return
|
|
636
|
+
function parseOptionalMcpServers(value, sourcePath, fieldName = "mcpServers") {
|
|
637
|
+
if (value === void 0) return;
|
|
638
|
+
return parseMcpServers(value, sourcePath, fieldName);
|
|
616
639
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
624
|
-
|
|
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
|
|
632
|
-
|
|
633
|
-
return outputModulePromise;
|
|
668
|
+
function isObject(value) {
|
|
669
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
634
670
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
640
|
-
|
|
641
|
-
|
|
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
|
|
644
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
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
|
|
742
|
-
if (
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
721
|
+
return parsed;
|
|
752
722
|
}
|
|
753
|
-
function
|
|
754
|
-
if (
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
731
|
+
return parsed;
|
|
767
732
|
}
|
|
768
|
-
function
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
|
|
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
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
|
881
|
-
const
|
|
882
|
-
const
|
|
883
|
-
const [
|
|
884
|
-
|
|
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
|
-
|
|
887
|
-
const
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/cli/config-command.ts
|
|
862
|
+
async function handleConfigShow(command, config) {
|
|
941
863
|
const globalFlags = resolveGlobalFlags(command, config);
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1034
|
-
const
|
|
1035
|
-
const
|
|
1036
|
-
if (format === "json") {
|
|
1037
|
-
process.stdout.write(`${JSON.stringify({
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
891
|
+
if (globalFlags.format === "quiet") {
|
|
892
|
+
process.stdout.write(`${result.path}\n`);
|
|
1048
893
|
return;
|
|
1049
894
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
899
|
+
process.stdout.write(`Config already exists: ${result.path}\n`);
|
|
1056
900
|
}
|
|
1057
|
-
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
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:
|
|
1076
|
-
|
|
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>",
|
|
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-
|
|
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")
|
|
1261
|
-
|
|
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
|
|
1291
|
-
const
|
|
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(
|
|
1294
|
-
suppressReads:
|
|
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
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
});
|