@openclaw/acpx 2026.5.2-beta.1 → 2026.5.3-beta.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/index.js +14 -0
- package/dist/register.runtime.js +108 -0
- package/dist/runtime-B3LWev_t.js +357 -0
- package/dist/runtime-api.js +4 -0
- package/dist/service-YhcC786_.js +665 -0
- package/dist/setup-api.js +16 -0
- package/package.json +33 -3
- package/AGENTS.md +0 -54
- package/index.test.ts +0 -119
- package/index.ts +0 -19
- package/register.runtime.ts +0 -154
- package/runtime-api.ts +0 -46
- package/setup-api.ts +0 -18
- package/src/acpx-runtime-compat.d.ts +0 -62
- package/src/claude-agent-acp-completion.test.ts +0 -129
- package/src/codex-auth-bridge.test.ts +0 -448
- package/src/codex-auth-bridge.ts +0 -385
- package/src/config-schema.ts +0 -117
- package/src/config.test.ts +0 -144
- package/src/config.ts +0 -273
- package/src/manifest.test.ts +0 -20
- package/src/runtime-internals/mcp-command-line.test.ts +0 -59
- package/src/runtime-internals/mcp-proxy.test.ts +0 -114
- package/src/runtime.test.ts +0 -816
- package/src/runtime.ts +0 -613
- package/src/service.test.ts +0 -401
- package/src/service.ts +0 -278
- package/tsconfig.json +0 -16
- /package/{src/runtime-internals → dist}/error-format.mjs +0 -0
- /package/{src/runtime-internals → dist}/mcp-command-line.mjs +0 -0
- /package/{src/runtime-internals → dist}/mcp-proxy.mjs +0 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "./runtime-api.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import { inspect } from "node:util";
|
|
6
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import fs$1 from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
|
|
11
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
12
|
+
//#region extensions/acpx/src/codex-auth-bridge.ts
|
|
13
|
+
const CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
|
|
14
|
+
const CODEX_ACP_PACKAGE_RANGE = "^0.12.0";
|
|
15
|
+
const CODEX_ACP_BIN = "codex-acp";
|
|
16
|
+
const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
|
|
17
|
+
const CLAUDE_ACP_PACKAGE_VERSION = "0.31.4";
|
|
18
|
+
const CLAUDE_ACP_BIN = "claude-agent-acp";
|
|
19
|
+
const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
|
|
20
|
+
const requireFromHere$1 = createRequire(import.meta.url);
|
|
21
|
+
function quoteCommandPart(value) {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
function splitCommandParts(value) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
let current = "";
|
|
27
|
+
let quote = null;
|
|
28
|
+
let escaping = false;
|
|
29
|
+
for (const ch of value) {
|
|
30
|
+
if (escaping) {
|
|
31
|
+
current += ch;
|
|
32
|
+
escaping = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ch === "\\" && quote !== "'") {
|
|
36
|
+
escaping = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (quote) {
|
|
40
|
+
if (ch === quote) quote = null;
|
|
41
|
+
else current += ch;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ch === "'" || ch === "\"") {
|
|
45
|
+
quote = ch;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (/\s/.test(ch)) {
|
|
49
|
+
if (current) {
|
|
50
|
+
parts.push(current);
|
|
51
|
+
current = "";
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
current += ch;
|
|
56
|
+
}
|
|
57
|
+
if (escaping) current += "\\";
|
|
58
|
+
if (current) parts.push(current);
|
|
59
|
+
return parts;
|
|
60
|
+
}
|
|
61
|
+
function basename(value) {
|
|
62
|
+
return value.split(/[\\/]/).pop() ?? value;
|
|
63
|
+
}
|
|
64
|
+
function resolvePackageBinPath(packageJsonPath, manifest, binName) {
|
|
65
|
+
const { bin } = manifest;
|
|
66
|
+
const relativeBinPath = typeof bin === "string" ? bin : bin && typeof bin === "object" ? bin[binName] : void 0;
|
|
67
|
+
if (typeof relativeBinPath !== "string" || relativeBinPath.trim() === "") return;
|
|
68
|
+
return path.resolve(path.dirname(packageJsonPath), relativeBinPath);
|
|
69
|
+
}
|
|
70
|
+
async function resolveInstalledAcpPackageBinPath(packageName, binName) {
|
|
71
|
+
try {
|
|
72
|
+
const packageJsonPath = requireFromHere$1.resolve(`${packageName}/package.json`);
|
|
73
|
+
const manifest = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
74
|
+
if (manifest.name !== packageName) return;
|
|
75
|
+
const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
|
|
76
|
+
if (!binPath) return;
|
|
77
|
+
await fs.access(binPath);
|
|
78
|
+
return binPath;
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function resolveInstalledCodexAcpBinPath() {
|
|
84
|
+
return await resolveInstalledAcpPackageBinPath(CODEX_ACP_PACKAGE, CODEX_ACP_BIN);
|
|
85
|
+
}
|
|
86
|
+
async function resolveInstalledClaudeAcpBinPath() {
|
|
87
|
+
return await resolveInstalledAcpPackageBinPath(CLAUDE_ACP_PACKAGE, CLAUDE_ACP_BIN);
|
|
88
|
+
}
|
|
89
|
+
function buildAdapterWrapperScript(params) {
|
|
90
|
+
return `#!/usr/bin/env node
|
|
91
|
+
import { existsSync } from "node:fs";
|
|
92
|
+
import path from "node:path";
|
|
93
|
+
import { spawn } from "node:child_process";
|
|
94
|
+
import { fileURLToPath } from "node:url";
|
|
95
|
+
|
|
96
|
+
${params.envSetup}
|
|
97
|
+
const configuredArgs = process.argv.slice(2);
|
|
98
|
+
|
|
99
|
+
function resolveNpmCliPath() {
|
|
100
|
+
const candidate = path.resolve(
|
|
101
|
+
path.dirname(process.execPath),
|
|
102
|
+
"..",
|
|
103
|
+
"lib",
|
|
104
|
+
"node_modules",
|
|
105
|
+
"npm",
|
|
106
|
+
"bin",
|
|
107
|
+
"npm-cli.js",
|
|
108
|
+
);
|
|
109
|
+
return existsSync(candidate) ? candidate : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const npmCliPath = resolveNpmCliPath();
|
|
113
|
+
const installedBinPath = ${params.installedBinPath ? quoteCommandPart(params.installedBinPath) : "undefined"};
|
|
114
|
+
let defaultCommand;
|
|
115
|
+
let defaultArgs;
|
|
116
|
+
if (installedBinPath) {
|
|
117
|
+
defaultCommand = process.execPath;
|
|
118
|
+
defaultArgs = [installedBinPath];
|
|
119
|
+
} else if (npmCliPath) {
|
|
120
|
+
defaultCommand = process.execPath;
|
|
121
|
+
defaultArgs = [npmCliPath, "exec", "--yes", "--package", "${params.packageSpec}", "--", "${params.binName}"];
|
|
122
|
+
} else {
|
|
123
|
+
defaultCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
124
|
+
defaultArgs = ["--yes", "--package", "${params.packageSpec}", "--", "${params.binName}"];
|
|
125
|
+
}
|
|
126
|
+
const command =
|
|
127
|
+
configuredArgs[0] === "${RUN_CONFIGURED_COMMAND_SENTINEL}" ? configuredArgs[1] : defaultCommand;
|
|
128
|
+
const args =
|
|
129
|
+
configuredArgs[0] === "${RUN_CONFIGURED_COMMAND_SENTINEL}"
|
|
130
|
+
? configuredArgs.slice(2)
|
|
131
|
+
: [...defaultArgs, ...configuredArgs];
|
|
132
|
+
|
|
133
|
+
if (!command) {
|
|
134
|
+
console.error("[openclaw] missing configured ${params.displayName} ACP command");
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const child = spawn(command, args, {
|
|
139
|
+
env,
|
|
140
|
+
stdio: "inherit",
|
|
141
|
+
windowsHide: true,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
145
|
+
process.once(signal, () => {
|
|
146
|
+
child.kill(signal);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
child.on("error", (error) => {
|
|
151
|
+
console.error(\`[openclaw] failed to launch ${params.displayName} ACP wrapper: \${error.message}\`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
child.on("exit", (code, signal) => {
|
|
156
|
+
if (code !== null) {
|
|
157
|
+
process.exit(code);
|
|
158
|
+
}
|
|
159
|
+
process.exit(signal ? 1 : 0);
|
|
160
|
+
});
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
function buildCodexAcpWrapperScript(installedBinPath) {
|
|
164
|
+
return buildAdapterWrapperScript({
|
|
165
|
+
displayName: "Codex",
|
|
166
|
+
packageSpec: `${CODEX_ACP_PACKAGE}@${CODEX_ACP_PACKAGE_RANGE}`,
|
|
167
|
+
binName: CODEX_ACP_BIN,
|
|
168
|
+
installedBinPath,
|
|
169
|
+
envSetup: `const codexHome = fileURLToPath(new URL("./codex-home/", import.meta.url));
|
|
170
|
+
const env = {
|
|
171
|
+
...process.env,
|
|
172
|
+
CODEX_HOME: codexHome,
|
|
173
|
+
};`
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function buildClaudeAcpWrapperScript(installedBinPath) {
|
|
177
|
+
return buildAdapterWrapperScript({
|
|
178
|
+
displayName: "Claude",
|
|
179
|
+
packageSpec: `${CLAUDE_ACP_PACKAGE}@${CLAUDE_ACP_PACKAGE_VERSION}`,
|
|
180
|
+
binName: CLAUDE_ACP_BIN,
|
|
181
|
+
installedBinPath,
|
|
182
|
+
envSetup: `const env = {
|
|
183
|
+
...process.env,
|
|
184
|
+
};`
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function prepareIsolatedCodexHome(baseDir) {
|
|
188
|
+
const codexHome = path.join(baseDir, "codex-home");
|
|
189
|
+
await fs.mkdir(codexHome, { recursive: true });
|
|
190
|
+
await fs.writeFile(path.join(codexHome, "config.toml"), "# Generated by OpenClaw for Codex ACP sessions.\n", "utf8");
|
|
191
|
+
return codexHome;
|
|
192
|
+
}
|
|
193
|
+
async function makeGeneratedWrapperExecutableIfPossible(wrapperPath) {
|
|
194
|
+
try {
|
|
195
|
+
await fs.chmod(wrapperPath, 493);
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
198
|
+
async function writeCodexAcpWrapper(baseDir, installedBinPath) {
|
|
199
|
+
await fs.mkdir(baseDir, { recursive: true });
|
|
200
|
+
const wrapperPath = path.join(baseDir, "codex-acp-wrapper.mjs");
|
|
201
|
+
await fs.writeFile(wrapperPath, buildCodexAcpWrapperScript(installedBinPath), { encoding: "utf8" });
|
|
202
|
+
await makeGeneratedWrapperExecutableIfPossible(wrapperPath);
|
|
203
|
+
return wrapperPath;
|
|
204
|
+
}
|
|
205
|
+
async function writeClaudeAcpWrapper(baseDir, installedBinPath) {
|
|
206
|
+
await fs.mkdir(baseDir, { recursive: true });
|
|
207
|
+
const wrapperPath = path.join(baseDir, "claude-agent-acp-wrapper.mjs");
|
|
208
|
+
await fs.writeFile(wrapperPath, buildClaudeAcpWrapperScript(installedBinPath), { encoding: "utf8" });
|
|
209
|
+
await makeGeneratedWrapperExecutableIfPossible(wrapperPath);
|
|
210
|
+
return wrapperPath;
|
|
211
|
+
}
|
|
212
|
+
function buildWrapperCommand(wrapperPath, args = []) {
|
|
213
|
+
return [
|
|
214
|
+
process.execPath,
|
|
215
|
+
wrapperPath,
|
|
216
|
+
...args
|
|
217
|
+
].map(quoteCommandPart).join(" ");
|
|
218
|
+
}
|
|
219
|
+
function isAcpPackageSpec(value, packageName) {
|
|
220
|
+
const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
221
|
+
return new RegExp(`^${escapedPackageName}(?:@.+)?$`, "i").test(value.trim());
|
|
222
|
+
}
|
|
223
|
+
function isAcpBinName(value, binName) {
|
|
224
|
+
const commandName = basename(value);
|
|
225
|
+
const escapedBinName = binName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
226
|
+
return new RegExp(`^${escapedBinName}(?:\\.exe|\\.[cm]?js)?$`, "i").test(commandName);
|
|
227
|
+
}
|
|
228
|
+
function isPackageRunnerCommand(value) {
|
|
229
|
+
return /^(?:npx|npm|pnpm|bunx)(?:\.cmd|\.exe)?$/i.test(basename(value));
|
|
230
|
+
}
|
|
231
|
+
function extractConfiguredAdapterArgs(params) {
|
|
232
|
+
const trimmedConfiguredCommand = params.configuredCommand?.trim();
|
|
233
|
+
if (!trimmedConfiguredCommand) return [];
|
|
234
|
+
const parts = splitCommandParts(trimmedConfiguredCommand);
|
|
235
|
+
if (!parts.length) return [];
|
|
236
|
+
const packageIndex = parts.findIndex((part) => isAcpPackageSpec(part, params.packageName));
|
|
237
|
+
if (packageIndex >= 0) {
|
|
238
|
+
if (!isPackageRunnerCommand(parts[0] ?? "")) return;
|
|
239
|
+
const afterPackage = parts.slice(packageIndex + 1);
|
|
240
|
+
if (afterPackage[0] === "--" && isAcpBinName(afterPackage[1] ?? "", params.binName)) return afterPackage.slice(2);
|
|
241
|
+
if (isAcpBinName(afterPackage[0] ?? "", params.binName)) return afterPackage.slice(1);
|
|
242
|
+
return afterPackage[0] === "--" ? afterPackage.slice(1) : afterPackage;
|
|
243
|
+
}
|
|
244
|
+
if (isAcpBinName(parts[0] ?? "", params.binName)) return parts.slice(1);
|
|
245
|
+
if (basename(parts[0] ?? "") === "node" && isAcpBinName(parts[1] ?? "", params.binName)) return parts.slice(2);
|
|
246
|
+
}
|
|
247
|
+
function buildCodexAcpWrapperCommand(wrapperPath, configuredCommand) {
|
|
248
|
+
const configuredAdapterArgs = extractConfiguredAdapterArgs({
|
|
249
|
+
configuredCommand,
|
|
250
|
+
packageName: CODEX_ACP_PACKAGE,
|
|
251
|
+
binName: CODEX_ACP_BIN
|
|
252
|
+
});
|
|
253
|
+
if (configuredAdapterArgs) return buildWrapperCommand(wrapperPath, configuredAdapterArgs);
|
|
254
|
+
return buildWrapperCommand(wrapperPath, [RUN_CONFIGURED_COMMAND_SENTINEL, ...splitCommandParts(configuredCommand?.trim() ?? "")]);
|
|
255
|
+
}
|
|
256
|
+
function buildClaudeAcpWrapperCommand(wrapperPath, configuredCommand) {
|
|
257
|
+
const configuredAdapterArgs = extractConfiguredAdapterArgs({
|
|
258
|
+
configuredCommand,
|
|
259
|
+
packageName: CLAUDE_ACP_PACKAGE,
|
|
260
|
+
binName: CLAUDE_ACP_BIN
|
|
261
|
+
});
|
|
262
|
+
if (configuredAdapterArgs) return buildWrapperCommand(wrapperPath, configuredAdapterArgs);
|
|
263
|
+
return configuredCommand?.trim() || buildWrapperCommand(wrapperPath);
|
|
264
|
+
}
|
|
265
|
+
async function prepareAcpxCodexAuthConfig(params) {
|
|
266
|
+
params.logger;
|
|
267
|
+
const codexBaseDir = path.join(params.stateDir, "acpx");
|
|
268
|
+
await prepareIsolatedCodexHome(codexBaseDir);
|
|
269
|
+
const installedCodexBinPath = await (params.resolveInstalledCodexAcpBinPath ?? resolveInstalledCodexAcpBinPath)();
|
|
270
|
+
const installedClaudeBinPath = await (params.resolveInstalledClaudeAcpBinPath ?? resolveInstalledClaudeAcpBinPath)();
|
|
271
|
+
const wrapperPath = await writeCodexAcpWrapper(codexBaseDir, installedCodexBinPath);
|
|
272
|
+
const claudeWrapperPath = await writeClaudeAcpWrapper(codexBaseDir, installedClaudeBinPath);
|
|
273
|
+
const configuredCodexCommand = params.pluginConfig.agents.codex;
|
|
274
|
+
const configuredClaudeCommand = params.pluginConfig.agents.claude;
|
|
275
|
+
return {
|
|
276
|
+
...params.pluginConfig,
|
|
277
|
+
agents: {
|
|
278
|
+
...params.pluginConfig.agents,
|
|
279
|
+
codex: buildCodexAcpWrapperCommand(wrapperPath, configuredCodexCommand),
|
|
280
|
+
claude: buildClaudeAcpWrapperCommand(claudeWrapperPath, configuredClaudeCommand)
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region extensions/acpx/src/config-schema.ts
|
|
286
|
+
const ACPX_PERMISSION_MODES = [
|
|
287
|
+
"approve-all",
|
|
288
|
+
"approve-reads",
|
|
289
|
+
"deny-all"
|
|
290
|
+
];
|
|
291
|
+
const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"];
|
|
292
|
+
const nonEmptyTrimmedString = (message) => z.string({ error: message }).trim().min(1, { error: message });
|
|
293
|
+
const McpServerConfigSchema = z.object({
|
|
294
|
+
command: nonEmptyTrimmedString("command must be a non-empty string").describe("Command to run the MCP server"),
|
|
295
|
+
args: z.array(z.string({ error: "args must be an array of strings" }), { error: "args must be an array of strings" }).optional().describe("Arguments to pass to the command"),
|
|
296
|
+
env: z.record(z.string(), z.string({ error: "env values must be strings" }), { error: "env must be an object of strings" }).optional().describe("Environment variables for the MCP server")
|
|
297
|
+
});
|
|
298
|
+
const AcpxPluginConfigSchema = z.strictObject({
|
|
299
|
+
cwd: nonEmptyTrimmedString("cwd must be a non-empty string").optional(),
|
|
300
|
+
stateDir: nonEmptyTrimmedString("stateDir must be a non-empty string").optional(),
|
|
301
|
+
probeAgent: nonEmptyTrimmedString("probeAgent must be a non-empty string").optional(),
|
|
302
|
+
permissionMode: z.enum(ACPX_PERMISSION_MODES, { error: `permissionMode must be one of: ${ACPX_PERMISSION_MODES.join(", ")}` }).optional(),
|
|
303
|
+
nonInteractivePermissions: z.enum(ACPX_NON_INTERACTIVE_POLICIES, { error: `nonInteractivePermissions must be one of: ${ACPX_NON_INTERACTIVE_POLICIES.join(", ")}` }).optional(),
|
|
304
|
+
pluginToolsMcpBridge: z.boolean({ error: "pluginToolsMcpBridge must be a boolean" }).optional(),
|
|
305
|
+
openClawToolsMcpBridge: z.boolean({ error: "openClawToolsMcpBridge must be a boolean" }).optional(),
|
|
306
|
+
strictWindowsCmdWrapper: z.boolean({ error: "strictWindowsCmdWrapper must be a boolean" }).optional(),
|
|
307
|
+
timeoutSeconds: z.number({ error: "timeoutSeconds must be a number >= 0.001" }).min(.001, { error: "timeoutSeconds must be a number >= 0.001" }).default(120),
|
|
308
|
+
queueOwnerTtlSeconds: z.number({ error: "queueOwnerTtlSeconds must be a number >= 0" }).min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" }).optional(),
|
|
309
|
+
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
|
|
310
|
+
agents: z.record(z.string(), z.strictObject({ command: nonEmptyTrimmedString("agents.<id>.command must be a non-empty string") })).optional()
|
|
311
|
+
});
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region extensions/acpx/src/config.ts
|
|
314
|
+
const ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME = "openclaw-plugin-tools";
|
|
315
|
+
const ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME = "openclaw-tools";
|
|
316
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
317
|
+
function isAcpxPluginRoot(dir) {
|
|
318
|
+
return fs$1.existsSync(path.join(dir, "openclaw.plugin.json")) && fs$1.existsSync(path.join(dir, "package.json"));
|
|
319
|
+
}
|
|
320
|
+
function resolveNearestAcpxPluginRoot(moduleUrl) {
|
|
321
|
+
let cursor = path.dirname(fileURLToPath(moduleUrl));
|
|
322
|
+
for (let i = 0; i < 3; i += 1) {
|
|
323
|
+
if (isAcpxPluginRoot(cursor)) return cursor;
|
|
324
|
+
const parent = path.dirname(cursor);
|
|
325
|
+
if (parent === cursor) break;
|
|
326
|
+
cursor = parent;
|
|
327
|
+
}
|
|
328
|
+
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), "..");
|
|
329
|
+
}
|
|
330
|
+
function resolveWorkspaceAcpxPluginRoot(currentRoot) {
|
|
331
|
+
if (path.basename(currentRoot) !== "acpx" || path.basename(path.dirname(currentRoot)) !== "extensions" || path.basename(path.dirname(path.dirname(currentRoot))) !== "dist") return null;
|
|
332
|
+
const workspaceRoot = path.resolve(currentRoot, "..", "..", "..", "extensions", "acpx");
|
|
333
|
+
return isAcpxPluginRoot(workspaceRoot) ? workspaceRoot : null;
|
|
334
|
+
}
|
|
335
|
+
function resolveRepoAcpxPluginRoot(currentRoot) {
|
|
336
|
+
const workspaceRoot = path.join(currentRoot, "extensions", "acpx");
|
|
337
|
+
return isAcpxPluginRoot(workspaceRoot) ? workspaceRoot : null;
|
|
338
|
+
}
|
|
339
|
+
function resolveAcpxPluginRootFromOpenClawLayout(moduleUrl) {
|
|
340
|
+
let cursor = path.dirname(fileURLToPath(moduleUrl));
|
|
341
|
+
for (let i = 0; i < 5; i += 1) {
|
|
342
|
+
const candidates = [
|
|
343
|
+
path.join(cursor, "extensions", "acpx"),
|
|
344
|
+
path.join(cursor, "dist", "extensions", "acpx"),
|
|
345
|
+
path.join(cursor, "dist-runtime", "extensions", "acpx")
|
|
346
|
+
];
|
|
347
|
+
for (const candidate of candidates) if (isAcpxPluginRoot(candidate)) return candidate;
|
|
348
|
+
const parent = path.dirname(cursor);
|
|
349
|
+
if (parent === cursor) break;
|
|
350
|
+
cursor = parent;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
function resolveAcpxPluginRoot(moduleUrl = import.meta.url) {
|
|
355
|
+
const resolvedRoot = resolveNearestAcpxPluginRoot(moduleUrl);
|
|
356
|
+
return resolveWorkspaceAcpxPluginRoot(resolvedRoot) ?? resolveRepoAcpxPluginRoot(resolvedRoot) ?? resolveAcpxPluginRootFromOpenClawLayout(moduleUrl) ?? resolvedRoot;
|
|
357
|
+
}
|
|
358
|
+
const DEFAULT_PERMISSION_MODE = "approve-reads";
|
|
359
|
+
const DEFAULT_NON_INTERACTIVE_POLICY = "fail";
|
|
360
|
+
const DEFAULT_QUEUE_OWNER_TTL_SECONDS = .1;
|
|
361
|
+
const DEFAULT_STRICT_WINDOWS_CMD_WRAPPER = true;
|
|
362
|
+
function parseAcpxPluginConfig(value) {
|
|
363
|
+
if (value === void 0) return {
|
|
364
|
+
ok: true,
|
|
365
|
+
value: void 0
|
|
366
|
+
};
|
|
367
|
+
const parsed = AcpxPluginConfigSchema.safeParse(value);
|
|
368
|
+
if (!parsed.success) return {
|
|
369
|
+
ok: false,
|
|
370
|
+
message: formatPluginConfigIssue(parsed.error.issues[0])
|
|
371
|
+
};
|
|
372
|
+
return {
|
|
373
|
+
ok: true,
|
|
374
|
+
value: parsed.data
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function resolveOpenClawRoot(currentRoot) {
|
|
378
|
+
if (path.basename(currentRoot) === "acpx" && path.basename(path.dirname(currentRoot)) === "extensions") {
|
|
379
|
+
const parent = path.dirname(path.dirname(currentRoot));
|
|
380
|
+
if (path.basename(parent) === "dist") return path.dirname(parent);
|
|
381
|
+
return parent;
|
|
382
|
+
}
|
|
383
|
+
return path.resolve(currentRoot, "..");
|
|
384
|
+
}
|
|
385
|
+
function resolveTsxImportSpecifier() {
|
|
386
|
+
try {
|
|
387
|
+
return requireFromHere.resolve("tsx");
|
|
388
|
+
} catch {
|
|
389
|
+
return "tsx";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function resolvePluginToolsMcpServerConfig(moduleUrl = import.meta.url) {
|
|
393
|
+
const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
|
|
394
|
+
const distEntry = path.join(openClawRoot, "dist", "mcp", "plugin-tools-serve.js");
|
|
395
|
+
if (fs$1.existsSync(distEntry)) return {
|
|
396
|
+
command: process.execPath,
|
|
397
|
+
args: [distEntry]
|
|
398
|
+
};
|
|
399
|
+
const sourceEntry = path.join(openClawRoot, "src", "mcp", "plugin-tools-serve.ts");
|
|
400
|
+
return {
|
|
401
|
+
command: process.execPath,
|
|
402
|
+
args: [
|
|
403
|
+
"--import",
|
|
404
|
+
resolveTsxImportSpecifier(),
|
|
405
|
+
sourceEntry
|
|
406
|
+
]
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function resolveOpenClawToolsMcpServerConfig(moduleUrl = import.meta.url) {
|
|
410
|
+
const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
|
|
411
|
+
const distEntry = path.join(openClawRoot, "dist", "mcp", "openclaw-tools-serve.js");
|
|
412
|
+
if (fs$1.existsSync(distEntry)) return {
|
|
413
|
+
command: process.execPath,
|
|
414
|
+
args: [distEntry]
|
|
415
|
+
};
|
|
416
|
+
const sourceEntry = path.join(openClawRoot, "src", "mcp", "openclaw-tools-serve.ts");
|
|
417
|
+
return {
|
|
418
|
+
command: process.execPath,
|
|
419
|
+
args: [
|
|
420
|
+
"--import",
|
|
421
|
+
resolveTsxImportSpecifier(),
|
|
422
|
+
sourceEntry
|
|
423
|
+
]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function resolveConfiguredMcpServers(params) {
|
|
427
|
+
const resolved = { ...params.mcpServers };
|
|
428
|
+
if (params.pluginToolsMcpBridge && resolved[ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME]) throw new Error(`mcpServers.${ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME} is reserved when pluginToolsMcpBridge=true`);
|
|
429
|
+
if (params.openClawToolsMcpBridge && resolved[ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME]) throw new Error(`mcpServers.${ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME} is reserved when openClawToolsMcpBridge=true`);
|
|
430
|
+
if (params.pluginToolsMcpBridge) resolved[ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME] = resolvePluginToolsMcpServerConfig(params.moduleUrl);
|
|
431
|
+
if (params.openClawToolsMcpBridge) resolved[ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME] = resolveOpenClawToolsMcpServerConfig(params.moduleUrl);
|
|
432
|
+
return resolved;
|
|
433
|
+
}
|
|
434
|
+
function toAcpMcpServers(mcpServers) {
|
|
435
|
+
return Object.entries(mcpServers).map(([name, server]) => ({
|
|
436
|
+
name,
|
|
437
|
+
command: server.command,
|
|
438
|
+
args: [...server.args ?? []],
|
|
439
|
+
env: Object.entries(server.env ?? {}).map(([envName, value]) => ({
|
|
440
|
+
name: envName,
|
|
441
|
+
value
|
|
442
|
+
}))
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
function resolveAcpxPluginConfig(params) {
|
|
446
|
+
const parsed = parseAcpxPluginConfig(params.rawConfig);
|
|
447
|
+
if (!parsed.ok) throw new Error(parsed.message);
|
|
448
|
+
const normalized = parsed.value ?? {};
|
|
449
|
+
const workspaceDir = params.workspaceDir?.trim() || process.cwd();
|
|
450
|
+
const fallbackCwd = workspaceDir;
|
|
451
|
+
const cwd = path.resolve(normalized.cwd?.trim() || fallbackCwd);
|
|
452
|
+
const stateDir = path.resolve(normalized.stateDir?.trim() || path.join(workspaceDir, "state"));
|
|
453
|
+
const pluginToolsMcpBridge = normalized.pluginToolsMcpBridge === true;
|
|
454
|
+
const openClawToolsMcpBridge = normalized.openClawToolsMcpBridge === true;
|
|
455
|
+
const mcpServers = resolveConfiguredMcpServers({
|
|
456
|
+
mcpServers: normalized.mcpServers,
|
|
457
|
+
pluginToolsMcpBridge,
|
|
458
|
+
openClawToolsMcpBridge,
|
|
459
|
+
moduleUrl: params.moduleUrl
|
|
460
|
+
});
|
|
461
|
+
const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) => [normalizeLowercaseStringOrEmpty(name), entry.command.trim()]));
|
|
462
|
+
return {
|
|
463
|
+
cwd,
|
|
464
|
+
stateDir,
|
|
465
|
+
probeAgent: normalizeLowercaseStringOrEmpty(normalized.probeAgent) || void 0,
|
|
466
|
+
permissionMode: normalized.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
467
|
+
nonInteractivePermissions: normalized.nonInteractivePermissions ?? DEFAULT_NON_INTERACTIVE_POLICY,
|
|
468
|
+
pluginToolsMcpBridge,
|
|
469
|
+
openClawToolsMcpBridge,
|
|
470
|
+
strictWindowsCmdWrapper: normalized.strictWindowsCmdWrapper ?? DEFAULT_STRICT_WINDOWS_CMD_WRAPPER,
|
|
471
|
+
timeoutSeconds: normalized.timeoutSeconds ?? 120,
|
|
472
|
+
queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds ?? DEFAULT_QUEUE_OWNER_TTL_SECONDS,
|
|
473
|
+
legacyCompatibilityConfig: {
|
|
474
|
+
strictWindowsCmdWrapper: normalized.strictWindowsCmdWrapper,
|
|
475
|
+
queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds
|
|
476
|
+
},
|
|
477
|
+
mcpServers,
|
|
478
|
+
agents
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region extensions/acpx/src/service.ts
|
|
483
|
+
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
|
|
484
|
+
const ACPX_BACKEND_ID = "acpx";
|
|
485
|
+
let runtimeModulePromise = null;
|
|
486
|
+
function loadRuntimeModule() {
|
|
487
|
+
runtimeModulePromise ??= import("./runtime-B3LWev_t.js");
|
|
488
|
+
return runtimeModulePromise;
|
|
489
|
+
}
|
|
490
|
+
function createLazyDefaultRuntime(params) {
|
|
491
|
+
let runtime = null;
|
|
492
|
+
let runtimePromise = null;
|
|
493
|
+
async function resolveRuntime() {
|
|
494
|
+
if (runtime) return runtime;
|
|
495
|
+
runtimePromise ??= loadRuntimeModule().then((module) => {
|
|
496
|
+
runtime = new module.AcpxRuntime({
|
|
497
|
+
cwd: params.pluginConfig.cwd,
|
|
498
|
+
sessionStore: module.createFileSessionStore({ stateDir: params.pluginConfig.stateDir }),
|
|
499
|
+
agentRegistry: module.createAgentRegistry({ overrides: params.pluginConfig.agents }),
|
|
500
|
+
probeAgent: params.pluginConfig.probeAgent,
|
|
501
|
+
mcpServers: toAcpMcpServers(params.pluginConfig.mcpServers),
|
|
502
|
+
permissionMode: params.pluginConfig.permissionMode,
|
|
503
|
+
nonInteractivePermissions: params.pluginConfig.nonInteractivePermissions,
|
|
504
|
+
timeoutMs: params.pluginConfig.timeoutSeconds != null ? params.pluginConfig.timeoutSeconds * 1e3 : void 0
|
|
505
|
+
});
|
|
506
|
+
return runtime;
|
|
507
|
+
});
|
|
508
|
+
return await runtimePromise;
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
async ensureSession(input) {
|
|
512
|
+
return await (await resolveRuntime()).ensureSession(input);
|
|
513
|
+
},
|
|
514
|
+
async *runTurn(input) {
|
|
515
|
+
yield* (await resolveRuntime()).runTurn(input);
|
|
516
|
+
},
|
|
517
|
+
async getCapabilities(input) {
|
|
518
|
+
return await (await resolveRuntime()).getCapabilities?.(input) ?? { controls: [] };
|
|
519
|
+
},
|
|
520
|
+
async getStatus(input) {
|
|
521
|
+
return await (await resolveRuntime()).getStatus?.(input) ?? {};
|
|
522
|
+
},
|
|
523
|
+
async setMode(input) {
|
|
524
|
+
await (await resolveRuntime()).setMode?.(input);
|
|
525
|
+
},
|
|
526
|
+
async setConfigOption(input) {
|
|
527
|
+
await (await resolveRuntime()).setConfigOption?.(input);
|
|
528
|
+
},
|
|
529
|
+
async doctor() {
|
|
530
|
+
return await (await resolveRuntime()).doctor?.() ?? {
|
|
531
|
+
ok: true,
|
|
532
|
+
message: "ok"
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
async prepareFreshSession(input) {
|
|
536
|
+
await (await resolveRuntime()).prepareFreshSession?.(input);
|
|
537
|
+
},
|
|
538
|
+
async cancel(input) {
|
|
539
|
+
await (await resolveRuntime()).cancel(input);
|
|
540
|
+
},
|
|
541
|
+
async close(input) {
|
|
542
|
+
await (await resolveRuntime()).close(input);
|
|
543
|
+
},
|
|
544
|
+
async probeAvailability() {
|
|
545
|
+
await (await resolveRuntime()).probeAvailability();
|
|
546
|
+
},
|
|
547
|
+
isHealthy() {
|
|
548
|
+
return runtime?.isHealthy() ?? false;
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function warnOnIgnoredLegacyCompatibilityConfig(params) {
|
|
553
|
+
const ignoredFields = [];
|
|
554
|
+
if (params.pluginConfig.legacyCompatibilityConfig.queueOwnerTtlSeconds != null) ignoredFields.push("queueOwnerTtlSeconds");
|
|
555
|
+
if (params.pluginConfig.legacyCompatibilityConfig.strictWindowsCmdWrapper === false) ignoredFields.push("strictWindowsCmdWrapper=false");
|
|
556
|
+
if (ignoredFields.length === 0) return;
|
|
557
|
+
params.logger?.warn(`embedded acpx runtime ignores legacy compatibility config: ${ignoredFields.join(", ")}`);
|
|
558
|
+
}
|
|
559
|
+
function formatDoctorDetail(detail) {
|
|
560
|
+
if (!detail) return null;
|
|
561
|
+
if (typeof detail === "string") return detail.trim() || null;
|
|
562
|
+
if (detail instanceof Error) return formatErrorMessage(detail);
|
|
563
|
+
if (typeof detail === "object") try {
|
|
564
|
+
return JSON.stringify(detail) ?? inspect(detail, {
|
|
565
|
+
breakLength: Infinity,
|
|
566
|
+
depth: 3
|
|
567
|
+
});
|
|
568
|
+
} catch {
|
|
569
|
+
return inspect(detail, {
|
|
570
|
+
breakLength: Infinity,
|
|
571
|
+
depth: 3
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (typeof detail === "number" || typeof detail === "boolean" || typeof detail === "bigint" || typeof detail === "symbol") return detail.toString();
|
|
575
|
+
return inspect(detail, {
|
|
576
|
+
breakLength: Infinity,
|
|
577
|
+
depth: 3
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function formatDoctorFailureMessage(report) {
|
|
581
|
+
const detailText = report.details?.map(formatDoctorDetail).filter(Boolean).join("; ").trim();
|
|
582
|
+
return detailText ? `${report.message} (${detailText})` : report.message;
|
|
583
|
+
}
|
|
584
|
+
function normalizeProbeAgent(value) {
|
|
585
|
+
const normalized = value?.trim().toLowerCase();
|
|
586
|
+
return normalized ? normalized : void 0;
|
|
587
|
+
}
|
|
588
|
+
function resolveAllowedAgentsProbeAgent(ctx) {
|
|
589
|
+
for (const agent of ctx.config.acp?.allowedAgents ?? []) {
|
|
590
|
+
const normalized = normalizeProbeAgent(agent);
|
|
591
|
+
if (normalized) return normalized;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function shouldRunStartupProbe(env = process.env) {
|
|
595
|
+
return env[ENABLE_STARTUP_PROBE_ENV] === "1";
|
|
596
|
+
}
|
|
597
|
+
function createAcpxRuntimeService(params = {}) {
|
|
598
|
+
let runtime = null;
|
|
599
|
+
let lifecycleRevision = 0;
|
|
600
|
+
return {
|
|
601
|
+
id: "acpx-runtime",
|
|
602
|
+
async start(ctx) {
|
|
603
|
+
if (process.env.OPENCLAW_SKIP_ACPX_RUNTIME === "1") {
|
|
604
|
+
ctx.logger.info("skipping embedded acpx runtime backend (OPENCLAW_SKIP_ACPX_RUNTIME=1)");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const basePluginConfig = resolveAcpxPluginConfig({
|
|
608
|
+
rawConfig: params.pluginConfig,
|
|
609
|
+
workspaceDir: ctx.workspaceDir
|
|
610
|
+
});
|
|
611
|
+
const pluginConfig = await prepareAcpxCodexAuthConfig({
|
|
612
|
+
pluginConfig: {
|
|
613
|
+
...basePluginConfig,
|
|
614
|
+
probeAgent: basePluginConfig.probeAgent ?? resolveAllowedAgentsProbeAgent(ctx)
|
|
615
|
+
},
|
|
616
|
+
stateDir: ctx.stateDir,
|
|
617
|
+
logger: ctx.logger
|
|
618
|
+
});
|
|
619
|
+
await fs.mkdir(pluginConfig.stateDir, { recursive: true });
|
|
620
|
+
warnOnIgnoredLegacyCompatibilityConfig({
|
|
621
|
+
pluginConfig,
|
|
622
|
+
logger: ctx.logger
|
|
623
|
+
});
|
|
624
|
+
runtime = params.runtimeFactory ? await params.runtimeFactory({
|
|
625
|
+
pluginConfig,
|
|
626
|
+
logger: ctx.logger
|
|
627
|
+
}) : createLazyDefaultRuntime({
|
|
628
|
+
pluginConfig,
|
|
629
|
+
logger: ctx.logger
|
|
630
|
+
});
|
|
631
|
+
registerAcpRuntimeBackend({
|
|
632
|
+
id: ACPX_BACKEND_ID,
|
|
633
|
+
runtime,
|
|
634
|
+
...shouldRunStartupProbe() ? { healthy: () => runtime?.isHealthy() ?? false } : {}
|
|
635
|
+
});
|
|
636
|
+
ctx.logger.info(`embedded acpx runtime backend registered (cwd: ${pluginConfig.cwd})`);
|
|
637
|
+
if (!shouldRunStartupProbe() || process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE === "1") return;
|
|
638
|
+
lifecycleRevision += 1;
|
|
639
|
+
const currentRevision = lifecycleRevision;
|
|
640
|
+
(async () => {
|
|
641
|
+
try {
|
|
642
|
+
await runtime?.probeAvailability();
|
|
643
|
+
if (currentRevision !== lifecycleRevision) return;
|
|
644
|
+
if (runtime?.isHealthy()) {
|
|
645
|
+
ctx.logger.info("embedded acpx runtime backend ready");
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const doctorReport = await runtime?.doctor?.();
|
|
649
|
+
if (currentRevision !== lifecycleRevision) return;
|
|
650
|
+
ctx.logger.warn(`embedded acpx runtime backend probe failed: ${doctorReport ? formatDoctorFailureMessage(doctorReport) : "backend remained unhealthy after probe"}`);
|
|
651
|
+
} catch (err) {
|
|
652
|
+
if (currentRevision !== lifecycleRevision) return;
|
|
653
|
+
ctx.logger.warn(`embedded acpx runtime setup failed: ${formatErrorMessage(err)}`);
|
|
654
|
+
}
|
|
655
|
+
})();
|
|
656
|
+
},
|
|
657
|
+
async stop(_ctx) {
|
|
658
|
+
lifecycleRevision += 1;
|
|
659
|
+
unregisterAcpRuntimeBackend(ACPX_BACKEND_ID);
|
|
660
|
+
runtime = null;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
export { createAcpxRuntimeService };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
3
|
+
//#region extensions/acpx/setup-api.ts
|
|
4
|
+
var setup_api_default = definePluginEntry({
|
|
5
|
+
id: "acpx",
|
|
6
|
+
name: "ACPX Setup",
|
|
7
|
+
description: "Lightweight ACPX setup hooks",
|
|
8
|
+
register(api) {
|
|
9
|
+
api.registerAutoEnableProbe(({ config }) => {
|
|
10
|
+
const backendRaw = normalizeLowercaseStringOrEmpty(config.acp?.backend);
|
|
11
|
+
return (config.acp?.enabled === true || config.acp?.dispatch?.enabled === true || backendRaw === "acpx") && (!backendRaw || backendRaw === "acpx") ? "ACP runtime configured" : null;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { setup_api_default as default };
|