@openclaw/acpx 2026.5.7 → 2026.5.9-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.
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
//#region extensions/acpx/src/process-lease.ts
|
|
8
|
+
const OPENCLAW_ACPX_LEASE_ID_ENV = "OPENCLAW_ACPX_LEASE_ID";
|
|
9
|
+
const OPENCLAW_GATEWAY_INSTANCE_ID_ENV = "OPENCLAW_GATEWAY_INSTANCE_ID";
|
|
10
|
+
const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
|
|
11
|
+
const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
|
|
12
|
+
const LEASE_FILE = "process-leases.json";
|
|
13
|
+
function normalizeLease(value) {
|
|
14
|
+
if (typeof value !== "object" || value === null) return;
|
|
15
|
+
const record = value;
|
|
16
|
+
if (typeof record.leaseId !== "string" || typeof record.gatewayInstanceId !== "string" || typeof record.sessionKey !== "string" || typeof record.wrapperRoot !== "string" || typeof record.wrapperPath !== "string" || typeof record.rootPid !== "number" || typeof record.commandHash !== "string" || typeof record.startedAt !== "number" || ![
|
|
17
|
+
"open",
|
|
18
|
+
"closing",
|
|
19
|
+
"closed",
|
|
20
|
+
"lost"
|
|
21
|
+
].includes(String(record.state))) return;
|
|
22
|
+
return {
|
|
23
|
+
leaseId: record.leaseId,
|
|
24
|
+
gatewayInstanceId: record.gatewayInstanceId,
|
|
25
|
+
sessionKey: record.sessionKey,
|
|
26
|
+
wrapperRoot: record.wrapperRoot,
|
|
27
|
+
wrapperPath: record.wrapperPath,
|
|
28
|
+
rootPid: record.rootPid,
|
|
29
|
+
...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
|
|
30
|
+
commandHash: record.commandHash,
|
|
31
|
+
startedAt: record.startedAt,
|
|
32
|
+
state: record.state
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function readLeaseFile(filePath) {
|
|
36
|
+
const { value } = await readJsonFileWithFallback(filePath, {
|
|
37
|
+
version: 1,
|
|
38
|
+
leases: []
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
version: 1,
|
|
42
|
+
leases: Array.isArray(value.leases) ? value.leases.map(normalizeLease).filter((lease) => !!lease) : []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function writeLeaseFile(filePath, value) {
|
|
46
|
+
return writeJsonFileAtomically(filePath, value);
|
|
47
|
+
}
|
|
48
|
+
function createAcpxProcessLeaseStore(params) {
|
|
49
|
+
const filePath = path.join(params.stateDir, LEASE_FILE);
|
|
50
|
+
let updateQueue = Promise.resolve();
|
|
51
|
+
async function update(mutator) {
|
|
52
|
+
const run = updateQueue.then(async () => {
|
|
53
|
+
await fs.mkdir(params.stateDir, { recursive: true });
|
|
54
|
+
await writeLeaseFile(filePath, {
|
|
55
|
+
version: 1,
|
|
56
|
+
leases: mutator((await readLeaseFile(filePath)).leases)
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
updateQueue = run.catch(() => {});
|
|
60
|
+
await run;
|
|
61
|
+
}
|
|
62
|
+
async function readCurrent() {
|
|
63
|
+
await updateQueue;
|
|
64
|
+
return await readLeaseFile(filePath);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
async load(leaseId) {
|
|
68
|
+
return (await readCurrent()).leases.find((lease) => lease.leaseId === leaseId);
|
|
69
|
+
},
|
|
70
|
+
async listOpen(gatewayInstanceId) {
|
|
71
|
+
return (await readCurrent()).leases.filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
|
|
72
|
+
},
|
|
73
|
+
async save(lease) {
|
|
74
|
+
await update((leases) => [...leases.filter((entry) => entry.leaseId !== lease.leaseId), lease]);
|
|
75
|
+
},
|
|
76
|
+
async markState(leaseId, state) {
|
|
77
|
+
await update((leases) => leases.map((lease) => lease.leaseId === leaseId ? {
|
|
78
|
+
...lease,
|
|
79
|
+
state
|
|
80
|
+
} : lease));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function createAcpxProcessLeaseId() {
|
|
85
|
+
return randomUUID();
|
|
86
|
+
}
|
|
87
|
+
function hashAcpxProcessCommand(command) {
|
|
88
|
+
return createHash("sha256").update(command).digest("hex");
|
|
89
|
+
}
|
|
90
|
+
function quoteEnvValue(value) {
|
|
91
|
+
return /^[A-Za-z0-9_./:=@+-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
92
|
+
}
|
|
93
|
+
function withAcpxLeaseEnvironment(params) {
|
|
94
|
+
if ((params.platform ?? process.platform) === "win32") return params.command;
|
|
95
|
+
return [
|
|
96
|
+
"env",
|
|
97
|
+
`${OPENCLAW_ACPX_LEASE_ID_ENV}=${quoteEnvValue(params.leaseId)}`,
|
|
98
|
+
`${OPENCLAW_GATEWAY_INSTANCE_ID_ENV}=${quoteEnvValue(params.gatewayInstanceId)}`,
|
|
99
|
+
params.command,
|
|
100
|
+
OPENCLAW_ACPX_LEASE_ID_ARG,
|
|
101
|
+
quoteEnvValue(params.leaseId),
|
|
102
|
+
OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
|
|
103
|
+
quoteEnvValue(params.gatewayInstanceId)
|
|
104
|
+
].join(" ");
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region extensions/acpx/src/process-reaper.ts
|
|
108
|
+
const execFileAsync = promisify(execFile);
|
|
109
|
+
const GENERATED_WRAPPER_BASENAMES = new Set(["codex-acp-wrapper.mjs", "claude-agent-acp-wrapper.mjs"]);
|
|
110
|
+
const OPENCLAW_PLUGIN_DEPS_MARKER = "/plugin-runtime-deps/";
|
|
111
|
+
const ACP_PACKAGE_MARKERS = [
|
|
112
|
+
"/@zed-industries/codex-acp/",
|
|
113
|
+
"/@agentclientprotocol/claude-agent-acp/",
|
|
114
|
+
"/acpx/dist/"
|
|
115
|
+
];
|
|
116
|
+
function normalizePathLike(value) {
|
|
117
|
+
return value.replaceAll("\\", "/");
|
|
118
|
+
}
|
|
119
|
+
function commandMentionsGeneratedWrapper(command) {
|
|
120
|
+
return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => command.includes(basename));
|
|
121
|
+
}
|
|
122
|
+
function commandWrapperBelongsToRoot(command, wrapperRoot) {
|
|
123
|
+
if (!wrapperRoot) return true;
|
|
124
|
+
const normalizedCommand = normalizePathLike(command);
|
|
125
|
+
const normalizedRoot = normalizePathLike(wrapperRoot).replace(/\/+$/, "");
|
|
126
|
+
return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => normalizedCommand.includes(`${normalizedRoot}/${basename}`));
|
|
127
|
+
}
|
|
128
|
+
function commandsReferToSameRootCommand(liveCommand, storedCommand) {
|
|
129
|
+
if (!storedCommand?.trim()) return true;
|
|
130
|
+
return normalizePathLike(liveCommand).trim() === normalizePathLike(storedCommand).trim();
|
|
131
|
+
}
|
|
132
|
+
function splitCommandParts(value) {
|
|
133
|
+
const parts = [];
|
|
134
|
+
let current = "";
|
|
135
|
+
let quote = null;
|
|
136
|
+
let escaping = false;
|
|
137
|
+
for (const ch of value) {
|
|
138
|
+
if (escaping) {
|
|
139
|
+
current += ch;
|
|
140
|
+
escaping = false;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ch === "\\" && quote !== "'") {
|
|
144
|
+
escaping = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (quote) {
|
|
148
|
+
if (ch === quote) quote = null;
|
|
149
|
+
else current += ch;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (ch === "'" || ch === "\"") {
|
|
153
|
+
quote = ch;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (/\s/.test(ch)) {
|
|
157
|
+
if (current) {
|
|
158
|
+
parts.push(current);
|
|
159
|
+
current = "";
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
current += ch;
|
|
164
|
+
}
|
|
165
|
+
if (escaping) current += "\\";
|
|
166
|
+
if (current) parts.push(current);
|
|
167
|
+
return parts;
|
|
168
|
+
}
|
|
169
|
+
function commandOptionEquals(parts, option, expected) {
|
|
170
|
+
if (!expected) return true;
|
|
171
|
+
const index = parts.indexOf(option);
|
|
172
|
+
return index >= 0 && parts[index + 1] === expected;
|
|
173
|
+
}
|
|
174
|
+
function liveCommandMatchesLeaseIdentity(params) {
|
|
175
|
+
if (!params.expectedLeaseId && !params.expectedGatewayInstanceId) return true;
|
|
176
|
+
const parts = splitCommandParts(params.command ?? "");
|
|
177
|
+
return commandOptionEquals(parts, "--openclaw-acpx-lease-id", params.expectedLeaseId) && commandOptionEquals(parts, "--openclaw-gateway-instance-id", params.expectedGatewayInstanceId);
|
|
178
|
+
}
|
|
179
|
+
function isOpenClawOwnedAcpxProcessCommand(params) {
|
|
180
|
+
const command = params.command?.trim();
|
|
181
|
+
if (!command) return false;
|
|
182
|
+
const normalized = normalizePathLike(command);
|
|
183
|
+
if (commandMentionsGeneratedWrapper(normalized)) return commandWrapperBelongsToRoot(normalized, params.wrapperRoot);
|
|
184
|
+
if (!normalized.includes(OPENCLAW_PLUGIN_DEPS_MARKER)) return false;
|
|
185
|
+
return ACP_PACKAGE_MARKERS.some((marker) => normalized.includes(marker));
|
|
186
|
+
}
|
|
187
|
+
function parseProcessList(stdout) {
|
|
188
|
+
const processes = [];
|
|
189
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
190
|
+
const match = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<command>.+?)\s*$/.exec(line);
|
|
191
|
+
if (!match?.groups) continue;
|
|
192
|
+
processes.push({
|
|
193
|
+
pid: Number.parseInt(match.groups.pid, 10),
|
|
194
|
+
ppid: Number.parseInt(match.groups.ppid, 10),
|
|
195
|
+
command: match.groups.command
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return processes;
|
|
199
|
+
}
|
|
200
|
+
async function listPlatformProcesses() {
|
|
201
|
+
if (process.platform === "win32") return [];
|
|
202
|
+
const { stdout } = await execFileAsync("ps", ["-axo", "pid=,ppid=,command="], { maxBuffer: 8 * 1024 * 1024 });
|
|
203
|
+
return parseProcessList(stdout);
|
|
204
|
+
}
|
|
205
|
+
function collectProcessTree(processes, rootPid) {
|
|
206
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
207
|
+
for (const processInfo of processes) {
|
|
208
|
+
const children = childrenByParent.get(processInfo.ppid) ?? [];
|
|
209
|
+
children.push(processInfo);
|
|
210
|
+
childrenByParent.set(processInfo.ppid, children);
|
|
211
|
+
}
|
|
212
|
+
const root = new Map(processes.map((processInfo) => [processInfo.pid, processInfo])).get(rootPid);
|
|
213
|
+
const collected = [];
|
|
214
|
+
if (root) collected.push(root);
|
|
215
|
+
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
216
|
+
while (queue.length > 0) {
|
|
217
|
+
const next = queue.shift();
|
|
218
|
+
if (!next || collected.some((processInfo) => processInfo.pid === next.pid)) continue;
|
|
219
|
+
collected.push(next);
|
|
220
|
+
queue.push(...childrenByParent.get(next.pid) ?? []);
|
|
221
|
+
}
|
|
222
|
+
return collected;
|
|
223
|
+
}
|
|
224
|
+
function uniquePids(processes) {
|
|
225
|
+
return Array.from(new Set(processes.map((processInfo) => processInfo.pid).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid)));
|
|
226
|
+
}
|
|
227
|
+
function isProcessAlive(pid) {
|
|
228
|
+
try {
|
|
229
|
+
process.kill(pid, 0);
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function terminatePids(pids, deps) {
|
|
236
|
+
const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
|
|
237
|
+
const sleep = deps?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
238
|
+
const terminated = [];
|
|
239
|
+
for (const pid of pids) try {
|
|
240
|
+
killProcess(pid, "SIGTERM");
|
|
241
|
+
terminated.push(pid);
|
|
242
|
+
} catch {}
|
|
243
|
+
if (terminated.length === 0) return terminated;
|
|
244
|
+
await sleep(750);
|
|
245
|
+
for (const pid of terminated) if (deps?.killProcess || isProcessAlive(pid)) try {
|
|
246
|
+
killProcess(pid, "SIGKILL");
|
|
247
|
+
} catch {}
|
|
248
|
+
return terminated;
|
|
249
|
+
}
|
|
250
|
+
async function cleanupOpenClawOwnedAcpxProcessTree(params) {
|
|
251
|
+
const rootPid = params.rootPid;
|
|
252
|
+
if (!rootPid || rootPid <= 0 || rootPid === process.pid) return {
|
|
253
|
+
inspectedPids: [],
|
|
254
|
+
terminatedPids: [],
|
|
255
|
+
skippedReason: "missing-root"
|
|
256
|
+
};
|
|
257
|
+
let processes = [];
|
|
258
|
+
try {
|
|
259
|
+
processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
|
|
260
|
+
} catch {
|
|
261
|
+
processes = [];
|
|
262
|
+
}
|
|
263
|
+
const listedTree = collectProcessTree(processes, rootPid);
|
|
264
|
+
if (listedTree.length === 0) return {
|
|
265
|
+
inspectedPids: [],
|
|
266
|
+
terminatedPids: [],
|
|
267
|
+
skippedReason: "unverified-root"
|
|
268
|
+
};
|
|
269
|
+
const rootCommand = listedTree[0]?.command ?? params.rootCommand;
|
|
270
|
+
const liveCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(rootCommand ?? ""));
|
|
271
|
+
const storedCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(params.rootCommand ?? ""));
|
|
272
|
+
if (!liveCommandWasGeneratedWrapper && storedCommandWasGeneratedWrapper) return {
|
|
273
|
+
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
274
|
+
terminatedPids: [],
|
|
275
|
+
skippedReason: "not-openclaw-owned"
|
|
276
|
+
};
|
|
277
|
+
if (!liveCommandWasGeneratedWrapper && !commandsReferToSameRootCommand(rootCommand ?? "", params.rootCommand)) return {
|
|
278
|
+
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
279
|
+
terminatedPids: [],
|
|
280
|
+
skippedReason: "not-openclaw-owned"
|
|
281
|
+
};
|
|
282
|
+
if (!isOpenClawOwnedAcpxProcessCommand({
|
|
283
|
+
command: rootCommand,
|
|
284
|
+
wrapperRoot: params.wrapperRoot
|
|
285
|
+
})) return {
|
|
286
|
+
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
287
|
+
terminatedPids: [],
|
|
288
|
+
skippedReason: "not-openclaw-owned"
|
|
289
|
+
};
|
|
290
|
+
if (!liveCommandMatchesLeaseIdentity({
|
|
291
|
+
command: rootCommand,
|
|
292
|
+
expectedLeaseId: params.expectedLeaseId,
|
|
293
|
+
expectedGatewayInstanceId: params.expectedGatewayInstanceId
|
|
294
|
+
})) return {
|
|
295
|
+
inspectedPids: listedTree.map((processInfo) => processInfo.pid),
|
|
296
|
+
terminatedPids: [],
|
|
297
|
+
skippedReason: "not-openclaw-owned"
|
|
298
|
+
};
|
|
299
|
+
const pids = uniquePids(listedTree.toReversed());
|
|
300
|
+
return {
|
|
301
|
+
inspectedPids: uniquePids(listedTree),
|
|
302
|
+
terminatedPids: await terminatePids(pids, params.deps)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async function reapStaleOpenClawOwnedAcpxOrphans(params) {
|
|
306
|
+
if (process.platform === "win32") return {
|
|
307
|
+
inspectedPids: [],
|
|
308
|
+
terminatedPids: [],
|
|
309
|
+
skippedReason: "unsupported-platform"
|
|
310
|
+
};
|
|
311
|
+
let processes;
|
|
312
|
+
try {
|
|
313
|
+
processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
|
|
314
|
+
} catch {
|
|
315
|
+
return {
|
|
316
|
+
inspectedPids: [],
|
|
317
|
+
terminatedPids: [],
|
|
318
|
+
skippedReason: "process-list-unavailable"
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const orphanTrees = processes.filter((processInfo) => processInfo.ppid === 1 && isOpenClawOwnedAcpxProcessCommand({
|
|
322
|
+
command: processInfo.command,
|
|
323
|
+
wrapperRoot: params.wrapperRoot
|
|
324
|
+
})).map((orphan) => collectProcessTree(processes, orphan.pid));
|
|
325
|
+
return {
|
|
326
|
+
inspectedPids: uniquePids(orphanTrees.flat()),
|
|
327
|
+
terminatedPids: await terminatePids(uniquePids(orphanTrees.flatMap((tree) => tree.toReversed())), params.deps)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
export { OPENCLAW_GATEWAY_INSTANCE_ID_ARG as a, hashAcpxProcessCommand as c, OPENCLAW_ACPX_LEASE_ID_ARG as i, withAcpxLeaseEnvironment as l, isOpenClawOwnedAcpxProcessCommand as n, createAcpxProcessLeaseId as o, reapStaleOpenClawOwnedAcpxOrphans as r, createAcpxProcessLeaseStore as s, cleanupOpenClawOwnedAcpxProcessTree as t };
|
package/dist/register.runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ const ACPX_BACKEND_ID = "acpx";
|
|
|
4
4
|
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
|
|
5
5
|
let serviceModulePromise = null;
|
|
6
6
|
function loadServiceModule() {
|
|
7
|
-
serviceModulePromise ??= import("./service-
|
|
7
|
+
serviceModulePromise ??= import("./service-DzKX5ybC.js");
|
|
8
8
|
return serviceModulePromise;
|
|
9
9
|
}
|
|
10
10
|
function shouldRunStartupProbe(env = process.env) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AcpRuntimeError } from "./runtime-api.js";
|
|
2
|
+
import { c as hashAcpxProcessCommand, l as withAcpxLeaseEnvironment, n as isOpenClawOwnedAcpxProcessCommand, o as createAcpxProcessLeaseId, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-DKzgrZt9.js";
|
|
2
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { resolve } from "node:path";
|
|
3
5
|
import { ACPX_BACKEND_ID, AcpxRuntime as AcpxRuntime$1, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState } from "acpx/runtime";
|
|
4
6
|
//#region extensions/acpx/src/runtime.ts
|
|
5
7
|
function readSessionRecordName(record) {
|
|
@@ -7,17 +9,94 @@ function readSessionRecordName(record) {
|
|
|
7
9
|
const { name } = record;
|
|
8
10
|
return typeof name === "string" ? name.trim() : "";
|
|
9
11
|
}
|
|
10
|
-
function
|
|
12
|
+
function readRecordAgentCommand(record) {
|
|
13
|
+
if (typeof record !== "object" || record === null) return;
|
|
14
|
+
const { agentCommand } = record;
|
|
15
|
+
return typeof agentCommand === "string" ? agentCommand.trim() || void 0 : void 0;
|
|
16
|
+
}
|
|
17
|
+
function readRecordCwd(record) {
|
|
18
|
+
if (typeof record !== "object" || record === null) return;
|
|
19
|
+
const { cwd } = record;
|
|
20
|
+
return typeof cwd === "string" ? cwd.trim() || void 0 : void 0;
|
|
21
|
+
}
|
|
22
|
+
function readRecordResetOnNextEnsure(record) {
|
|
23
|
+
if (typeof record !== "object" || record === null) return false;
|
|
24
|
+
const { acpx } = record;
|
|
25
|
+
if (typeof acpx !== "object" || acpx === null) return false;
|
|
26
|
+
return acpx.reset_on_next_ensure === true;
|
|
27
|
+
}
|
|
28
|
+
function readRecordAgentPid(record) {
|
|
29
|
+
if (typeof record !== "object" || record === null) return;
|
|
30
|
+
const { pid, processId } = record;
|
|
31
|
+
const rawPid = pid ?? processId;
|
|
32
|
+
const numericPid = typeof rawPid === "number" ? rawPid : typeof rawPid === "string" ? Number.parseInt(rawPid, 10) : void 0;
|
|
33
|
+
return numericPid && Number.isInteger(numericPid) && numericPid > 0 ? numericPid : void 0;
|
|
34
|
+
}
|
|
35
|
+
function readOpenClawLeaseIdFromRecord(record) {
|
|
36
|
+
if (typeof record !== "object" || record === null) return;
|
|
37
|
+
const { openclawLeaseId } = record;
|
|
38
|
+
return typeof openclawLeaseId === "string" ? openclawLeaseId.trim() || void 0 : void 0;
|
|
39
|
+
}
|
|
40
|
+
function extractGeneratedWrapperPath(command) {
|
|
41
|
+
return splitCommandParts(command ?? "").find((part) => basename(part) === "codex-acp-wrapper.mjs" || basename(part) === "claude-agent-acp-wrapper.mjs") ?? "";
|
|
42
|
+
}
|
|
43
|
+
function selectCurrentSessionLease(params) {
|
|
44
|
+
const sessionKeys = new Set(params.sessionKeys.map((entry) => entry.trim()).filter(Boolean));
|
|
45
|
+
const candidates = params.leases.filter((lease) => sessionKeys.has(lease.sessionKey));
|
|
46
|
+
if (params.rootPid) return candidates.find((lease) => lease.rootPid === params.rootPid);
|
|
47
|
+
let selected;
|
|
48
|
+
for (const lease of candidates) if (!selected || lease.startedAt > selected.startedAt) selected = lease;
|
|
49
|
+
return selected;
|
|
50
|
+
}
|
|
51
|
+
function createResetAwareSessionStore(baseStore, params) {
|
|
11
52
|
const freshSessionKeys = /* @__PURE__ */ new Set();
|
|
12
53
|
return {
|
|
13
54
|
async load(sessionId) {
|
|
14
55
|
const normalized = sessionId.trim();
|
|
15
56
|
if (normalized && freshSessionKeys.has(normalized)) return;
|
|
16
|
-
|
|
57
|
+
const record = await baseStore.load(sessionId);
|
|
58
|
+
if (!record || !params?.leaseStore || !params.gatewayInstanceId) return record;
|
|
59
|
+
const sessionName = readSessionRecordName(record) || normalized;
|
|
60
|
+
const lease = selectCurrentSessionLease({
|
|
61
|
+
leases: await params.leaseStore.listOpen(params.gatewayInstanceId),
|
|
62
|
+
sessionKeys: [sessionName, normalized],
|
|
63
|
+
rootPid: readRecordAgentPid(record)
|
|
64
|
+
});
|
|
65
|
+
if (!lease) return record;
|
|
66
|
+
return {
|
|
67
|
+
...record,
|
|
68
|
+
openclawLeaseId: lease.leaseId,
|
|
69
|
+
openclawGatewayInstanceId: lease.gatewayInstanceId
|
|
70
|
+
};
|
|
17
71
|
},
|
|
18
72
|
async save(record) {
|
|
19
|
-
|
|
73
|
+
let recordToSave = record;
|
|
74
|
+
const launch = params?.launchScope?.getStore();
|
|
20
75
|
const sessionName = readSessionRecordName(record);
|
|
76
|
+
const rootPid = readRecordAgentPid(record);
|
|
77
|
+
const agentCommand = readRecordAgentCommand(record);
|
|
78
|
+
const stableAgentCommand = launch?.stableCommand ?? agentCommand;
|
|
79
|
+
if (launch && params?.leaseStore && sessionName === launch.sessionKey && rootPid && stableAgentCommand) {
|
|
80
|
+
const lease = {
|
|
81
|
+
leaseId: launch.leaseId,
|
|
82
|
+
gatewayInstanceId: launch.gatewayInstanceId,
|
|
83
|
+
sessionKey: launch.sessionKey,
|
|
84
|
+
wrapperRoot: launch.wrapperRoot,
|
|
85
|
+
wrapperPath: extractGeneratedWrapperPath(stableAgentCommand),
|
|
86
|
+
rootPid,
|
|
87
|
+
commandHash: hashAcpxProcessCommand(stableAgentCommand),
|
|
88
|
+
startedAt: Date.now(),
|
|
89
|
+
state: "open"
|
|
90
|
+
};
|
|
91
|
+
await params.leaseStore.save(lease);
|
|
92
|
+
recordToSave = {
|
|
93
|
+
...record,
|
|
94
|
+
agentCommand: stableAgentCommand,
|
|
95
|
+
openclawLeaseId: launch.leaseId,
|
|
96
|
+
openclawGatewayInstanceId: launch.gatewayInstanceId
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
await baseStore.save(recordToSave);
|
|
21
100
|
if (sessionName) freshSessionKeys.delete(sessionName);
|
|
22
101
|
},
|
|
23
102
|
markFresh(sessionKey) {
|
|
@@ -67,9 +146,10 @@ function readAgentFromHandle(handle) {
|
|
|
67
146
|
return readAgentFromSessionKey(handle.sessionKey);
|
|
68
147
|
}
|
|
69
148
|
function readAgentCommandFromRecord(record) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
149
|
+
return readRecordAgentCommand(record);
|
|
150
|
+
}
|
|
151
|
+
function readAgentPidFromRecord(record) {
|
|
152
|
+
return readRecordAgentPid(record);
|
|
73
153
|
}
|
|
74
154
|
function splitCommandParts(value) {
|
|
75
155
|
const parts = [];
|
|
@@ -197,8 +277,8 @@ function createModelScopedAgentRegistry(params) {
|
|
|
197
277
|
resolve(agentName) {
|
|
198
278
|
const command = params.agentRegistry.resolve(agentName);
|
|
199
279
|
const override = params.scope.getStore();
|
|
200
|
-
if (!override || normalizeAgentName(agentName) !== CODEX_ACP_AGENT_ID || typeof command !== "string" || !isCodexAcpCommand(command)) return command;
|
|
201
|
-
return appendCodexAcpConfigOverrides(command, override);
|
|
280
|
+
if (!override || normalizeAgentName(agentName) !== CODEX_ACP_AGENT_ID || typeof command !== "string" || !isCodexAcpCommand(command)) return params.leaseCommand(command);
|
|
281
|
+
return params.leaseCommand(appendCodexAcpConfigOverrides(command, override));
|
|
202
282
|
},
|
|
203
283
|
list() {
|
|
204
284
|
return params.agentRegistry.list();
|
|
@@ -228,22 +308,34 @@ function shouldUseDistinctBridgeDelegate(options) {
|
|
|
228
308
|
var AcpxRuntime = class {
|
|
229
309
|
constructor(options, testOptions) {
|
|
230
310
|
this.codexAcpModelOverrideScope = new AsyncLocalStorage();
|
|
231
|
-
this.
|
|
311
|
+
this.launchLeaseScope = new AsyncLocalStorage();
|
|
312
|
+
const { openclawProcessCleanup, ...delegateTestOptions } = testOptions ?? {};
|
|
313
|
+
this.processCleanupDeps = openclawProcessCleanup;
|
|
314
|
+
this.wrapperRoot = options.openclawWrapperRoot;
|
|
315
|
+
this.gatewayInstanceId = options.openclawGatewayInstanceId;
|
|
316
|
+
this.processLeaseStore = options.openclawProcessLeaseStore;
|
|
317
|
+
this.cwd = options.cwd;
|
|
318
|
+
this.sessionStore = createResetAwareSessionStore(options.sessionStore, {
|
|
319
|
+
gatewayInstanceId: this.gatewayInstanceId,
|
|
320
|
+
leaseStore: this.processLeaseStore,
|
|
321
|
+
launchScope: this.launchLeaseScope
|
|
322
|
+
});
|
|
232
323
|
this.agentRegistry = options.agentRegistry;
|
|
233
324
|
this.scopedAgentRegistry = createModelScopedAgentRegistry({
|
|
234
325
|
agentRegistry: this.agentRegistry,
|
|
235
|
-
scope: this.codexAcpModelOverrideScope
|
|
326
|
+
scope: this.codexAcpModelOverrideScope,
|
|
327
|
+
leaseCommand: (command) => this.commandWithLaunchLease(command)
|
|
236
328
|
});
|
|
237
329
|
const sharedOptions = {
|
|
238
330
|
...options,
|
|
239
331
|
sessionStore: this.sessionStore,
|
|
240
332
|
agentRegistry: this.scopedAgentRegistry
|
|
241
333
|
};
|
|
242
|
-
this.delegate = new AcpxRuntime$1(sharedOptions,
|
|
334
|
+
this.delegate = new AcpxRuntime$1(sharedOptions, delegateTestOptions);
|
|
243
335
|
this.bridgeSafeDelegate = shouldUseDistinctBridgeDelegate(options) ? new AcpxRuntime$1({
|
|
244
336
|
...sharedOptions,
|
|
245
337
|
mcpServers: []
|
|
246
|
-
},
|
|
338
|
+
}, delegateTestOptions) : this.delegate;
|
|
247
339
|
this.probeDelegate = this.resolveDelegateForAgent(resolveProbeAgentName(options));
|
|
248
340
|
}
|
|
249
341
|
resolveDelegateForAgent(agentName) {
|
|
@@ -257,7 +349,11 @@ var AcpxRuntime = class {
|
|
|
257
349
|
return shouldUseBridgeSafeDelegateForCommand(command) ? this.bridgeSafeDelegate : this.delegate;
|
|
258
350
|
}
|
|
259
351
|
async resolveDelegateForHandle(handle) {
|
|
260
|
-
const
|
|
352
|
+
const record = await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
|
|
353
|
+
return this.resolveDelegateForLoadedRecord(handle, record);
|
|
354
|
+
}
|
|
355
|
+
resolveDelegateForLoadedRecord(handle, record) {
|
|
356
|
+
const recordCommand = readAgentCommandFromRecord(record);
|
|
261
357
|
if (recordCommand) return this.resolveDelegateForCommand(recordCommand);
|
|
262
358
|
return this.resolveDelegateForAgent(readAgentFromHandle(handle));
|
|
263
359
|
}
|
|
@@ -269,6 +365,87 @@ var AcpxRuntime = class {
|
|
|
269
365
|
agentRegistry: this.agentRegistry
|
|
270
366
|
});
|
|
271
367
|
}
|
|
368
|
+
commandWithLaunchLease(command) {
|
|
369
|
+
const launch = this.launchLeaseScope.getStore();
|
|
370
|
+
if (!command || !launch) return command;
|
|
371
|
+
launch.stableCommand = command;
|
|
372
|
+
return withAcpxLeaseEnvironment({
|
|
373
|
+
command,
|
|
374
|
+
leaseId: launch.leaseId,
|
|
375
|
+
gatewayInstanceId: launch.gatewayInstanceId
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
async canReuseStablePersistentSession(params) {
|
|
379
|
+
if (params.mode !== "persistent" || !params.command) return false;
|
|
380
|
+
const existing = await this.sessionStore.load(params.sessionKey);
|
|
381
|
+
if (!existing || readRecordResetOnNextEnsure(existing)) return false;
|
|
382
|
+
const recordCwd = readRecordCwd(existing);
|
|
383
|
+
if (!recordCwd || resolve(recordCwd) !== resolve(params.cwd?.trim() || this.cwd)) return false;
|
|
384
|
+
if (readRecordAgentCommand(existing) !== params.command) return false;
|
|
385
|
+
const existingSessionId = typeof existing === "object" && existing !== null ? existing.acpSessionId : void 0;
|
|
386
|
+
return !params.resumeSessionId || existingSessionId === params.resumeSessionId;
|
|
387
|
+
}
|
|
388
|
+
async runWithLaunchLease(params) {
|
|
389
|
+
if (params.enabled === false || !params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawOwnedAcpxProcessCommand({
|
|
390
|
+
command: params.command,
|
|
391
|
+
wrapperRoot: this.wrapperRoot
|
|
392
|
+
})) return await params.run();
|
|
393
|
+
const launch = {
|
|
394
|
+
leaseId: createAcpxProcessLeaseId(),
|
|
395
|
+
gatewayInstanceId: this.gatewayInstanceId,
|
|
396
|
+
sessionKey: params.sessionKey,
|
|
397
|
+
wrapperRoot: this.wrapperRoot,
|
|
398
|
+
stableCommand: params.command
|
|
399
|
+
};
|
|
400
|
+
await this.processLeaseStore.save({
|
|
401
|
+
leaseId: launch.leaseId,
|
|
402
|
+
gatewayInstanceId: launch.gatewayInstanceId,
|
|
403
|
+
sessionKey: launch.sessionKey,
|
|
404
|
+
wrapperRoot: launch.wrapperRoot,
|
|
405
|
+
wrapperPath: extractGeneratedWrapperPath(params.command),
|
|
406
|
+
rootPid: 0,
|
|
407
|
+
commandHash: hashAcpxProcessCommand(params.command),
|
|
408
|
+
startedAt: Date.now(),
|
|
409
|
+
state: "open"
|
|
410
|
+
});
|
|
411
|
+
return await this.launchLeaseScope.run(launch, params.run);
|
|
412
|
+
}
|
|
413
|
+
async cleanupProcessTreeForRecord(handle, record) {
|
|
414
|
+
const leaseId = readOpenClawLeaseIdFromRecord(record);
|
|
415
|
+
const rootPid = readAgentPidFromRecord(record);
|
|
416
|
+
const sessionKeys = [handle.sessionKey, readSessionRecordName(record)];
|
|
417
|
+
const selectedLease = selectCurrentSessionLease({
|
|
418
|
+
leases: this.gatewayInstanceId && this.processLeaseStore ? await this.processLeaseStore.listOpen(this.gatewayInstanceId) : [],
|
|
419
|
+
sessionKeys,
|
|
420
|
+
rootPid
|
|
421
|
+
});
|
|
422
|
+
const loadedLease = leaseId ? await this.processLeaseStore?.load(leaseId) : void 0;
|
|
423
|
+
const lease = selectedLease ?? (loadedLease && loadedLease.gatewayInstanceId === this.gatewayInstanceId && (!rootPid || loadedLease.rootPid === rootPid) && sessionKeys.includes(loadedLease.sessionKey) ? loadedLease : void 0);
|
|
424
|
+
if (lease && lease.gatewayInstanceId === this.gatewayInstanceId && lease.rootPid > 0) {
|
|
425
|
+
await this.processLeaseStore?.markState(lease.leaseId, "closing");
|
|
426
|
+
const result = await cleanupOpenClawOwnedAcpxProcessTree({
|
|
427
|
+
rootPid: lease.rootPid,
|
|
428
|
+
rootCommand: readAgentCommandFromRecord(record),
|
|
429
|
+
expectedLeaseId: lease.leaseId,
|
|
430
|
+
expectedGatewayInstanceId: lease.gatewayInstanceId,
|
|
431
|
+
wrapperRoot: lease.wrapperRoot,
|
|
432
|
+
deps: this.processCleanupDeps
|
|
433
|
+
});
|
|
434
|
+
await this.processLeaseStore?.markState(lease.leaseId, result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const rootCommand = readAgentCommandFromRecord(record) ?? resolveAgentCommandForName({
|
|
438
|
+
agentName: readAgentFromHandle(handle),
|
|
439
|
+
agentRegistry: this.agentRegistry
|
|
440
|
+
});
|
|
441
|
+
if (!rootPid || !rootCommand) return;
|
|
442
|
+
await cleanupOpenClawOwnedAcpxProcessTree({
|
|
443
|
+
rootPid,
|
|
444
|
+
rootCommand,
|
|
445
|
+
wrapperRoot: this.wrapperRoot,
|
|
446
|
+
deps: this.processCleanupDeps
|
|
447
|
+
});
|
|
448
|
+
}
|
|
272
449
|
isHealthy() {
|
|
273
450
|
return this.probeDelegate.isHealthy();
|
|
274
451
|
}
|
|
@@ -286,12 +463,30 @@ var AcpxRuntime = class {
|
|
|
286
463
|
});
|
|
287
464
|
const delegate = this.resolveDelegateForCommand(command);
|
|
288
465
|
const codexModelOverride = normalizeAgentName(input.agent) === CODEX_ACP_AGENT_ID && isCodexAcpCommand(command) ? normalizeCodexAcpModelOverride(input.model, input.thinking) : void 0;
|
|
289
|
-
|
|
466
|
+
const stableLaunchCommand = codexModelOverride && command ? appendCodexAcpConfigOverrides(command, codexModelOverride) : command;
|
|
467
|
+
const shouldStartWithLease = !await this.canReuseStablePersistentSession({
|
|
468
|
+
sessionKey: input.sessionKey,
|
|
469
|
+
mode: input.mode,
|
|
470
|
+
cwd: input.cwd,
|
|
471
|
+
command: stableLaunchCommand,
|
|
472
|
+
resumeSessionId: input.resumeSessionId
|
|
473
|
+
});
|
|
474
|
+
if (!codexModelOverride) return await this.runWithLaunchLease({
|
|
475
|
+
sessionKey: input.sessionKey,
|
|
476
|
+
command: stableLaunchCommand,
|
|
477
|
+
enabled: shouldStartWithLease,
|
|
478
|
+
run: () => delegate.ensureSession(input)
|
|
479
|
+
});
|
|
290
480
|
const normalizedInput = {
|
|
291
481
|
...input,
|
|
292
482
|
...codexAcpSessionModelId(codexModelOverride) ? { model: codexAcpSessionModelId(codexModelOverride) } : {}
|
|
293
483
|
};
|
|
294
|
-
return this.
|
|
484
|
+
return await this.runWithLaunchLease({
|
|
485
|
+
sessionKey: input.sessionKey,
|
|
486
|
+
command: stableLaunchCommand,
|
|
487
|
+
enabled: shouldStartWithLease,
|
|
488
|
+
run: () => this.codexAcpModelOverrideScope.run(codexModelOverride, () => delegate.ensureSession(normalizedInput))
|
|
489
|
+
});
|
|
295
490
|
}
|
|
296
491
|
async *runTurn(input) {
|
|
297
492
|
yield* (await this.resolveDelegateForHandle(input.handle)).runTurn(input);
|
|
@@ -332,18 +527,26 @@ var AcpxRuntime = class {
|
|
|
332
527
|
await delegate.setConfigOption(input);
|
|
333
528
|
}
|
|
334
529
|
async cancel(input) {
|
|
335
|
-
|
|
530
|
+
const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
|
|
531
|
+
await this.resolveDelegateForLoadedRecord(input.handle, record).cancel(input);
|
|
336
532
|
}
|
|
337
533
|
async prepareFreshSession(input) {
|
|
338
534
|
this.sessionStore.markFresh(input.sessionKey);
|
|
339
535
|
}
|
|
340
536
|
async close(input) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
537
|
+
const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
|
|
538
|
+
let closeSucceeded = false;
|
|
539
|
+
try {
|
|
540
|
+
await this.resolveDelegateForLoadedRecord(input.handle, record).close({
|
|
541
|
+
handle: input.handle,
|
|
542
|
+
reason: input.reason,
|
|
543
|
+
discardPersistentState: input.discardPersistentState
|
|
544
|
+
});
|
|
545
|
+
closeSucceeded = true;
|
|
546
|
+
} finally {
|
|
547
|
+
await this.cleanupProcessTreeForRecord(input.handle, record);
|
|
548
|
+
}
|
|
549
|
+
if (closeSucceeded && input.discardPersistentState) this.sessionStore.markFresh(input.handle.sessionKey);
|
|
347
550
|
}
|
|
348
551
|
};
|
|
349
552
|
const __testing = {
|
|
@@ -1,14 +1,146 @@
|
|
|
1
1
|
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "./runtime-api.js";
|
|
2
|
+
import { a as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, i as OPENCLAW_ACPX_LEASE_ID_ARG, r as reapStaleOpenClawOwnedAcpxOrphans, s as createAcpxProcessLeaseStore, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-DKzgrZt9.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
4
7
|
import fs from "node:fs/promises";
|
|
8
|
+
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
5
9
|
import { inspect } from "node:util";
|
|
6
10
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
11
|
import fsSync from "node:fs";
|
|
8
|
-
import
|
|
12
|
+
import os from "node:os";
|
|
9
13
|
import { fileURLToPath } from "node:url";
|
|
10
14
|
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
|
|
11
15
|
import { z } from "openclaw/plugin-sdk/zod";
|
|
16
|
+
//#region extensions/acpx/src/codex-trust-config.ts
|
|
17
|
+
function stripTomlComment(line) {
|
|
18
|
+
let quote = null;
|
|
19
|
+
let escaping = false;
|
|
20
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
21
|
+
const ch = line[index];
|
|
22
|
+
if (escaping) {
|
|
23
|
+
escaping = false;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (quote === "\"" && ch === "\\") {
|
|
27
|
+
escaping = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (quote) {
|
|
31
|
+
if (ch === quote) quote = null;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (ch === "'" || ch === "\"") {
|
|
35
|
+
quote = ch;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === "#") return line.slice(0, index);
|
|
39
|
+
}
|
|
40
|
+
return line;
|
|
41
|
+
}
|
|
42
|
+
function parseTomlString(value) {
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) try {
|
|
45
|
+
return JSON.parse(trimmed);
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
50
|
+
}
|
|
51
|
+
function parseTomlDottedKey(value) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
let current = "";
|
|
54
|
+
let quote = null;
|
|
55
|
+
let escaping = false;
|
|
56
|
+
for (const ch of value.trim()) {
|
|
57
|
+
if (escaping) {
|
|
58
|
+
current += ch;
|
|
59
|
+
escaping = false;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (quote === "\"" && ch === "\\") {
|
|
63
|
+
current += ch;
|
|
64
|
+
escaping = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (quote) {
|
|
68
|
+
current += ch;
|
|
69
|
+
if (ch === quote) quote = null;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === "'" || ch === "\"") {
|
|
73
|
+
quote = ch;
|
|
74
|
+
current += ch;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (ch === ".") {
|
|
78
|
+
parts.push(current.trim());
|
|
79
|
+
current = "";
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
current += ch;
|
|
83
|
+
}
|
|
84
|
+
if (current.trim()) parts.push(current.trim());
|
|
85
|
+
return parts.map((part) => parseTomlString(part) ?? part);
|
|
86
|
+
}
|
|
87
|
+
function parseProjectHeader(line) {
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]") || trimmed.startsWith("[[")) return;
|
|
90
|
+
const parts = parseTomlDottedKey(trimmed.slice(1, -1));
|
|
91
|
+
return parts.length === 2 && parts[0] === "projects" ? parts[1] : void 0;
|
|
92
|
+
}
|
|
93
|
+
function parseTrustedInlineProjectEntries(value) {
|
|
94
|
+
const trusted = [];
|
|
95
|
+
for (const match of value.matchAll(/(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*\{(?<body>[^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g)) {
|
|
96
|
+
const key = match.groups?.key;
|
|
97
|
+
const body = match.groups?.body;
|
|
98
|
+
if (!key || !body || !/\btrust_level\s*=\s*["']trusted["']/.test(body)) continue;
|
|
99
|
+
const projectPath = parseTomlString(key) ?? key.trim();
|
|
100
|
+
if (projectPath) trusted.push(projectPath);
|
|
101
|
+
}
|
|
102
|
+
return trusted;
|
|
103
|
+
}
|
|
104
|
+
function extractTrustedCodexProjectPaths(configToml) {
|
|
105
|
+
const trusted = /* @__PURE__ */ new Set();
|
|
106
|
+
let currentProjectPath;
|
|
107
|
+
let inProjectsTable = false;
|
|
108
|
+
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
109
|
+
const line = stripTomlComment(rawLine).trim();
|
|
110
|
+
if (!line) continue;
|
|
111
|
+
if (line.startsWith("[")) {
|
|
112
|
+
currentProjectPath = parseProjectHeader(line);
|
|
113
|
+
inProjectsTable = line === "[projects]";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (currentProjectPath && /^trust_level\s*=\s*["']trusted["']\s*$/.test(line)) {
|
|
117
|
+
trusted.add(currentProjectPath);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const assignment = /^(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*(?<value>.+)$/.exec(line);
|
|
121
|
+
if (!assignment?.groups) continue;
|
|
122
|
+
const key = parseTomlString(assignment.groups.key) ?? assignment.groups.key;
|
|
123
|
+
const value = assignment.groups.value.trim();
|
|
124
|
+
if (inProjectsTable && /^\{.*\}$/.test(value)) {
|
|
125
|
+
if (/\btrust_level\s*=\s*["']trusted["']/.test(value) && key) trusted.add(key);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (key === "projects" || inProjectsTable) for (const projectPath of parseTrustedInlineProjectEntries(value)) trusted.add(projectPath);
|
|
129
|
+
}
|
|
130
|
+
return Array.from(trusted);
|
|
131
|
+
}
|
|
132
|
+
function renderIsolatedCodexProjectTrustConfig(projectPaths) {
|
|
133
|
+
return [
|
|
134
|
+
"# Generated by OpenClaw for Codex ACP sessions.",
|
|
135
|
+
...Array.from(new Set(projectPaths.map((projectPath) => projectPath.trim()).filter(Boolean).map((projectPath) => path.resolve(projectPath)))).toSorted((left, right) => left.localeCompare(right)).flatMap((projectPath) => [
|
|
136
|
+
"",
|
|
137
|
+
`[projects.${JSON.stringify(projectPath)}]`,
|
|
138
|
+
"trust_level = \"trusted\""
|
|
139
|
+
]),
|
|
140
|
+
""
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
12
144
|
//#region extensions/acpx/src/config-schema.ts
|
|
13
145
|
const ACPX_PERMISSION_MODES = [
|
|
14
146
|
"approve-all",
|
|
@@ -34,7 +166,10 @@ const AcpxPluginConfigSchema = z.strictObject({
|
|
|
34
166
|
timeoutSeconds: z.number({ error: "timeoutSeconds must be a number >= 0.001" }).min(.001, { error: "timeoutSeconds must be a number >= 0.001" }).default(120),
|
|
35
167
|
queueOwnerTtlSeconds: z.number({ error: "queueOwnerTtlSeconds must be a number >= 0" }).min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" }).optional(),
|
|
36
168
|
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
|
|
37
|
-
agents: z.record(z.string(), z.strictObject({
|
|
169
|
+
agents: z.record(z.string(), z.strictObject({
|
|
170
|
+
command: nonEmptyTrimmedString("agents.<id>.command must be a non-empty string"),
|
|
171
|
+
args: z.array(z.string({ error: "args must be an array of strings" })).optional()
|
|
172
|
+
})).optional()
|
|
38
173
|
});
|
|
39
174
|
//#endregion
|
|
40
175
|
//#region extensions/acpx/src/config.ts
|
|
@@ -116,6 +251,10 @@ function resolveTsxImportSpecifier() {
|
|
|
116
251
|
return "tsx";
|
|
117
252
|
}
|
|
118
253
|
}
|
|
254
|
+
function shellQuoteCommandArg(arg) {
|
|
255
|
+
if (!/[\s'"\\$|&;<>{}()*?[\]~`]/.test(arg)) return arg;
|
|
256
|
+
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
257
|
+
}
|
|
119
258
|
function resolvePluginToolsMcpServerConfig(moduleUrl = import.meta.url) {
|
|
120
259
|
const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
|
|
121
260
|
const distEntry = path.join(openClawRoot, "dist", "mcp", "plugin-tools-serve.js");
|
|
@@ -185,7 +324,12 @@ function resolveAcpxPluginConfig(params) {
|
|
|
185
324
|
openClawToolsMcpBridge,
|
|
186
325
|
moduleUrl: params.moduleUrl
|
|
187
326
|
});
|
|
188
|
-
const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) =>
|
|
327
|
+
const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) => {
|
|
328
|
+
const cmd = entry.command.trim();
|
|
329
|
+
const cmdArgs = entry.args ?? [];
|
|
330
|
+
const fullCommand = cmdArgs.length > 0 ? `${cmd} ${cmdArgs.map(shellQuoteCommandArg).join(" ")}` : cmd;
|
|
331
|
+
return [normalizeLowercaseStringOrEmpty(name), fullCommand];
|
|
332
|
+
}));
|
|
189
333
|
return {
|
|
190
334
|
cwd,
|
|
191
335
|
stateDir,
|
|
@@ -276,7 +420,7 @@ function resolvePackageBinPath(packageJsonPath, manifest, binName) {
|
|
|
276
420
|
async function resolveInstalledAcpPackageBinPath(packageName, binName) {
|
|
277
421
|
try {
|
|
278
422
|
const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`);
|
|
279
|
-
const manifest =
|
|
423
|
+
const { value: manifest } = await readJsonFileWithFallback(packageJsonPath, {});
|
|
280
424
|
if (manifest.name !== packageName) return;
|
|
281
425
|
const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
|
|
282
426
|
if (!binPath) return;
|
|
@@ -300,7 +444,25 @@ import { spawn } from "node:child_process";
|
|
|
300
444
|
import { fileURLToPath } from "node:url";
|
|
301
445
|
|
|
302
446
|
${params.envSetup}
|
|
303
|
-
const
|
|
447
|
+
const openClawWrapperArgs = new Set([
|
|
448
|
+
${quoteCommandPart(OPENCLAW_ACPX_LEASE_ID_ARG)},
|
|
449
|
+
${quoteCommandPart(OPENCLAW_GATEWAY_INSTANCE_ID_ARG)},
|
|
450
|
+
]);
|
|
451
|
+
|
|
452
|
+
function stripOpenClawWrapperArgs(args) {
|
|
453
|
+
const stripped = [];
|
|
454
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
455
|
+
const value = args[index];
|
|
456
|
+
if (openClawWrapperArgs.has(value)) {
|
|
457
|
+
index += 1;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
stripped.push(value);
|
|
461
|
+
}
|
|
462
|
+
return stripped;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const configuredArgs = stripOpenClawWrapperArgs(process.argv.slice(2));
|
|
304
466
|
|
|
305
467
|
function resolveNpmCliPath() {
|
|
306
468
|
const candidate = path.resolve(
|
|
@@ -342,23 +504,78 @@ if (!command) {
|
|
|
342
504
|
}
|
|
343
505
|
|
|
344
506
|
const child = spawn(command, args, {
|
|
507
|
+
detached: process.platform !== "win32",
|
|
345
508
|
env,
|
|
346
509
|
stdio: "inherit",
|
|
347
510
|
windowsHide: true,
|
|
348
511
|
});
|
|
349
512
|
|
|
513
|
+
let forceKillTimer;
|
|
514
|
+
let orphanCleanupStarted = false;
|
|
515
|
+
|
|
516
|
+
function killChildTree(signal, options = {}) {
|
|
517
|
+
if (!child.pid || (!options.force && child.killed)) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (process.platform !== "win32") {
|
|
521
|
+
try {
|
|
522
|
+
// The adapter can spawn grandchildren; signaling the process group keeps
|
|
523
|
+
// the generated wrapper from leaving an ACP tree behind.
|
|
524
|
+
process.kill(-child.pid, signal);
|
|
525
|
+
return;
|
|
526
|
+
} catch {
|
|
527
|
+
// Fall back to direct child signaling below.
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
child.kill(signal);
|
|
531
|
+
}
|
|
532
|
+
|
|
350
533
|
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
351
534
|
process.once(signal, () => {
|
|
352
|
-
|
|
535
|
+
killChildTree(signal);
|
|
353
536
|
});
|
|
354
537
|
}
|
|
355
538
|
|
|
539
|
+
const originalParentPid = process.ppid;
|
|
540
|
+
const parentWatcher =
|
|
541
|
+
process.platform === "win32"
|
|
542
|
+
? undefined
|
|
543
|
+
: setInterval(() => {
|
|
544
|
+
if (process.ppid === originalParentPid || process.ppid !== 1) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (orphanCleanupStarted) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
orphanCleanupStarted = true;
|
|
551
|
+
if (parentWatcher) {
|
|
552
|
+
clearInterval(parentWatcher);
|
|
553
|
+
}
|
|
554
|
+
killChildTree("SIGTERM");
|
|
555
|
+
// Keep the wrapper alive long enough for stubborn adapters to receive
|
|
556
|
+
// a forced fallback signal after SIGTERM.
|
|
557
|
+
forceKillTimer = setTimeout(() => {
|
|
558
|
+
killChildTree("SIGKILL", { force: true });
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}, 1_500);
|
|
561
|
+
}, 1_000);
|
|
562
|
+
parentWatcher?.unref?.();
|
|
563
|
+
|
|
356
564
|
child.on("error", (error) => {
|
|
357
565
|
console.error(\`[openclaw] failed to launch ${params.displayName} ACP wrapper: \${error.message}\`);
|
|
358
566
|
process.exit(1);
|
|
359
567
|
});
|
|
360
568
|
|
|
361
569
|
child.on("exit", (code, signal) => {
|
|
570
|
+
if (parentWatcher) {
|
|
571
|
+
clearInterval(parentWatcher);
|
|
572
|
+
}
|
|
573
|
+
if (orphanCleanupStarted) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (forceKillTimer) {
|
|
577
|
+
clearTimeout(forceKillTimer);
|
|
578
|
+
}
|
|
362
579
|
if (code !== null) {
|
|
363
580
|
process.exit(code);
|
|
364
581
|
}
|
|
@@ -390,10 +607,20 @@ function buildClaudeAcpWrapperScript(installedBinPath) {
|
|
|
390
607
|
};`
|
|
391
608
|
});
|
|
392
609
|
}
|
|
393
|
-
async function
|
|
394
|
-
|
|
610
|
+
async function readSourceCodexConfig(codexHome) {
|
|
611
|
+
try {
|
|
612
|
+
return await fs.readFile(path.join(codexHome, "config.toml"), "utf8");
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (error.code === "ENOENT") return;
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async function prepareIsolatedCodexHome(params) {
|
|
619
|
+
const sourceConfig = await readSourceCodexConfig(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"));
|
|
620
|
+
const trustedProjectPaths = [...sourceConfig ? extractTrustedCodexProjectPaths(sourceConfig) : [], params.workspaceDir];
|
|
621
|
+
const codexHome = path.join(params.baseDir, "codex-home");
|
|
395
622
|
await fs.mkdir(codexHome, { recursive: true });
|
|
396
|
-
await fs.writeFile(path.join(codexHome, "config.toml"),
|
|
623
|
+
await fs.writeFile(path.join(codexHome, "config.toml"), renderIsolatedCodexProjectTrustConfig(trustedProjectPaths), "utf8");
|
|
397
624
|
return codexHome;
|
|
398
625
|
}
|
|
399
626
|
async function makeGeneratedWrapperExecutableIfPossible(wrapperPath) {
|
|
@@ -471,7 +698,10 @@ function buildClaudeAcpWrapperCommand(wrapperPath, configuredCommand) {
|
|
|
471
698
|
async function prepareAcpxCodexAuthConfig(params) {
|
|
472
699
|
params.logger;
|
|
473
700
|
const codexBaseDir = path.join(params.stateDir, "acpx");
|
|
474
|
-
await prepareIsolatedCodexHome(
|
|
701
|
+
await prepareIsolatedCodexHome({
|
|
702
|
+
baseDir: codexBaseDir,
|
|
703
|
+
workspaceDir: params.pluginConfig.cwd
|
|
704
|
+
});
|
|
475
705
|
const installedCodexBinPath = await (params.resolveInstalledCodexAcpBinPath ?? resolveInstalledCodexAcpBinPath)();
|
|
476
706
|
const installedClaudeBinPath = await (params.resolveInstalledClaudeAcpBinPath ?? resolveInstalledClaudeAcpBinPath)();
|
|
477
707
|
const wrapperPath = await writeCodexAcpWrapper(codexBaseDir, installedCodexBinPath);
|
|
@@ -493,7 +723,7 @@ const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
|
|
|
493
723
|
const ACPX_BACKEND_ID = "acpx";
|
|
494
724
|
let runtimeModulePromise = null;
|
|
495
725
|
function loadRuntimeModule() {
|
|
496
|
-
runtimeModulePromise ??= import("./runtime-
|
|
726
|
+
runtimeModulePromise ??= import("./runtime-QqpvXys6.js");
|
|
497
727
|
return runtimeModulePromise;
|
|
498
728
|
}
|
|
499
729
|
function createLazyDefaultRuntime(params) {
|
|
@@ -504,6 +734,9 @@ function createLazyDefaultRuntime(params) {
|
|
|
504
734
|
runtimePromise ??= loadRuntimeModule().then((module) => {
|
|
505
735
|
runtime = new module.AcpxRuntime({
|
|
506
736
|
cwd: params.pluginConfig.cwd,
|
|
737
|
+
openclawGatewayInstanceId: params.gatewayInstanceId,
|
|
738
|
+
openclawProcessLeaseStore: params.processLeaseStore,
|
|
739
|
+
openclawWrapperRoot: params.wrapperRoot,
|
|
507
740
|
sessionStore: module.createFileSessionStore({ stateDir: params.pluginConfig.stateDir }),
|
|
508
741
|
agentRegistry: module.createAgentRegistry({ overrides: params.pluginConfig.agents }),
|
|
509
742
|
probeAgent: params.pluginConfig.probeAgent,
|
|
@@ -603,6 +836,57 @@ function resolveAllowedAgentsProbeAgent(ctx) {
|
|
|
603
836
|
function shouldRunStartupProbe(env = process.env) {
|
|
604
837
|
return env[ENABLE_STARTUP_PROBE_ENV] === "1";
|
|
605
838
|
}
|
|
839
|
+
async function resolveGatewayInstanceId(stateDir) {
|
|
840
|
+
const filePath = path.join(stateDir, "gateway-instance-id");
|
|
841
|
+
try {
|
|
842
|
+
const existing = (await fs.readFile(filePath, "utf8")).trim();
|
|
843
|
+
if (existing) return existing;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (error.code !== "ENOENT") throw error;
|
|
846
|
+
}
|
|
847
|
+
const next = randomUUID();
|
|
848
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
849
|
+
await fs.writeFile(filePath, `${next}\n`, { mode: 384 });
|
|
850
|
+
return next;
|
|
851
|
+
}
|
|
852
|
+
async function reapOpenAcpxProcessLeases(params) {
|
|
853
|
+
const leases = await params.leaseStore.listOpen(params.gatewayInstanceId);
|
|
854
|
+
const inspectedPids = [];
|
|
855
|
+
const terminatedPids = [];
|
|
856
|
+
const pendingLeaseRootResults = /* @__PURE__ */ new Map();
|
|
857
|
+
for (const lease of leases) {
|
|
858
|
+
if (lease.rootPid <= 0) {
|
|
859
|
+
await params.leaseStore.markState(lease.leaseId, "closing");
|
|
860
|
+
let result = pendingLeaseRootResults.get(lease.wrapperRoot);
|
|
861
|
+
if (!result) {
|
|
862
|
+
result = await reapStaleOpenClawOwnedAcpxOrphans({
|
|
863
|
+
wrapperRoot: lease.wrapperRoot,
|
|
864
|
+
deps: params.deps
|
|
865
|
+
});
|
|
866
|
+
pendingLeaseRootResults.set(lease.wrapperRoot, result);
|
|
867
|
+
inspectedPids.push(...result.inspectedPids);
|
|
868
|
+
terminatedPids.push(...result.terminatedPids);
|
|
869
|
+
}
|
|
870
|
+
await params.leaseStore.markState(lease.leaseId, result.terminatedPids.length > 0 ? "closed" : "lost");
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
await params.leaseStore.markState(lease.leaseId, "closing");
|
|
874
|
+
const result = await cleanupOpenClawOwnedAcpxProcessTree({
|
|
875
|
+
rootPid: lease.rootPid,
|
|
876
|
+
expectedLeaseId: lease.leaseId,
|
|
877
|
+
expectedGatewayInstanceId: lease.gatewayInstanceId,
|
|
878
|
+
wrapperRoot: lease.wrapperRoot,
|
|
879
|
+
deps: params.deps
|
|
880
|
+
});
|
|
881
|
+
inspectedPids.push(...result.inspectedPids);
|
|
882
|
+
terminatedPids.push(...result.terminatedPids);
|
|
883
|
+
await params.leaseStore.markState(lease.leaseId, result.terminatedPids.length > 0 ? "closed" : "lost");
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
inspectedPids,
|
|
887
|
+
terminatedPids
|
|
888
|
+
};
|
|
889
|
+
}
|
|
606
890
|
function createAcpxRuntimeService(params = {}) {
|
|
607
891
|
let runtime = null;
|
|
608
892
|
let lifecycleRevision = 0;
|
|
@@ -625,16 +909,32 @@ function createAcpxRuntimeService(params = {}) {
|
|
|
625
909
|
stateDir: ctx.stateDir,
|
|
626
910
|
logger: ctx.logger
|
|
627
911
|
});
|
|
912
|
+
const wrapperRoot = path.join(ctx.stateDir, "acpx");
|
|
628
913
|
await fs.mkdir(pluginConfig.stateDir, { recursive: true });
|
|
914
|
+
await fs.mkdir(wrapperRoot, { recursive: true });
|
|
915
|
+
const gatewayInstanceId = await resolveGatewayInstanceId(ctx.stateDir);
|
|
916
|
+
const processLeaseStore = createAcpxProcessLeaseStore({ stateDir: wrapperRoot });
|
|
917
|
+
const startupReap = await reapOpenAcpxProcessLeases({
|
|
918
|
+
gatewayInstanceId,
|
|
919
|
+
leaseStore: processLeaseStore,
|
|
920
|
+
deps: params.processCleanupDeps
|
|
921
|
+
});
|
|
922
|
+
if (startupReap.terminatedPids.length > 0) ctx.logger.info(`reaped ${startupReap.terminatedPids.length} stale OpenClaw-owned ACPX process${startupReap.terminatedPids.length === 1 ? "" : "es"}`);
|
|
629
923
|
warnOnIgnoredLegacyCompatibilityConfig({
|
|
630
924
|
pluginConfig,
|
|
631
925
|
logger: ctx.logger
|
|
632
926
|
});
|
|
633
927
|
runtime = params.runtimeFactory ? await params.runtimeFactory({
|
|
634
928
|
pluginConfig,
|
|
929
|
+
gatewayInstanceId,
|
|
930
|
+
processLeaseStore,
|
|
931
|
+
wrapperRoot,
|
|
635
932
|
logger: ctx.logger
|
|
636
933
|
}) : createLazyDefaultRuntime({
|
|
637
934
|
pluginConfig,
|
|
935
|
+
gatewayInstanceId,
|
|
936
|
+
processLeaseStore,
|
|
937
|
+
wrapperRoot,
|
|
638
938
|
logger: ctx.logger
|
|
639
939
|
});
|
|
640
940
|
registerAcpRuntimeBackend({
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/acpx",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.9-beta.1",
|
|
4
4
|
"description": "OpenClaw ACP runtime backend",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@agentclientprotocol/claude-agent-acp": "0.32.0",
|
|
12
12
|
"@zed-industries/codex-acp": "0.13.0",
|
|
13
|
-
"acpx": "0.
|
|
13
|
+
"acpx": "0.7.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@openclaw/plugin-sdk": "workspace:*"
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"minHostVersion": ">=2026.4.25"
|
|
26
26
|
},
|
|
27
27
|
"compat": {
|
|
28
|
-
"pluginApi": ">=2026.5.
|
|
28
|
+
"pluginApi": ">=2026.5.9-beta.1"
|
|
29
29
|
},
|
|
30
30
|
"build": {
|
|
31
|
-
"openclawVersion": "2026.5.
|
|
31
|
+
"openclawVersion": "2026.5.9-beta.1",
|
|
32
32
|
"staticAssets": [
|
|
33
33
|
{
|
|
34
34
|
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"skills/**"
|
|
59
59
|
],
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"openclaw": ">=2026.5.
|
|
61
|
+
"openclaw": ">=2026.5.9-beta.1"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"openclaw": {
|