@openclaw/acpx 2026.7.2-beta.5 → 2026.7.33
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/{process-lease-CU1cFI4I.js → .setup/process-lease-DiKkFj6F.mjs} +14 -4
- package/dist/{register.runtime-Dajn8myc.js → .setup/register.runtime-DjVBj71-.mjs} +1 -1
- package/dist/{runtime-CN4JQkdV.js → .setup/runtime-FxGnBh3l.mjs} +101 -137
- package/dist/{service-DOsKw_R1.js → .setup/service-CE4bgxXH.mjs} +586 -225
- package/dist/doctor-contract-api.js +1 -1
- package/dist/index.js +2 -1333
- package/dist/register.runtime.js +1 -1
- package/dist/setup-api.js +1 -1
- package/openclaw.plugin.json +3 -13
- package/package.json +8 -25
- package/skills/acp-router/SKILL.md +1 -1
- package/dist/config-schema-lrk5nlcV.js +0 -39
- package/dist/process-reaper-BIOtp_Ek.js +0 -513
package/dist/register.runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "
|
|
1
|
+
import { t as createAcpxRuntimeService } from "./.setup/register.runtime-DjVBj71-.mjs";
|
|
2
2
|
export { createAcpxRuntimeService };
|
package/dist/setup-api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
3
|
//#region extensions/acpx/setup-api.ts
|
|
4
4
|
/**
|
|
5
5
|
* ACPX setup plugin entry. It auto-enables setup when ACP config already points
|
package/openclaw.plugin.json
CHANGED
|
@@ -49,15 +49,9 @@
|
|
|
49
49
|
"type": "number",
|
|
50
50
|
"minimum": 0
|
|
51
51
|
},
|
|
52
|
-
"
|
|
53
|
-
"type": "
|
|
54
|
-
"
|
|
55
|
-
"properties": {
|
|
56
|
-
"enabled": {
|
|
57
|
-
"type": "boolean",
|
|
58
|
-
"default": true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
52
|
+
"probeAgent": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1
|
|
61
55
|
},
|
|
62
56
|
"mcpServers": {
|
|
63
57
|
"type": "object",
|
|
@@ -144,10 +138,6 @@
|
|
|
144
138
|
"help": "Reserved compatibility field for the older embedded ACPX queue-owner path. Accepted for compatibility and logged as ignored.",
|
|
145
139
|
"advanced": true
|
|
146
140
|
},
|
|
147
|
-
"piSessionCatalog.enabled": {
|
|
148
|
-
"label": "Pi Session Catalog",
|
|
149
|
-
"help": "Auto-detect Pi sessions on the Gateway and paired nodes, then show them in the sessions sidebar."
|
|
150
|
-
},
|
|
151
141
|
"probeAgent": {
|
|
152
142
|
"label": "Health Probe Agent",
|
|
153
143
|
"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the first `acp.allowedAgents` entry when that allowlist is set, otherwise to the runtime built-in probe agent (codex). Set this explicitly (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/acpx",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.33",
|
|
4
4
|
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@agentclientprotocol/claude-agent-acp": "0.
|
|
12
|
-
"@
|
|
13
|
-
"acpx": "0.
|
|
14
|
-
"smol-toml": "1.7.0",
|
|
11
|
+
"@agentclientprotocol/claude-agent-acp": "0.55.0",
|
|
12
|
+
"@zed-industries/codex-acp": "0.16.0",
|
|
13
|
+
"acpx": "0.11.2",
|
|
15
14
|
"zod": "4.4.3"
|
|
16
15
|
},
|
|
17
16
|
"devDependencies": {
|
|
@@ -24,29 +23,13 @@
|
|
|
24
23
|
"install": {
|
|
25
24
|
"npmSpec": "@openclaw/acpx",
|
|
26
25
|
"defaultChoice": "npm",
|
|
27
|
-
"minHostVersion": ">=2026.4.25"
|
|
28
|
-
"requiredPlatformPackages": [
|
|
29
|
-
"@anthropic-ai/claude-agent-sdk-linux-x64",
|
|
30
|
-
"@anthropic-ai/claude-agent-sdk-linux-arm64",
|
|
31
|
-
"@anthropic-ai/claude-agent-sdk-linux-x64-musl",
|
|
32
|
-
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl",
|
|
33
|
-
"@anthropic-ai/claude-agent-sdk-darwin-x64",
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk-darwin-arm64",
|
|
35
|
-
"@anthropic-ai/claude-agent-sdk-win32-x64",
|
|
36
|
-
"@anthropic-ai/claude-agent-sdk-win32-arm64",
|
|
37
|
-
"@openai/codex-linux-x64",
|
|
38
|
-
"@openai/codex-linux-arm64",
|
|
39
|
-
"@openai/codex-darwin-x64",
|
|
40
|
-
"@openai/codex-darwin-arm64",
|
|
41
|
-
"@openai/codex-win32-x64",
|
|
42
|
-
"@openai/codex-win32-arm64"
|
|
43
|
-
]
|
|
26
|
+
"minHostVersion": ">=2026.4.25"
|
|
44
27
|
},
|
|
45
28
|
"compat": {
|
|
46
|
-
"pluginApi": ">=2026.7.
|
|
29
|
+
"pluginApi": ">=2026.7.33"
|
|
47
30
|
},
|
|
48
31
|
"build": {
|
|
49
|
-
"openclawVersion": "2026.7.
|
|
32
|
+
"openclawVersion": "2026.7.33",
|
|
50
33
|
"staticAssets": [
|
|
51
34
|
{
|
|
52
35
|
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
|
@@ -74,7 +57,7 @@
|
|
|
74
57
|
"skills/**"
|
|
75
58
|
],
|
|
76
59
|
"peerDependencies": {
|
|
77
|
-
"openclaw": ">=2026.7.
|
|
60
|
+
"openclaw": ">=2026.7.33"
|
|
78
61
|
},
|
|
79
62
|
"peerDependenciesMeta": {
|
|
80
63
|
"openclaw": {
|
|
@@ -210,7 +210,7 @@ Defaults are:
|
|
|
210
210
|
|
|
211
211
|
- `openclaw -> openclaw acp`
|
|
212
212
|
- `claude -> bundled @agentclientprotocol/claude-agent-acp@0.55.0`
|
|
213
|
-
- `codex -> bundled @
|
|
213
|
+
- `codex -> bundled @zed-industries/codex-acp@0.16.0 through OpenClaw's isolated CODEX_HOME wrapper`
|
|
214
214
|
- `copilot -> copilot --acp --stdio`
|
|
215
215
|
- `cursor -> cursor-agent acp`
|
|
216
216
|
- `droid -> droid exec --output-format acp`
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
//#region extensions/acpx/src/config-schema.ts
|
|
3
|
-
/**
|
|
4
|
-
* ACPX plugin configuration schema and public config types. Runtime setup uses
|
|
5
|
-
* this file as the single source of truth for validation and defaulting.
|
|
6
|
-
*/
|
|
7
|
-
const ACPX_PERMISSION_MODES = [
|
|
8
|
-
"approve-all",
|
|
9
|
-
"approve-reads",
|
|
10
|
-
"deny-all"
|
|
11
|
-
];
|
|
12
|
-
const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"];
|
|
13
|
-
const nonEmptyTrimmedString = (message) => z.string({ error: message }).trim().min(1, { error: message });
|
|
14
|
-
const McpServerConfigSchema = z.object({
|
|
15
|
-
command: nonEmptyTrimmedString("command must be a non-empty string").describe("Command to run the MCP server"),
|
|
16
|
-
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"),
|
|
17
|
-
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")
|
|
18
|
-
});
|
|
19
|
-
/** Zod schema for validating raw ACPX plugin config from OpenClaw config. */
|
|
20
|
-
const AcpxPluginConfigSchema = z.strictObject({
|
|
21
|
-
cwd: nonEmptyTrimmedString("cwd must be a non-empty string").optional(),
|
|
22
|
-
stateDir: nonEmptyTrimmedString("stateDir must be a non-empty string").optional(),
|
|
23
|
-
probeAgent: nonEmptyTrimmedString("probeAgent must be a non-empty string").optional(),
|
|
24
|
-
permissionMode: z.enum(ACPX_PERMISSION_MODES, { error: `permissionMode must be one of: ${ACPX_PERMISSION_MODES.join(", ")}` }).optional(),
|
|
25
|
-
nonInteractivePermissions: z.enum(ACPX_NON_INTERACTIVE_POLICIES, { error: `nonInteractivePermissions must be one of: ${ACPX_NON_INTERACTIVE_POLICIES.join(", ")}` }).optional(),
|
|
26
|
-
pluginToolsMcpBridge: z.boolean({ error: "pluginToolsMcpBridge must be a boolean" }).optional(),
|
|
27
|
-
openClawToolsMcpBridge: z.boolean({ error: "openClawToolsMcpBridge must be a boolean" }).optional(),
|
|
28
|
-
strictWindowsCmdWrapper: z.boolean({ error: "strictWindowsCmdWrapper must be a boolean" }).optional(),
|
|
29
|
-
timeoutSeconds: z.number({ error: "timeoutSeconds must be a number >= 0.001" }).min(.001, { error: "timeoutSeconds must be a number >= 0.001" }).default(120),
|
|
30
|
-
queueOwnerTtlSeconds: z.number({ error: "queueOwnerTtlSeconds must be a number >= 0" }).min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" }).optional(),
|
|
31
|
-
piSessionCatalog: z.strictObject({ enabled: z.boolean({ error: "piSessionCatalog.enabled must be a boolean" }).default(true) }).optional(),
|
|
32
|
-
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
|
|
33
|
-
agents: z.record(z.string(), z.strictObject({
|
|
34
|
-
command: nonEmptyTrimmedString("agents.<id>.command must be a non-empty string"),
|
|
35
|
-
args: z.array(z.string({ error: "args must be an array of strings" })).optional()
|
|
36
|
-
})).optional()
|
|
37
|
-
});
|
|
38
|
-
//#endregion
|
|
39
|
-
export { AcpxPluginConfigSchema as t };
|
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
import { t as AcpxPluginConfigSchema } from "./config-schema-lrk5nlcV.js";
|
|
2
|
-
import "./process-lease-CU1cFI4I.js";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
|
|
5
|
-
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
//#region extensions/acpx/src/codex-adapter.ts
|
|
11
|
-
const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
|
|
12
|
-
const CODEX_ACP_BIN = "codex-acp";
|
|
13
|
-
const LEGACY_CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
|
|
14
|
-
const OPENCLAW_CODEX_CONFIG_ARG = "--openclaw-codex-config";
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region extensions/acpx/src/command-line.ts
|
|
17
|
-
/**
|
|
18
|
-
* Small shell-command helpers for ACPX-launched processes. Splitting supports
|
|
19
|
-
* simple quoted command strings from config without invoking a shell parser.
|
|
20
|
-
*/
|
|
21
|
-
/** Quote one command argument for display or config serialization. */
|
|
22
|
-
function quoteCommandPart(value) {
|
|
23
|
-
return JSON.stringify(value);
|
|
24
|
-
}
|
|
25
|
-
/** Split a command string into argv-like parts using simple quote/backslash rules. */
|
|
26
|
-
function splitCommandParts(value) {
|
|
27
|
-
const parts = [];
|
|
28
|
-
let current = "";
|
|
29
|
-
let quote = null;
|
|
30
|
-
let escaping = false;
|
|
31
|
-
for (const ch of value) {
|
|
32
|
-
if (escaping) {
|
|
33
|
-
current += ch;
|
|
34
|
-
escaping = false;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (ch === "\\" && quote !== "'") {
|
|
38
|
-
escaping = true;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (quote) {
|
|
42
|
-
if (ch === quote) quote = null;
|
|
43
|
-
else current += ch;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (ch === "'" || ch === "\"") {
|
|
47
|
-
quote = ch;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (/\s/.test(ch)) {
|
|
51
|
-
if (current) {
|
|
52
|
-
parts.push(current);
|
|
53
|
-
current = "";
|
|
54
|
-
}
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
current += ch;
|
|
58
|
-
}
|
|
59
|
-
if (escaping) current += "\\";
|
|
60
|
-
if (current) parts.push(current);
|
|
61
|
-
return parts;
|
|
62
|
-
}
|
|
63
|
-
//#endregion
|
|
64
|
-
//#region extensions/acpx/src/config.ts
|
|
65
|
-
/**
|
|
66
|
-
* Resolves ACPX plugin config from raw user configuration. It locates the
|
|
67
|
-
* plugin root, injects optional MCP bridge servers, and applies runtime defaults.
|
|
68
|
-
*/
|
|
69
|
-
const ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME = "openclaw-plugin-tools";
|
|
70
|
-
const ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME = "openclaw-tools";
|
|
71
|
-
const requireFromHere$1 = createRequire(import.meta.url);
|
|
72
|
-
function isAcpxPluginRoot(dir) {
|
|
73
|
-
return fs.existsSync(path.join(dir, "openclaw.plugin.json")) && fs.existsSync(path.join(dir, "package.json"));
|
|
74
|
-
}
|
|
75
|
-
function resolveNearestAcpxPluginRoot(moduleUrl) {
|
|
76
|
-
let cursor = path.dirname(fileURLToPath(moduleUrl));
|
|
77
|
-
for (let i = 0; i < 3; i += 1) {
|
|
78
|
-
if (isAcpxPluginRoot(cursor)) return cursor;
|
|
79
|
-
const parent = path.dirname(cursor);
|
|
80
|
-
if (parent === cursor) break;
|
|
81
|
-
cursor = parent;
|
|
82
|
-
}
|
|
83
|
-
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), "..");
|
|
84
|
-
}
|
|
85
|
-
function resolveWorkspaceAcpxPluginRoot(currentRoot) {
|
|
86
|
-
if (path.basename(currentRoot) !== "acpx" || path.basename(path.dirname(currentRoot)) !== "extensions" || path.basename(path.dirname(path.dirname(currentRoot))) !== "dist") return null;
|
|
87
|
-
const workspaceRoot = path.resolve(currentRoot, "..", "..", "..", "extensions", "acpx");
|
|
88
|
-
return isAcpxPluginRoot(workspaceRoot) ? workspaceRoot : null;
|
|
89
|
-
}
|
|
90
|
-
function resolveRepoAcpxPluginRoot(currentRoot) {
|
|
91
|
-
const workspaceRoot = path.join(currentRoot, "extensions", "acpx");
|
|
92
|
-
return isAcpxPluginRoot(workspaceRoot) ? workspaceRoot : null;
|
|
93
|
-
}
|
|
94
|
-
function resolveAcpxPluginRootFromOpenClawLayout(moduleUrl) {
|
|
95
|
-
let cursor = path.dirname(fileURLToPath(moduleUrl));
|
|
96
|
-
for (let i = 0; i < 5; i += 1) {
|
|
97
|
-
const candidates = [
|
|
98
|
-
path.join(cursor, "extensions", "acpx"),
|
|
99
|
-
path.join(cursor, "dist", "extensions", "acpx"),
|
|
100
|
-
path.join(cursor, "dist-runtime", "extensions", "acpx")
|
|
101
|
-
];
|
|
102
|
-
for (const candidate of candidates) if (isAcpxPluginRoot(candidate)) return candidate;
|
|
103
|
-
const parent = path.dirname(cursor);
|
|
104
|
-
if (parent === cursor) break;
|
|
105
|
-
cursor = parent;
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
/** Resolve the ACPX plugin root across source, dist, and dist-runtime layouts. */
|
|
110
|
-
function resolveAcpxPluginRoot(moduleUrl = import.meta.url) {
|
|
111
|
-
const resolvedRoot = resolveNearestAcpxPluginRoot(moduleUrl);
|
|
112
|
-
return resolveWorkspaceAcpxPluginRoot(resolvedRoot) ?? resolveRepoAcpxPluginRoot(resolvedRoot) ?? resolveAcpxPluginRootFromOpenClawLayout(moduleUrl) ?? resolvedRoot;
|
|
113
|
-
}
|
|
114
|
-
const DEFAULT_PERMISSION_MODE = "approve-reads";
|
|
115
|
-
const DEFAULT_NON_INTERACTIVE_POLICY = "fail";
|
|
116
|
-
const DEFAULT_QUEUE_OWNER_TTL_SECONDS = .1;
|
|
117
|
-
const DEFAULT_STRICT_WINDOWS_CMD_WRAPPER = true;
|
|
118
|
-
function parseAcpxPluginConfig(value) {
|
|
119
|
-
if (value === void 0) return {
|
|
120
|
-
ok: true,
|
|
121
|
-
value: void 0
|
|
122
|
-
};
|
|
123
|
-
const parsed = AcpxPluginConfigSchema.safeParse(value);
|
|
124
|
-
if (!parsed.success) return {
|
|
125
|
-
ok: false,
|
|
126
|
-
message: formatPluginConfigIssue(parsed.error.issues[0])
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
ok: true,
|
|
130
|
-
value: parsed.data
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
function resolveOpenClawRoot(currentRoot) {
|
|
134
|
-
if (path.basename(currentRoot) === "acpx" && path.basename(path.dirname(currentRoot)) === "extensions") {
|
|
135
|
-
const parent = path.dirname(path.dirname(currentRoot));
|
|
136
|
-
if (path.basename(parent) === "dist") return path.dirname(parent);
|
|
137
|
-
return parent;
|
|
138
|
-
}
|
|
139
|
-
return path.resolve(currentRoot, "..");
|
|
140
|
-
}
|
|
141
|
-
function resolveTsxImportSpecifier() {
|
|
142
|
-
try {
|
|
143
|
-
return requireFromHere$1.resolve("tsx");
|
|
144
|
-
} catch {
|
|
145
|
-
return "tsx";
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
function shellQuoteCommandArg(arg) {
|
|
149
|
-
if (!/[\s'"\\$|&;<>{}()*?[\]~`]/.test(arg)) return arg;
|
|
150
|
-
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
151
|
-
}
|
|
152
|
-
function resolvePluginToolsMcpServerConfig(moduleUrl = import.meta.url) {
|
|
153
|
-
const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
|
|
154
|
-
const distEntry = path.join(openClawRoot, "dist", "mcp", "plugin-tools-serve.js");
|
|
155
|
-
if (fs.existsSync(distEntry)) return {
|
|
156
|
-
command: process.execPath,
|
|
157
|
-
args: [distEntry]
|
|
158
|
-
};
|
|
159
|
-
const sourceEntry = path.join(openClawRoot, "src", "mcp", "plugin-tools-serve.ts");
|
|
160
|
-
return {
|
|
161
|
-
command: process.execPath,
|
|
162
|
-
args: [
|
|
163
|
-
"--import",
|
|
164
|
-
resolveTsxImportSpecifier(),
|
|
165
|
-
sourceEntry
|
|
166
|
-
]
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function resolveOpenClawToolsMcpServerConfig(moduleUrl = import.meta.url) {
|
|
170
|
-
const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
|
|
171
|
-
const distEntry = path.join(openClawRoot, "dist", "mcp", "openclaw-tools-serve.js");
|
|
172
|
-
if (fs.existsSync(distEntry)) return {
|
|
173
|
-
command: process.execPath,
|
|
174
|
-
args: [distEntry]
|
|
175
|
-
};
|
|
176
|
-
const sourceEntry = path.join(openClawRoot, "src", "mcp", "openclaw-tools-serve.ts");
|
|
177
|
-
return {
|
|
178
|
-
command: process.execPath,
|
|
179
|
-
args: [
|
|
180
|
-
"--import",
|
|
181
|
-
resolveTsxImportSpecifier(),
|
|
182
|
-
sourceEntry
|
|
183
|
-
]
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
function resolveConfiguredMcpServers(params) {
|
|
187
|
-
const resolved = { ...params.mcpServers };
|
|
188
|
-
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`);
|
|
189
|
-
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`);
|
|
190
|
-
if (params.pluginToolsMcpBridge) resolved[ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME] = resolvePluginToolsMcpServerConfig(params.moduleUrl);
|
|
191
|
-
if (params.openClawToolsMcpBridge) resolved[ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME] = resolveOpenClawToolsMcpServerConfig(params.moduleUrl);
|
|
192
|
-
return resolved;
|
|
193
|
-
}
|
|
194
|
-
/** Convert OpenClaw MCP server config into ACPX runtime MCP server entries. */
|
|
195
|
-
function toAcpMcpServers(mcpServers) {
|
|
196
|
-
return Object.entries(mcpServers).map(([name, server]) => ({
|
|
197
|
-
name,
|
|
198
|
-
command: server.command,
|
|
199
|
-
args: [...server.args ?? []],
|
|
200
|
-
env: Object.entries(server.env ?? {}).map(([envName, value]) => ({
|
|
201
|
-
name: envName,
|
|
202
|
-
value
|
|
203
|
-
}))
|
|
204
|
-
}));
|
|
205
|
-
}
|
|
206
|
-
/** Validate and normalize raw ACPX plugin config for runtime startup. */
|
|
207
|
-
function resolveAcpxPluginConfig(params) {
|
|
208
|
-
const parsed = parseAcpxPluginConfig(params.rawConfig);
|
|
209
|
-
if (!parsed.ok) throw new Error(parsed.message);
|
|
210
|
-
const normalized = parsed.value ?? {};
|
|
211
|
-
const workspaceDir = params.workspaceDir?.trim() || process.cwd();
|
|
212
|
-
const fallbackCwd = workspaceDir;
|
|
213
|
-
const cwd = path.resolve(normalized.cwd?.trim() || fallbackCwd);
|
|
214
|
-
const stateDir = path.resolve(normalized.stateDir?.trim() || path.join(workspaceDir, "state"));
|
|
215
|
-
const pluginToolsMcpBridge = normalized.pluginToolsMcpBridge === true;
|
|
216
|
-
const openClawToolsMcpBridge = normalized.openClawToolsMcpBridge === true;
|
|
217
|
-
const mcpServers = resolveConfiguredMcpServers({
|
|
218
|
-
mcpServers: normalized.mcpServers,
|
|
219
|
-
pluginToolsMcpBridge,
|
|
220
|
-
openClawToolsMcpBridge,
|
|
221
|
-
moduleUrl: params.moduleUrl
|
|
222
|
-
});
|
|
223
|
-
const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) => {
|
|
224
|
-
const cmd = entry.command.trim();
|
|
225
|
-
const cmdArgs = entry.args ?? [];
|
|
226
|
-
const fullCommand = cmdArgs.length > 0 ? `${cmd} ${cmdArgs.map(shellQuoteCommandArg).join(" ")}` : cmd;
|
|
227
|
-
return [normalizeLowercaseStringOrEmpty(name), fullCommand];
|
|
228
|
-
}));
|
|
229
|
-
return {
|
|
230
|
-
cwd,
|
|
231
|
-
stateDir,
|
|
232
|
-
probeAgent: normalized.probeAgent,
|
|
233
|
-
permissionMode: normalized.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
234
|
-
nonInteractivePermissions: normalized.nonInteractivePermissions ?? DEFAULT_NON_INTERACTIVE_POLICY,
|
|
235
|
-
pluginToolsMcpBridge,
|
|
236
|
-
openClawToolsMcpBridge,
|
|
237
|
-
strictWindowsCmdWrapper: normalized.strictWindowsCmdWrapper ?? DEFAULT_STRICT_WINDOWS_CMD_WRAPPER,
|
|
238
|
-
timeoutSeconds: normalized.timeoutSeconds ?? 120,
|
|
239
|
-
queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds ?? DEFAULT_QUEUE_OWNER_TTL_SECONDS,
|
|
240
|
-
legacyCompatibilityConfig: {
|
|
241
|
-
strictWindowsCmdWrapper: normalized.strictWindowsCmdWrapper,
|
|
242
|
-
queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds
|
|
243
|
-
},
|
|
244
|
-
mcpServers,
|
|
245
|
-
agents
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
//#endregion
|
|
249
|
-
//#region extensions/acpx/src/process-reaper.ts
|
|
250
|
-
/**
|
|
251
|
-
* ACPX process ownership checks and cleanup. The reaper only terminates
|
|
252
|
-
* OpenClaw-owned wrapper trees after validating paths, packages, and lease ids.
|
|
253
|
-
*/
|
|
254
|
-
const requireFromHere = createRequire(import.meta.url);
|
|
255
|
-
const GENERATED_WRAPPER_BASENAMES = /* @__PURE__ */ new Set(["codex-acp-wrapper.mjs", "claude-agent-acp-wrapper.mjs"]);
|
|
256
|
-
const OPENCLAW_PLUGIN_DEPS_MARKER = "/plugin-runtime-deps/";
|
|
257
|
-
const ACPX_PROCESS_LIST_TIMEOUT_MS = 2e3;
|
|
258
|
-
const OWNED_ACP_PACKAGE_NAMES = [
|
|
259
|
-
CODEX_ACP_PACKAGE,
|
|
260
|
-
LEGACY_CODEX_ACP_PACKAGE,
|
|
261
|
-
"@zed-industries/codex-acp-darwin-arm64",
|
|
262
|
-
"@zed-industries/codex-acp-darwin-x64",
|
|
263
|
-
"@zed-industries/codex-acp-linux-arm64",
|
|
264
|
-
"@zed-industries/codex-acp-linux-x64",
|
|
265
|
-
"@zed-industries/codex-acp-win32-arm64",
|
|
266
|
-
"@zed-industries/codex-acp-win32-x64",
|
|
267
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
268
|
-
"acpx"
|
|
269
|
-
];
|
|
270
|
-
const PLUGIN_DEPS_CODEX_PACKAGE_NAMES = [
|
|
271
|
-
"@openai/codex",
|
|
272
|
-
"@openai/codex-darwin-arm64",
|
|
273
|
-
"@openai/codex-darwin-x64",
|
|
274
|
-
"@openai/codex-linux-arm64",
|
|
275
|
-
"@openai/codex-linux-x64",
|
|
276
|
-
"@openai/codex-win32-arm64",
|
|
277
|
-
"@openai/codex-win32-x64"
|
|
278
|
-
];
|
|
279
|
-
const ACP_PACKAGE_MARKERS = [
|
|
280
|
-
...OWNED_ACP_PACKAGE_NAMES.map((packageName) => `/node_modules/${packageName}/`),
|
|
281
|
-
...PLUGIN_DEPS_CODEX_PACKAGE_NAMES.map((packageName) => `/node_modules/${packageName}/`),
|
|
282
|
-
"/acpx/dist/"
|
|
283
|
-
];
|
|
284
|
-
function normalizePathLike(value) {
|
|
285
|
-
return value.replaceAll("\\", "/");
|
|
286
|
-
}
|
|
287
|
-
function resolvePackageRoot(packageName) {
|
|
288
|
-
try {
|
|
289
|
-
return normalizePathLike(path.dirname(requireFromHere.resolve(`${packageName}/package.json`)));
|
|
290
|
-
} catch {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
function resolveOpenClawInstallRoot(pluginRoot) {
|
|
295
|
-
if (path.basename(pluginRoot) === "acpx" && path.basename(path.dirname(pluginRoot)) === "extensions") {
|
|
296
|
-
const parent = path.dirname(path.dirname(pluginRoot));
|
|
297
|
-
return path.basename(parent) === "dist" ? path.dirname(parent) : parent;
|
|
298
|
-
}
|
|
299
|
-
return path.resolve(pluginRoot, "..");
|
|
300
|
-
}
|
|
301
|
-
function resolveOwnedAcpPackageRootCandidates(packageName) {
|
|
302
|
-
const pluginRoot = resolveAcpxPluginRoot(import.meta.url);
|
|
303
|
-
const openClawRoot = resolveOpenClawInstallRoot(pluginRoot);
|
|
304
|
-
return [
|
|
305
|
-
resolvePackageRoot(packageName),
|
|
306
|
-
path.join(pluginRoot, "node_modules", packageName),
|
|
307
|
-
path.join(openClawRoot, "node_modules", packageName)
|
|
308
|
-
].flatMap((root) => root ? [normalizePathLike(root)] : []);
|
|
309
|
-
}
|
|
310
|
-
const OWNED_ACP_PACKAGE_ROOTS = Array.from(new Set(OWNED_ACP_PACKAGE_NAMES.flatMap(resolveOwnedAcpPackageRootCandidates)));
|
|
311
|
-
function commandBelongsToResolvedAcpPackage(command) {
|
|
312
|
-
return OWNED_ACP_PACKAGE_ROOTS.some((root) => command.includes(`${root}/`));
|
|
313
|
-
}
|
|
314
|
-
function commandMentionsGeneratedWrapper(command) {
|
|
315
|
-
return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => command.includes(basename));
|
|
316
|
-
}
|
|
317
|
-
function commandWrapperBelongsToRoot(command, wrapperRoot) {
|
|
318
|
-
if (!wrapperRoot) return true;
|
|
319
|
-
const normalizedCommand = normalizePathLike(command);
|
|
320
|
-
const normalizedRoot = normalizePathLike(wrapperRoot).replace(/\/+$/, "");
|
|
321
|
-
return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => normalizedCommand.includes(`${normalizedRoot}/${basename}`));
|
|
322
|
-
}
|
|
323
|
-
/** Check whether a command references an OpenClaw-generated ACPX wrapper path. */
|
|
324
|
-
function isOpenClawLeaseAwareAcpxProcessCommand(params) {
|
|
325
|
-
const command = params.command?.trim();
|
|
326
|
-
if (!command) return false;
|
|
327
|
-
const normalized = normalizePathLike(command);
|
|
328
|
-
return commandMentionsGeneratedWrapper(normalized) && commandWrapperBelongsToRoot(normalized, params.wrapperRoot);
|
|
329
|
-
}
|
|
330
|
-
function commandsReferToSameRootCommand(liveCommand, storedCommand) {
|
|
331
|
-
if (!storedCommand?.trim()) return true;
|
|
332
|
-
return normalizePathLike(liveCommand).trim() === normalizePathLike(storedCommand).trim();
|
|
333
|
-
}
|
|
334
|
-
function commandOptionEquals(parts, option, expected) {
|
|
335
|
-
if (!expected) return true;
|
|
336
|
-
const index = parts.indexOf(option);
|
|
337
|
-
return index >= 0 && parts[index + 1] === expected;
|
|
338
|
-
}
|
|
339
|
-
function liveCommandMatchesLeaseIdentity(params) {
|
|
340
|
-
if (!params.expectedLeaseId && !params.expectedGatewayInstanceId) return true;
|
|
341
|
-
const parts = splitCommandParts(params.command ?? "");
|
|
342
|
-
return commandOptionEquals(parts, "--openclaw-acpx-lease-id", params.expectedLeaseId) && commandOptionEquals(parts, "--openclaw-gateway-instance-id", params.expectedGatewayInstanceId);
|
|
343
|
-
}
|
|
344
|
-
/** Check whether a command is owned by OpenClaw ACPX runtime packages or wrappers. */
|
|
345
|
-
function isOpenClawOwnedAcpxProcessCommand(params) {
|
|
346
|
-
const command = params.command?.trim();
|
|
347
|
-
if (!command) return false;
|
|
348
|
-
const normalized = normalizePathLike(command);
|
|
349
|
-
if (isOpenClawLeaseAwareAcpxProcessCommand({
|
|
350
|
-
command: normalized,
|
|
351
|
-
wrapperRoot: params.wrapperRoot
|
|
352
|
-
})) return true;
|
|
353
|
-
if (commandBelongsToResolvedAcpPackage(normalized)) return true;
|
|
354
|
-
if (!normalized.includes(OPENCLAW_PLUGIN_DEPS_MARKER)) return false;
|
|
355
|
-
return ACP_PACKAGE_MARKERS.some((marker) => normalized.includes(marker));
|
|
356
|
-
}
|
|
357
|
-
function parseProcessList(stdout) {
|
|
358
|
-
const processes = [];
|
|
359
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
360
|
-
const match = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<command>.+?)\s*$/.exec(line);
|
|
361
|
-
const pid = match?.groups?.pid;
|
|
362
|
-
const ppid = match?.groups?.ppid;
|
|
363
|
-
const command = match?.groups?.command;
|
|
364
|
-
if (!pid || !ppid || !command) continue;
|
|
365
|
-
processes.push({
|
|
366
|
-
pid: Number.parseInt(pid, 10),
|
|
367
|
-
ppid: Number.parseInt(ppid, 10),
|
|
368
|
-
command
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
return processes;
|
|
372
|
-
}
|
|
373
|
-
/** List host processes in the compact shape needed by ACPX cleanup. */
|
|
374
|
-
async function listPlatformProcesses() {
|
|
375
|
-
if (process.platform === "win32") return [];
|
|
376
|
-
const { stdout } = await runExec("ps", ["-axo", "pid=,ppid=,command="], {
|
|
377
|
-
logOutput: false,
|
|
378
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
379
|
-
timeoutMs: ACPX_PROCESS_LIST_TIMEOUT_MS
|
|
380
|
-
});
|
|
381
|
-
return parseProcessList(stdout);
|
|
382
|
-
}
|
|
383
|
-
function collectProcessTree(processes, rootPid) {
|
|
384
|
-
const childrenByParent = /* @__PURE__ */ new Map();
|
|
385
|
-
for (const processInfo of processes) {
|
|
386
|
-
const children = childrenByParent.get(processInfo.ppid) ?? [];
|
|
387
|
-
children.push(processInfo);
|
|
388
|
-
childrenByParent.set(processInfo.ppid, children);
|
|
389
|
-
}
|
|
390
|
-
const root = new Map(processes.map((processInfo) => [processInfo.pid, processInfo])).get(rootPid);
|
|
391
|
-
const collected = [];
|
|
392
|
-
if (root) collected.push(root);
|
|
393
|
-
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
394
|
-
while (queue.length > 0) {
|
|
395
|
-
const next = queue.shift();
|
|
396
|
-
if (!next || collected.some((processInfo) => processInfo.pid === next.pid)) continue;
|
|
397
|
-
collected.push(next);
|
|
398
|
-
queue.push(...childrenByParent.get(next.pid) ?? []);
|
|
399
|
-
}
|
|
400
|
-
return collected;
|
|
401
|
-
}
|
|
402
|
-
function uniquePids(processes) {
|
|
403
|
-
return Array.from(new Set(processes.map((processInfo) => processInfo.pid).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid)));
|
|
404
|
-
}
|
|
405
|
-
function isProcessAlive(pid) {
|
|
406
|
-
try {
|
|
407
|
-
process.kill(pid, 0);
|
|
408
|
-
return true;
|
|
409
|
-
} catch {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
async function terminatePids(pids, deps) {
|
|
414
|
-
const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
|
|
415
|
-
const sleep = deps?.sleep ?? ((ms) => new Promise((resolve) => {
|
|
416
|
-
setTimeout(resolve, ms);
|
|
417
|
-
}));
|
|
418
|
-
const terminated = [];
|
|
419
|
-
for (const pid of pids) try {
|
|
420
|
-
killProcess(pid, "SIGTERM");
|
|
421
|
-
terminated.push(pid);
|
|
422
|
-
} catch {}
|
|
423
|
-
if (terminated.length === 0) return terminated;
|
|
424
|
-
await sleep(750);
|
|
425
|
-
for (const pid of terminated) if (deps?.killProcess || isProcessAlive(pid)) try {
|
|
426
|
-
killProcess(pid, "SIGKILL");
|
|
427
|
-
} catch {}
|
|
428
|
-
return terminated;
|
|
429
|
-
}
|
|
430
|
-
/** Terminate one validated OpenClaw-owned ACPX wrapper process tree. */
|
|
431
|
-
async function cleanupOpenClawOwnedAcpxProcessTree(params) {
|
|
432
|
-
const rootPid = params.rootPid;
|
|
433
|
-
if (!rootPid || rootPid <= 0 || rootPid === process.pid) return {
|
|
434
|
-
inspectedPids: [],
|
|
435
|
-
terminatedPids: [],
|
|
436
|
-
skippedReason: "missing-root"
|
|
437
|
-
};
|
|
438
|
-
let processes;
|
|
439
|
-
try {
|
|
440
|
-
processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
|
|
441
|
-
} catch {
|
|
442
|
-
processes = [];
|
|
443
|
-
}
|
|
444
|
-
const listedTree = collectProcessTree(processes, rootPid);
|
|
445
|
-
if (listedTree.length === 0) return {
|
|
446
|
-
inspectedPids: [],
|
|
447
|
-
terminatedPids: [],
|
|
448
|
-
skippedReason: "unverified-root"
|
|
449
|
-
};
|
|
450
|
-
const rootCommand = listedTree[0]?.command ?? params.rootCommand;
|
|
451
|
-
const liveCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(rootCommand ?? ""));
|
|
452
|
-
const storedCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(params.rootCommand ?? ""));
|
|
453
|
-
if (!liveCommandWasGeneratedWrapper && storedCommandWasGeneratedWrapper) return {
|
|
454
|
-
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
455
|
-
terminatedPids: [],
|
|
456
|
-
skippedReason: "not-openclaw-owned"
|
|
457
|
-
};
|
|
458
|
-
if (!liveCommandWasGeneratedWrapper && !commandsReferToSameRootCommand(rootCommand ?? "", params.rootCommand)) return {
|
|
459
|
-
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
460
|
-
terminatedPids: [],
|
|
461
|
-
skippedReason: "not-openclaw-owned"
|
|
462
|
-
};
|
|
463
|
-
if (!isOpenClawOwnedAcpxProcessCommand({
|
|
464
|
-
command: rootCommand,
|
|
465
|
-
wrapperRoot: params.wrapperRoot
|
|
466
|
-
})) return {
|
|
467
|
-
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
468
|
-
terminatedPids: [],
|
|
469
|
-
skippedReason: "not-openclaw-owned"
|
|
470
|
-
};
|
|
471
|
-
if (!liveCommandMatchesLeaseIdentity({
|
|
472
|
-
command: rootCommand,
|
|
473
|
-
expectedLeaseId: params.expectedLeaseId,
|
|
474
|
-
expectedGatewayInstanceId: params.expectedGatewayInstanceId
|
|
475
|
-
})) return {
|
|
476
|
-
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
477
|
-
terminatedPids: [],
|
|
478
|
-
skippedReason: "not-openclaw-owned"
|
|
479
|
-
};
|
|
480
|
-
const pids = uniquePids(listedTree.toReversed());
|
|
481
|
-
return {
|
|
482
|
-
inspectedPids: uniquePids(listedTree),
|
|
483
|
-
terminatedPids: await terminatePids(pids, params.deps)
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
/** Reap orphaned OpenClaw-owned ACPX wrapper trees during runtime startup. */
|
|
487
|
-
async function reapStaleOpenClawOwnedAcpxOrphans(params) {
|
|
488
|
-
if (process.platform === "win32") return {
|
|
489
|
-
inspectedPids: [],
|
|
490
|
-
terminatedPids: [],
|
|
491
|
-
skippedReason: "unsupported-platform"
|
|
492
|
-
};
|
|
493
|
-
let processes;
|
|
494
|
-
try {
|
|
495
|
-
processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
|
|
496
|
-
} catch {
|
|
497
|
-
return {
|
|
498
|
-
inspectedPids: [],
|
|
499
|
-
terminatedPids: [],
|
|
500
|
-
skippedReason: "process-list-unavailable"
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
const orphanTrees = processes.filter((processInfo) => processInfo.ppid === 1 && isOpenClawOwnedAcpxProcessCommand({
|
|
504
|
-
command: processInfo.command,
|
|
505
|
-
wrapperRoot: params.wrapperRoot
|
|
506
|
-
})).map((orphan) => collectProcessTree(processes, orphan.pid));
|
|
507
|
-
return {
|
|
508
|
-
inspectedPids: uniquePids(orphanTrees.flat()),
|
|
509
|
-
terminatedPids: await terminatePids(uniquePids(orphanTrees.flatMap((tree) => tree.toReversed())), params.deps)
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
//#endregion
|
|
513
|
-
export { resolveAcpxPluginRoot as a, splitCommandParts as c, LEGACY_CODEX_ACP_PACKAGE as d, OPENCLAW_CODEX_CONFIG_ARG as f, resolveAcpxPluginConfig as i, CODEX_ACP_BIN as l, isOpenClawLeaseAwareAcpxProcessCommand as n, toAcpMcpServers as o, reapStaleOpenClawOwnedAcpxOrphans as r, quoteCommandPart as s, cleanupOpenClawOwnedAcpxProcessTree as t, CODEX_ACP_PACKAGE as u };
|