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