@openclaw/acpx 2026.9.4 → 2026.9.6

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.
@@ -1,1565 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { n as createLazyAcpRuntimeProxy } from "./register.runtime-CksaMeEx.js";
3
- import "./config-schema-DN_uAi4R.js";
4
- import { _ as splitCommandParts, c as openAcpxProcessLeaseStateStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, h as normalizeAcpxGatewayInstanceRecord, i as createAcpxProcessLeaseStore, l as readAcpxProcessLeaseIdentity, n as OPENCLAW_ACPX_LEASE_ID_ARG, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG } from "./process-lease-B83BGiLj.js";
5
- import { i as toAcpMcpServers, n as resolveAcpxPluginRoot, r as resolveOpenClawRoot, t as resolveAcpxPluginConfig } from "./config-D2FTk0K7.js";
6
- import { createRequire } from "node:module";
7
- import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
8
- import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
9
- import { isRecord, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
10
- import fs from "node:fs";
11
- import os from "node:os";
12
- import path from "node:path";
13
- import fs$1 from "node:fs/promises";
14
- import { randomUUID } from "node:crypto";
15
- import { escapeRegExp } from "openclaw/plugin-sdk/text-utility-runtime";
16
- import { inspect } from "node:util";
17
- import { isPidAlive, runExec } from "openclaw/plugin-sdk/process-runtime";
18
- import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
19
- import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
20
- import { parse, stringify } from "smol-toml";
21
- //#region extensions/acpx/src/codex-adapter.ts
22
- const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
23
- const CODEX_ACP_BIN = "codex-acp";
24
- const LEGACY_CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
25
- const OPENCLAW_CODEX_CONFIG_ARG = "--openclaw-codex-config";
26
- //#endregion
27
- //#region extensions/acpx/src/process-reaper.ts
28
- /**
29
- * ACPX process ownership checks and cleanup. The reaper only terminates
30
- * OpenClaw-owned wrapper trees after validating paths, packages, and lease ids.
31
- */
32
- const requireFromHere$1 = createRequire(import.meta.url);
33
- const GENERATED_WRAPPER_BASENAMES = /* @__PURE__ */ new Set(["codex-acp-wrapper.mjs", "claude-agent-acp-wrapper.mjs"]);
34
- const OPENCLAW_PLUGIN_DEPS_MARKER = "/plugin-runtime-deps/";
35
- const ACPX_PROCESS_LIST_TIMEOUT_MS = 2e3;
36
- const OWNED_ACP_PACKAGE_NAMES = [
37
- CODEX_ACP_PACKAGE,
38
- LEGACY_CODEX_ACP_PACKAGE,
39
- "@zed-industries/codex-acp-darwin-arm64",
40
- "@zed-industries/codex-acp-darwin-x64",
41
- "@zed-industries/codex-acp-linux-arm64",
42
- "@zed-industries/codex-acp-linux-x64",
43
- "@zed-industries/codex-acp-win32-arm64",
44
- "@zed-industries/codex-acp-win32-x64",
45
- "@agentclientprotocol/claude-agent-acp",
46
- "acpx"
47
- ];
48
- const PLUGIN_DEPS_CODEX_PACKAGE_NAMES = [
49
- "@openai/codex",
50
- "@openai/codex-darwin-arm64",
51
- "@openai/codex-darwin-x64",
52
- "@openai/codex-linux-arm64",
53
- "@openai/codex-linux-x64",
54
- "@openai/codex-win32-arm64",
55
- "@openai/codex-win32-x64"
56
- ];
57
- const ACP_PACKAGE_MARKERS = [
58
- ...OWNED_ACP_PACKAGE_NAMES.map((packageName) => `/node_modules/${packageName}/`),
59
- ...PLUGIN_DEPS_CODEX_PACKAGE_NAMES.map((packageName) => `/node_modules/${packageName}/`),
60
- "/acpx/dist/"
61
- ];
62
- function normalizePathLike(value) {
63
- return value.replaceAll("\\", "/");
64
- }
65
- function resolvePackageRoot(packageName) {
66
- try {
67
- return normalizePathLike(path.dirname(requireFromHere$1.resolve(`${packageName}/package.json`)));
68
- } catch {
69
- return;
70
- }
71
- }
72
- function resolveOwnedAcpPackageRootCandidates(packageName) {
73
- const pluginRoot = resolveAcpxPluginRoot(import.meta.url);
74
- const openClawRoot = resolveOpenClawRoot(pluginRoot);
75
- return [
76
- resolvePackageRoot(packageName),
77
- path.join(pluginRoot, "node_modules", packageName),
78
- path.join(openClawRoot, "node_modules", packageName)
79
- ].flatMap((root) => root ? [normalizePathLike(root)] : []);
80
- }
81
- const OWNED_ACP_PACKAGE_ROOTS = Array.from(new Set(OWNED_ACP_PACKAGE_NAMES.flatMap(resolveOwnedAcpPackageRootCandidates)));
82
- function commandBelongsToResolvedAcpPackage(command) {
83
- return OWNED_ACP_PACKAGE_ROOTS.some((root) => command.includes(`${root}/`));
84
- }
85
- function commandMentionsGeneratedWrapper(command) {
86
- return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => command.includes(basename));
87
- }
88
- function commandContainsExactWrapperPath(command, wrapperPath) {
89
- const expectedPath = normalizePathLike(wrapperPath);
90
- return new RegExp(`(?:^|[\\s"'])${escapeRegExp(expectedPath)}(?=$|[\\s"'])`).test(normalizePathLike(command));
91
- }
92
- function wrapperPathBelongsToRoot(wrapperPath, wrapperRoot) {
93
- const normalizedPath = normalizePathLike(wrapperPath);
94
- const normalizedRoot = normalizePathLike(wrapperRoot).replace(/\/+$/, "");
95
- return GENERATED_WRAPPER_BASENAMES.has(path.posix.basename(normalizedPath)) && normalizedPath.startsWith(`${normalizedRoot}/`);
96
- }
97
- /** Check whether a command references an OpenClaw-generated ACPX wrapper path. */
98
- function isOpenClawLeaseAwareAcpxProcessCommand(params) {
99
- const command = normalizePathLike(Array.isArray(params.command) ? params.command.join(" ") : params.command ?? "");
100
- const root = params.wrapperRoot ? `${normalizePathLike(params.wrapperRoot).replace(/\/+$/, "")}/` : "";
101
- return Array.from(GENERATED_WRAPPER_BASENAMES).some((basename) => command.includes(`${root}${basename}`));
102
- }
103
- function commandsReferToSameRootCommand(liveCommand, storedCommand) {
104
- if (!storedCommand?.trim()) return true;
105
- return normalizePathLike(liveCommand).trim() === normalizePathLike(storedCommand).trim();
106
- }
107
- function liveCommandMatchesLeaseIdentity(params) {
108
- if (!params.expectedLeaseId && !params.expectedGatewayInstanceId) return true;
109
- const identity = readAcpxProcessLeaseIdentity(params.command);
110
- return (!params.expectedLeaseId || identity?.leaseId === params.expectedLeaseId) && (!params.expectedGatewayInstanceId || identity?.gatewayInstanceId === params.expectedGatewayInstanceId);
111
- }
112
- /** Check whether a command is owned by OpenClaw ACPX runtime packages or wrappers. */
113
- function isOpenClawOwnedAcpxProcessCommand(params) {
114
- const command = params.command?.trim();
115
- if (!command) return false;
116
- const normalized = normalizePathLike(command);
117
- if (isOpenClawLeaseAwareAcpxProcessCommand({
118
- command: normalized,
119
- wrapperRoot: params.wrapperRoot
120
- })) return true;
121
- if (commandBelongsToResolvedAcpPackage(normalized)) return true;
122
- if (!normalized.includes(OPENCLAW_PLUGIN_DEPS_MARKER)) return false;
123
- return ACP_PACKAGE_MARKERS.some((marker) => normalized.includes(marker));
124
- }
125
- function parseProcessList(stdout) {
126
- const processes = [];
127
- for (const line of stdout.split(/\r?\n/)) {
128
- const match = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<command>.+?)\s*$/.exec(line);
129
- const pid = match?.groups?.pid;
130
- const ppid = match?.groups?.ppid;
131
- const command = match?.groups?.command;
132
- if (!pid || !ppid || !command) continue;
133
- processes.push({
134
- pid: Number.parseInt(pid, 10),
135
- ppid: Number.parseInt(ppid, 10),
136
- command
137
- });
138
- }
139
- return processes;
140
- }
141
- /** List host processes in the compact shape needed by ACPX cleanup. */
142
- async function listPlatformProcesses() {
143
- if (process.platform === "win32") return [];
144
- const { stdout } = await runExec("ps", ["-axo", "pid=,ppid=,command="], {
145
- logOutput: false,
146
- maxBuffer: 8388608,
147
- timeoutMs: ACPX_PROCESS_LIST_TIMEOUT_MS
148
- });
149
- return parseProcessList(stdout);
150
- }
151
- function collectProcessTree(processes, rootPid) {
152
- const childrenByParent = /* @__PURE__ */ new Map();
153
- for (const processInfo of processes) {
154
- const children = childrenByParent.get(processInfo.ppid) ?? [];
155
- children.push(processInfo);
156
- childrenByParent.set(processInfo.ppid, children);
157
- }
158
- const root = new Map(processes.map((processInfo) => [processInfo.pid, processInfo])).get(rootPid);
159
- const collected = [];
160
- if (root) collected.push(root);
161
- const queue = [...childrenByParent.get(rootPid) ?? []];
162
- while (queue.length > 0) {
163
- const next = queue.shift();
164
- if (!next || collected.some((processInfo) => processInfo.pid === next.pid)) continue;
165
- collected.push(next);
166
- queue.push(...childrenByParent.get(next.pid) ?? []);
167
- }
168
- return collected;
169
- }
170
- function uniquePids(processes) {
171
- return Array.from(new Set(processes.map((processInfo) => processInfo.pid).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid)));
172
- }
173
- async function terminatePids(pids, deps) {
174
- const killProcess = deps?.killProcess ?? ((pid, signal) => process.kill(pid, signal));
175
- const sleep = deps?.sleep ?? ((ms) => new Promise((resolve) => {
176
- setTimeout(resolve, ms);
177
- }));
178
- const terminated = [];
179
- for (const pid of pids) try {
180
- killProcess(pid, "SIGTERM");
181
- terminated.push(pid);
182
- } catch {}
183
- if (terminated.length === 0) return terminated;
184
- await sleep(750);
185
- for (const pid of terminated) if (deps?.killProcess || isPidAlive(pid)) try {
186
- killProcess(pid, "SIGKILL");
187
- } catch {}
188
- return terminated;
189
- }
190
- /** Terminate one validated OpenClaw-owned ACPX wrapper process tree. */
191
- async function cleanupOpenClawOwnedAcpxProcessTree(params) {
192
- const rootPid = params.rootPid;
193
- if (!rootPid || rootPid <= 0 || rootPid === process.pid) return {
194
- inspectedPids: [],
195
- terminatedPids: [],
196
- skippedReason: "missing-root"
197
- };
198
- if ((params.deps?.platform ?? process.platform) === "win32") return {
199
- inspectedPids: [],
200
- terminatedPids: [],
201
- skippedReason: "unsupported-platform"
202
- };
203
- let processes;
204
- try {
205
- processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
206
- } catch {
207
- return {
208
- inspectedPids: [],
209
- terminatedPids: [],
210
- skippedReason: "process-list-unavailable"
211
- };
212
- }
213
- const listedTree = collectProcessTree(processes, rootPid);
214
- if (listedTree.length === 0) return {
215
- inspectedPids: [],
216
- terminatedPids: [],
217
- skippedReason: "unverified-root"
218
- };
219
- const rootCommand = listedTree[0]?.command ?? params.rootCommand;
220
- const liveCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(rootCommand ?? ""));
221
- const storedCommandWasGeneratedWrapper = commandMentionsGeneratedWrapper(normalizePathLike(params.rootCommand ?? ""));
222
- if (!liveCommandWasGeneratedWrapper && (storedCommandWasGeneratedWrapper || !commandsReferToSameRootCommand(rootCommand ?? "", params.rootCommand)) || !isOpenClawOwnedAcpxProcessCommand({
223
- command: rootCommand,
224
- wrapperRoot: params.wrapperRoot
225
- }) || !liveCommandMatchesLeaseIdentity({
226
- command: rootCommand,
227
- expectedLeaseId: params.expectedLeaseId,
228
- expectedGatewayInstanceId: params.expectedGatewayInstanceId
229
- })) return {
230
- inspectedPids: listedTree.map((processInfo) => processInfo.pid),
231
- terminatedPids: [],
232
- skippedReason: "not-openclaw-owned"
233
- };
234
- const pids = uniquePids(listedTree.toReversed());
235
- return {
236
- inspectedPids: uniquePids(listedTree),
237
- terminatedPids: await terminatePids(pids, params.deps)
238
- };
239
- }
240
- /** Recover a pending lease by matching its exact live wrapper identity. */
241
- async function cleanupOpenClawOwnedAcpxPendingLease(params) {
242
- if ((params.deps?.platform ?? process.platform) === "win32") return {
243
- inspectedPids: [],
244
- terminatedPids: [],
245
- skippedReason: "unsupported-platform"
246
- };
247
- if (!params.wrapperPath || !wrapperPathBelongsToRoot(params.wrapperPath, params.wrapperRoot)) return {
248
- inspectedPids: [],
249
- terminatedPids: [],
250
- skippedReason: "unverified-root"
251
- };
252
- let processes;
253
- try {
254
- processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
255
- } catch {
256
- return {
257
- inspectedPids: [],
258
- terminatedPids: [],
259
- skippedReason: "process-list-unavailable"
260
- };
261
- }
262
- const matchingRoots = processes.filter((processInfo) => commandContainsExactWrapperPath(processInfo.command, params.wrapperPath) && liveCommandMatchesLeaseIdentity({
263
- command: processInfo.command,
264
- expectedLeaseId: params.leaseId,
265
- expectedGatewayInstanceId: params.gatewayInstanceId
266
- }));
267
- if (matchingRoots.length === 0) return {
268
- inspectedPids: [],
269
- terminatedPids: [],
270
- skippedReason: "missing-root"
271
- };
272
- if (matchingRoots.length > 1) return {
273
- inspectedPids: uniquePids(matchingRoots),
274
- terminatedPids: [],
275
- skippedReason: "ambiguous-root"
276
- };
277
- const listedTree = collectProcessTree(processes, matchingRoots[0].pid);
278
- const pids = uniquePids(listedTree.toReversed());
279
- return {
280
- inspectedPids: uniquePids(listedTree),
281
- terminatedPids: await terminatePids(pids, params.deps)
282
- };
283
- }
284
- /** Reap orphaned OpenClaw-owned ACPX wrapper trees during runtime startup. */
285
- async function reapStaleOpenClawOwnedAcpxOrphans(params) {
286
- if ((params.deps?.platform ?? process.platform) === "win32") return {
287
- inspectedPids: [],
288
- terminatedPids: [],
289
- skippedReason: "unsupported-platform"
290
- };
291
- let processes;
292
- try {
293
- processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
294
- } catch {
295
- return {
296
- inspectedPids: [],
297
- terminatedPids: [],
298
- skippedReason: "process-list-unavailable"
299
- };
300
- }
301
- const orphanTrees = processes.filter((processInfo) => processInfo.ppid === 1 && !readAcpxProcessLeaseIdentity(processInfo.command) && isOpenClawOwnedAcpxProcessCommand({
302
- command: processInfo.command,
303
- wrapperRoot: params.wrapperRoot
304
- })).map((orphan) => collectProcessTree(processes, orphan.pid));
305
- return {
306
- inspectedPids: uniquePids(orphanTrees.flat()),
307
- terminatedPids: await terminatePids(uniquePids(orphanTrees.flatMap((tree) => tree.toReversed())), params.deps)
308
- };
309
- }
310
- //#endregion
311
- //#region extensions/acpx/src/codex-trust-config.ts
312
- /**
313
- * Builds isolated Codex config for ACPX sessions. It preserves safe inherited
314
- * runtime options while rendering only trusted project entries for the session.
315
- */
316
- function stripTomlComment(line) {
317
- let quote = null;
318
- let escaping = false;
319
- for (let index = 0; index < line.length; index += 1) {
320
- const ch = line[index];
321
- if (escaping) {
322
- escaping = false;
323
- continue;
324
- }
325
- if (quote === "\"" && ch === "\\") {
326
- escaping = true;
327
- continue;
328
- }
329
- if (quote) {
330
- if (ch === quote) quote = null;
331
- continue;
332
- }
333
- if (ch === "'" || ch === "\"") {
334
- quote = ch;
335
- continue;
336
- }
337
- if (ch === "#") return line.slice(0, index);
338
- }
339
- return line;
340
- }
341
- function parseTomlString(value) {
342
- const trimmed = value.trim();
343
- if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) try {
344
- return JSON.parse(trimmed);
345
- } catch {
346
- return;
347
- }
348
- if (trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
349
- }
350
- function parseTomlDottedKey(value) {
351
- const parts = [];
352
- let current = "";
353
- let quote = null;
354
- let escaping = false;
355
- for (const ch of value.trim()) {
356
- if (escaping) {
357
- current += ch;
358
- escaping = false;
359
- continue;
360
- }
361
- if (quote === "\"" && ch === "\\") {
362
- current += ch;
363
- escaping = true;
364
- continue;
365
- }
366
- if (quote) {
367
- current += ch;
368
- if (ch === quote) quote = null;
369
- continue;
370
- }
371
- if (ch === "'" || ch === "\"") {
372
- quote = ch;
373
- current += ch;
374
- continue;
375
- }
376
- if (ch === ".") {
377
- parts.push(current.trim());
378
- current = "";
379
- continue;
380
- }
381
- current += ch;
382
- }
383
- if (current.trim()) parts.push(current.trim());
384
- return parts.map((part) => parseTomlString(part) ?? part);
385
- }
386
- function parseProjectHeader(line) {
387
- const trimmed = line.trim();
388
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]") || trimmed.startsWith("[[")) return;
389
- const parts = parseTomlDottedKey(trimmed.slice(1, -1));
390
- return parts.length === 2 && parts[0] === "projects" ? parts[1] : void 0;
391
- }
392
- function parseTrustedInlineProjectEntries(value) {
393
- const trusted = [];
394
- for (const match of value.matchAll(/(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*\{(?<body>[^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g)) {
395
- const key = match.groups?.key;
396
- const body = match.groups?.body;
397
- if (!key || !body || !/\btrust_level\s*=\s*["']trusted["']/.test(body)) continue;
398
- const projectPath = parseTomlString(key) ?? key.trim();
399
- if (projectPath) trusted.push(projectPath);
400
- }
401
- return trusted;
402
- }
403
- /** Extract trusted project paths from Codex TOML config. */
404
- function extractTrustedCodexProjectPaths(configToml) {
405
- const trusted = /* @__PURE__ */ new Set();
406
- let currentProjectPath;
407
- let inProjectsTable = false;
408
- for (const rawLine of configToml.split(/\r?\n/)) {
409
- const line = stripTomlComment(rawLine).trim();
410
- if (!line) continue;
411
- if (line.startsWith("[")) {
412
- currentProjectPath = parseProjectHeader(line);
413
- inProjectsTable = line === "[projects]";
414
- continue;
415
- }
416
- if (currentProjectPath && /^trust_level\s*=\s*["']trusted["']\s*$/.test(line)) {
417
- trusted.add(currentProjectPath);
418
- continue;
419
- }
420
- const assignment = /^(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*(?<value>.+)$/.exec(line);
421
- const rawKey = assignment?.groups?.key;
422
- const rawValue = assignment?.groups?.value;
423
- if (!rawKey || rawValue === void 0) continue;
424
- const key = parseTomlString(rawKey) ?? rawKey;
425
- const value = rawValue.trim();
426
- if (inProjectsTable && /^\{.*\}$/.test(value)) {
427
- if (/\btrust_level\s*=\s*["']trusted["']/.test(value) && key) trusted.add(key);
428
- continue;
429
- }
430
- if (key === "projects" || inProjectsTable) for (const projectPath of parseTrustedInlineProjectEntries(value)) trusted.add(projectPath);
431
- }
432
- return Array.from(trusted);
433
- }
434
- const INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS = /* @__PURE__ */ new Set([
435
- "model",
436
- "model_provider",
437
- "model_reasoning_effort",
438
- "sandbox_mode"
439
- ]);
440
- const INHERITED_MODEL_PROVIDER_CONFIG_KEYS = /* @__PURE__ */ new Set([
441
- "name",
442
- "base_url",
443
- "wire_api",
444
- "env_key",
445
- "env_key_instructions",
446
- "requires_openai_auth",
447
- "request_max_retries",
448
- "stream_max_retries",
449
- "stream_idle_timeout_ms"
450
- ]);
451
- function parseTableHeader(line) {
452
- const trimmed = line.trim();
453
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]") || trimmed.startsWith("[[")) return;
454
- return parseTomlDottedKey(trimmed.slice(1, -1));
455
- }
456
- function isInheritedModelProviderTable(parts) {
457
- return parts?.[0] === "model_providers" && parts.length === 2;
458
- }
459
- function parseTopLevelAssignmentKey(line) {
460
- return /^(?<key>[A-Za-z0-9_-]+)\s*=\s*(?<value>.+)$/.exec(line)?.groups?.key;
461
- }
462
- function extractInheritedCodexRuntimeConfig(configToml) {
463
- const inheritedLines = [];
464
- let inAnyTable = false;
465
- let inInheritedTable = false;
466
- let pendingInheritedTableHeader = "";
467
- function flushInheritedTableHeader() {
468
- if (!pendingInheritedTableHeader) return;
469
- if (inheritedLines.length > 0 && inheritedLines[inheritedLines.length - 1] !== "") inheritedLines.push("");
470
- inheritedLines.push(pendingInheritedTableHeader);
471
- pendingInheritedTableHeader = "";
472
- }
473
- for (const rawLine of configToml.split(/\r?\n/)) {
474
- const trimmedLine = rawLine.trim();
475
- const semanticLine = stripTomlComment(rawLine).trim();
476
- if (trimmedLine.startsWith("[")) {
477
- const tableParts = parseTableHeader(trimmedLine);
478
- inAnyTable = true;
479
- inInheritedTable = isInheritedModelProviderTable(tableParts);
480
- if (inInheritedTable) pendingInheritedTableHeader = rawLine.trimEnd();
481
- else pendingInheritedTableHeader = "";
482
- continue;
483
- }
484
- if (inInheritedTable) {
485
- if (!semanticLine) continue;
486
- const key = parseTopLevelAssignmentKey(semanticLine);
487
- if (!key || !INHERITED_MODEL_PROVIDER_CONFIG_KEYS.has(key)) continue;
488
- flushInheritedTableHeader();
489
- inheritedLines.push(rawLine.trimEnd());
490
- continue;
491
- }
492
- if (inAnyTable) continue;
493
- const key = parseTopLevelAssignmentKey(semanticLine);
494
- if (!key) continue;
495
- if (!INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS.has(key)) continue;
496
- inheritedLines.push(rawLine.trimEnd());
497
- }
498
- while (inheritedLines.length > 0 && inheritedLines[inheritedLines.length - 1] === "") inheritedLines.pop();
499
- return inheritedLines.join("\n");
500
- }
501
- /** Render a session-local Codex config with inherited runtime settings and trust entries. */
502
- function renderIsolatedCodexConfig(params) {
503
- const normalized = Array.from(new Set(params.projectPaths.map((projectPath) => projectPath.trim()).filter(Boolean).map((projectPath) => path.resolve(projectPath)))).toSorted((left, right) => left.localeCompare(right));
504
- return [
505
- "# Generated by OpenClaw for Codex ACP sessions.",
506
- params.sourceConfigToml ? extractInheritedCodexRuntimeConfig(params.sourceConfigToml) : "",
507
- ...normalized.flatMap((projectPath) => [
508
- "",
509
- `[projects.${JSON.stringify(projectPath)}]`,
510
- "trust_level = \"trusted\""
511
- ]),
512
- ""
513
- ].filter((line, index, lines) => !(line === "" && lines[index - 1] === "")).join("\n");
514
- }
515
- //#endregion
516
- //#region extensions/acpx/src/codex-auth-bridge.ts
517
- /**
518
- * Prepares isolated Codex and Claude ACP wrapper commands for ACPX. The bridge
519
- * copies safe auth/config state into plugin-owned homes and redacts diagnostics.
520
- */
521
- const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
522
- const CLAUDE_ACP_BIN = "claude-agent-acp";
523
- const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
524
- const requireFromHere = createRequire(import.meta.url);
525
- function readSelfManifest() {
526
- const manifestPath = path.join(resolveAcpxPluginRoot(import.meta.url), "package.json");
527
- return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
528
- }
529
- function readManifestDependencyVersion(packageName) {
530
- const version = readSelfManifest().dependencies?.[packageName];
531
- if (typeof version !== "string" || version.trim() === "") throw new Error(`Missing ${packageName} dependency version in @openclaw/acpx manifest`);
532
- return version;
533
- }
534
- const CODEX_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CODEX_ACP_PACKAGE);
535
- const CLAUDE_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CLAUDE_ACP_PACKAGE);
536
- function basename(value) {
537
- return value.split(/[\\/]/).pop() ?? value;
538
- }
539
- function resolvePackageBinPath(packageJsonPath, manifest, binName) {
540
- const { bin } = manifest;
541
- const relativeBinPath = typeof bin === "string" ? bin : bin && typeof bin === "object" ? bin[binName] : void 0;
542
- if (typeof relativeBinPath !== "string" || relativeBinPath.trim() === "") return;
543
- return path.resolve(path.dirname(packageJsonPath), relativeBinPath);
544
- }
545
- async function resolveInstalledAcpPackageBinPath(packageName, binName) {
546
- try {
547
- const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`);
548
- const { value: manifest } = await readJsonFileWithFallback(packageJsonPath, {});
549
- if (manifest.name !== packageName) return;
550
- const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
551
- if (!binPath) return;
552
- await fs$1.access(binPath);
553
- return binPath;
554
- } catch {
555
- return;
556
- }
557
- }
558
- async function resolveInstalledCodexAcpBinPath() {
559
- return await resolveInstalledAcpPackageBinPath(CODEX_ACP_PACKAGE, CODEX_ACP_BIN);
560
- }
561
- async function resolveInstalledClaudeAcpBinPath() {
562
- return await resolveInstalledAcpPackageBinPath(CLAUDE_ACP_PACKAGE, CLAUDE_ACP_BIN);
563
- }
564
- const DIAGNOSTIC_REDACTION_RULES = [
565
- {
566
- source: String.raw`(authorization\s*[:=]\s*bearer\s+)[^\s'"<>]+`,
567
- flags: "gi",
568
- replacement: "$1[REDACTED]"
569
- },
570
- {
571
- source: String.raw`((?:api[_-]?key|apiKey|access[_-]?token|refresh[_-]?token|client[_-]?secret|token|secret|password|passwd|credential)\s*[:=]\s*)[^\s'"<>]+`,
572
- flags: "gi",
573
- replacement: "$1[REDACTED]"
574
- },
575
- {
576
- source: String.raw`("(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*")[^"]+`,
577
- flags: "g",
578
- replacement: "$1[REDACTED]"
579
- },
580
- {
581
- source: String.raw`(["']?(?:api[-_]?key|apiKey|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|id[-_]?token|idToken|auth[-_]?token|authToken|client[-_]?secret|clientSecret|app[-_]?secret|appSecret|token|secret|password|passwd|credential)["']?\s*[:=]\s*["']?)[^"',}\s<>]+`,
582
- flags: "gi",
583
- replacement: "$1[REDACTED]"
584
- },
585
- {
586
- source: String.raw`([?&](?:access[-_]?token|auth[-_]?token|refresh[-_]?token|api[-_]?key|client[-_]?secret|token|key|secret|password|pass|passwd|auth|signature)=)[^&\s'"<>]+`,
587
- flags: "gi",
588
- replacement: "$1[REDACTED]"
589
- },
590
- {
591
- source: String.raw`(--(?:api[-_]?key|token|secret|password|passwd)\s+)[^\s'"]+`,
592
- flags: "gi",
593
- replacement: "$1[REDACTED]"
594
- },
595
- {
596
- source: String.raw`-----BEGIN [A-Z ]*PRI` + String.raw`VATE KEY-----[\s\S]+?-----END [A-Z ]*PRI` + String.raw`VATE KEY-----`,
597
- flags: "g",
598
- replacement: "[REDACTED_PRIVATE_KEY]"
599
- },
600
- {
601
- source: String.raw`\b(sk-[A-Za-z0-9_-]{8,})\b`,
602
- flags: "g",
603
- replacement: "[REDACTED_OPENAI_KEY]"
604
- },
605
- {
606
- source: String.raw`\b(gh[pousr]_[A-Za-z0-9_]{20,})\b`,
607
- flags: "g",
608
- replacement: "[REDACTED_GITHUB_TOKEN]"
609
- },
610
- {
611
- source: String.raw`\b(github_pat_[A-Za-z0-9_]{20,})\b`,
612
- flags: "g",
613
- replacement: "[REDACTED_GITHUB_TOKEN]"
614
- },
615
- {
616
- source: String.raw`\b(xox[baprs]-[A-Za-z0-9-]{10,})\b`,
617
- flags: "g",
618
- replacement: "[REDACTED_SLACK_TOKEN]"
619
- },
620
- {
621
- source: String.raw`\b(gsk_[A-Za-z0-9_-]{10,})\b`,
622
- flags: "g",
623
- replacement: "[REDACTED_API_KEY]"
624
- },
625
- {
626
- source: String.raw`\b(AIza[0-9A-Za-z\-_]{20,})\b`,
627
- flags: "g",
628
- replacement: "[REDACTED_GOOGLE_KEY]"
629
- },
630
- {
631
- source: String.raw`\b(ya29\.[0-9A-Za-z_\-./+=]{10,})\b`,
632
- flags: "g",
633
- replacement: "[REDACTED_GOOGLE_TOKEN]"
634
- },
635
- {
636
- source: String.raw`\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b`,
637
- flags: "g",
638
- replacement: "[REDACTED_JWT]"
639
- },
640
- {
641
- source: String.raw`\b(pplx-[A-Za-z0-9_-]{10,})\b`,
642
- flags: "g",
643
- replacement: "[REDACTED_API_KEY]"
644
- },
645
- {
646
- source: String.raw`\b(npm_[A-Za-z0-9]{10,})\b`,
647
- flags: "g",
648
- replacement: "[REDACTED_NPM_TOKEN]"
649
- },
650
- {
651
- source: String.raw`\b(LTAI[A-Za-z0-9]{10,})\b`,
652
- flags: "g",
653
- replacement: "[REDACTED_ACCESS_KEY]"
654
- },
655
- {
656
- source: String.raw`\b(hf_[A-Za-z0-9]{10,})\b`,
657
- flags: "g",
658
- replacement: "[REDACTED_API_KEY]"
659
- },
660
- {
661
- source: String.raw`\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
662
- flags: "g",
663
- replacement: "bot[REDACTED_TELEGRAM_TOKEN]"
664
- },
665
- {
666
- source: String.raw`\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
667
- flags: "g",
668
- replacement: "[REDACTED_TELEGRAM_TOKEN]"
669
- }
670
- ];
671
- function buildAdapterWrapperScript(params) {
672
- return `#!/usr/bin/env node
673
- import { appendFileSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
674
- import path from "node:path";
675
- import { spawn } from "node:child_process";
676
- import { StringDecoder } from "node:string_decoder";
677
- import { fileURLToPath } from "node:url";
678
-
679
- ${params.envSetup}
680
- const stderrLogFileNamePrefix = ${params.stderrLogFileNamePrefix ? JSON.stringify(params.stderrLogFileNamePrefix) : "undefined"};
681
- const stderrLogMaxChars = 256 * 1024;
682
-
683
- const openClawWrapperArgs = new Set([
684
- ${JSON.stringify(OPENCLAW_ACPX_LEASE_ID_ARG)},
685
- ${JSON.stringify(OPENCLAW_GATEWAY_INSTANCE_ID_ARG)},
686
- ${(params.openClawWrapperArgs ?? []).map((arg) => JSON.stringify(arg)).join(",\n ")}
687
- ]);
688
-
689
- function readOpenClawWrapperArg(args, name) {
690
- const index = args.indexOf(name);
691
- if (index < 0) {
692
- return undefined;
693
- }
694
- const value = args[index + 1];
695
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
696
- }
697
-
698
- function readOpenClawWrapperArgs(args, name) {
699
- const values = [];
700
- for (let index = 0; index < args.length; index += 1) {
701
- if (args[index] !== name) {
702
- continue;
703
- }
704
- const value = args[index + 1];
705
- if (typeof value === "string" && value.trim()) {
706
- values.push(value.trim());
707
- }
708
- index += 1;
709
- }
710
- return values;
711
- }
712
-
713
- function safeDiagnosticFilePart(value) {
714
- const sanitized = String(value || "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120);
715
- return sanitized || "pid-" + process.pid;
716
- }
717
-
718
- function resolveStderrLogPath(args) {
719
- if (!stderrLogFileNamePrefix) {
720
- return undefined;
721
- }
722
- const leaseId =
723
- readOpenClawWrapperArg(args, ${JSON.stringify(OPENCLAW_ACPX_LEASE_ID_ARG)}) ||
724
- "pid-" + process.pid;
725
- const fileName = stderrLogFileNamePrefix + "." + safeDiagnosticFilePart(leaseId) + ".log";
726
- return fileURLToPath(new URL("./" + fileName, import.meta.url));
727
- }
728
-
729
- const diagnosticRedactionRules = ${JSON.stringify(DIAGNOSTIC_REDACTION_RULES)}.map((rule) => [
730
- new RegExp(rule.source, rule.flags),
731
- rule.replacement,
732
- ]);
733
-
734
- function redactDiagnosticText(text) {
735
- let redacted = text;
736
- for (const [pattern, replacement] of diagnosticRedactionRules) {
737
- redacted = redacted.replace(pattern, replacement);
738
- }
739
- return redacted;
740
- }
741
-
742
- function tailUtf16Safe(text, maxChars) {
743
- let start = Math.max(0, text.length - maxChars);
744
- const startsInsideSurrogatePair =
745
- start > 0 &&
746
- start < text.length &&
747
- text.charCodeAt(start) >= 0xdc00 &&
748
- text.charCodeAt(start) <= 0xdfff &&
749
- text.charCodeAt(start - 1) >= 0xd800 &&
750
- text.charCodeAt(start - 1) <= 0xdbff;
751
- if (startsInsideSurrogatePair) {
752
- start += 1;
753
- }
754
- return text.slice(start);
755
- }
756
-
757
- let pendingStderrLogText = "";
758
- // Pipe chunks can split a UTF-8 sequence. Preserve decoder state so diagnostic
759
- // capture does not manufacture replacement characters between chunks.
760
- const stderrDecoder = new StringDecoder("utf8");
761
- const stderrPrivateKeyEndPattern = /-----END [A-Z ]*PRIVATE KEY-----/;
762
-
763
- function hasUnclosedPrivateKeyBlock(text) {
764
- let lastBeginIndex = -1;
765
- for (const match of text.matchAll(/-----BEGIN [A-Z ]*PRIVATE KEY-----/g)) {
766
- lastBeginIndex = match.index ?? lastBeginIndex;
767
- }
768
- if (lastBeginIndex === -1) {
769
- return -1;
770
- }
771
- return stderrPrivateKeyEndPattern.test(text.slice(lastBeginIndex)) ? -1 : lastBeginIndex;
772
- }
773
-
774
- function writeRedactedStderrLog(text) {
775
- if (!stderrLogPath) {
776
- return;
777
- }
778
- if (!text) {
779
- return;
780
- }
781
- try {
782
- appendFileSync(stderrLogPath, redactDiagnosticText(text), "utf8");
783
- const current = readFileSync(stderrLogPath, "utf8");
784
- if (current.length > stderrLogMaxChars) {
785
- writeFileSync(stderrLogPath, tailUtf16Safe(current, stderrLogMaxChars), "utf8");
786
- }
787
- } catch {
788
- // Stderr capture is diagnostic-only; never break the ACP adapter.
789
- }
790
- }
791
-
792
- function redactIncompletePrivateKeyTail(text) {
793
- const unclosedPrivateKeyStart = hasUnclosedPrivateKeyBlock(text);
794
- if (unclosedPrivateKeyStart === -1) {
795
- return text;
796
- }
797
- return text.slice(0, unclosedPrivateKeyStart) + "[REDACTED_PRIVATE_KEY]";
798
- }
799
-
800
- function flushFinalizedStderrLogText() {
801
- const lastLineBreak = pendingStderrLogText.lastIndexOf("\\n");
802
- if (lastLineBreak === -1) {
803
- if (pendingStderrLogText.length > stderrLogMaxChars) {
804
- pendingStderrLogText = tailUtf16Safe(pendingStderrLogText, stderrLogMaxChars);
805
- }
806
- return;
807
- }
808
- let flushEnd = lastLineBreak + 1;
809
- const unclosedPrivateKeyStart = hasUnclosedPrivateKeyBlock(
810
- pendingStderrLogText.slice(0, flushEnd),
811
- );
812
- if (unclosedPrivateKeyStart !== -1) {
813
- flushEnd = unclosedPrivateKeyStart;
814
- }
815
- if (flushEnd <= 0) {
816
- if (pendingStderrLogText.length > stderrLogMaxChars) {
817
- pendingStderrLogText = tailUtf16Safe(pendingStderrLogText, stderrLogMaxChars);
818
- }
819
- return;
820
- }
821
- const finalizedText = pendingStderrLogText.slice(0, flushEnd);
822
- pendingStderrLogText = pendingStderrLogText.slice(flushEnd);
823
- writeRedactedStderrLog(finalizedText);
824
- }
825
-
826
- function appendStderrLog(chunk) {
827
- const text = stderrDecoder.write(chunk);
828
- if (!text) {
829
- return;
830
- }
831
- pendingStderrLogText += text;
832
- flushFinalizedStderrLogText();
833
- }
834
-
835
- function finishStderrLog() {
836
- pendingStderrLogText += stderrDecoder.end();
837
- const text = redactIncompletePrivateKeyTail(pendingStderrLogText);
838
- pendingStderrLogText = "";
839
- writeRedactedStderrLog(text);
840
- }
841
-
842
- function stripOpenClawWrapperArgs(args) {
843
- const stripped = [];
844
- for (let index = 0; index < args.length; index += 1) {
845
- const value = args[index];
846
- if (openClawWrapperArgs.has(value)) {
847
- index += 1;
848
- continue;
849
- }
850
- stripped.push(value);
851
- }
852
- return stripped;
853
- }
854
-
855
- const rawConfiguredArgs = process.argv.slice(2);
856
- ${params.envConfigSetup ?? ""}
857
- const stderrLogPath = resolveStderrLogPath(rawConfiguredArgs);
858
- if (stderrLogPath) {
859
- try {
860
- rmSync(stderrLogPath, { force: true });
861
- } catch {
862
- // Diagnostic cleanup must never prevent the adapter from starting.
863
- }
864
- }
865
-
866
- const configuredArgs = stripOpenClawWrapperArgs(rawConfiguredArgs);
867
-
868
- function resolveNpmCliPath() {
869
- const candidate = path.resolve(
870
- path.dirname(process.execPath),
871
- "..",
872
- "lib",
873
- "node_modules",
874
- "npm",
875
- "bin",
876
- "npm-cli.js",
877
- );
878
- return existsSync(candidate) ? candidate : undefined;
879
- }
880
-
881
- const npmCliPath = resolveNpmCliPath();
882
- const installedBinPath = ${params.installedBinPath ? JSON.stringify(params.installedBinPath) : "undefined"};
883
- let defaultCommand;
884
- let defaultArgs;
885
- if (installedBinPath) {
886
- defaultCommand = process.execPath;
887
- defaultArgs = [installedBinPath];
888
- } else if (npmCliPath) {
889
- defaultCommand = process.execPath;
890
- defaultArgs = [npmCliPath, "exec", "--yes", "--package", "${params.packageSpec}", "--", "${params.binName}"];
891
- } else {
892
- defaultCommand = process.platform === "win32" ? "npx.cmd" : "npx";
893
- defaultArgs = ["--yes", "--package", "${params.packageSpec}", "--", "${params.binName}"];
894
- }
895
- const command =
896
- configuredArgs[0] === "${RUN_CONFIGURED_COMMAND_SENTINEL}" ? configuredArgs[1] : defaultCommand;
897
- const args =
898
- configuredArgs[0] === "${RUN_CONFIGURED_COMMAND_SENTINEL}"
899
- ? configuredArgs.slice(2)
900
- : [...defaultArgs, ...configuredArgs];
901
-
902
- if (!command) {
903
- console.error("[openclaw] missing configured ${params.displayName} ACP command");
904
- process.exit(1);
905
- }
906
-
907
- const child = spawn(command, args, {
908
- detached: process.platform !== "win32",
909
- env,
910
- stdio: ["inherit", "inherit", "pipe"],
911
- windowsHide: true,
912
- });
913
-
914
- child.stderr?.on("data", (chunk) => {
915
- appendStderrLog(chunk);
916
- process.stderr.write(chunk);
917
- });
918
-
919
- let forceKillTimer;
920
- let orphanCleanupStarted = false;
921
- let childExitCode = 1;
922
-
923
- function killChildTree(signal, options = {}) {
924
- if (!child.pid || (!options.force && child.killed)) {
925
- return;
926
- }
927
- if (process.platform !== "win32") {
928
- try {
929
- // The adapter can spawn grandchildren; signaling the process group keeps
930
- // the generated wrapper from leaving an ACP tree behind.
931
- process.kill(-child.pid, signal);
932
- return;
933
- } catch {
934
- // Fall back to direct child signaling below.
935
- }
936
- }
937
- child.kill(signal);
938
- }
939
-
940
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
941
- process.once(signal, () => {
942
- killChildTree(signal);
943
- });
944
- }
945
-
946
- const originalParentPid = process.ppid;
947
- const parentWatcher =
948
- process.platform === "win32"
949
- ? undefined
950
- : setInterval(() => {
951
- // Orphan detection: parent PID changed means our original parent died.
952
- // The new parent could be PID 1 (init) on bare-metal hosts, OR a
953
- // systemd user-session manager, OR a container init, OR a session
954
- // leader — depending on environment. Previously this only triggered
955
- // on PPID == 1, which missed all systemd-managed deployments and
956
- // leaked codex-acp adapter trees on every gateway restart.
957
- if (process.ppid === originalParentPid) {
958
- return;
959
- }
960
- if (orphanCleanupStarted) {
961
- return;
962
- }
963
- orphanCleanupStarted = true;
964
- if (parentWatcher) {
965
- clearInterval(parentWatcher);
966
- }
967
- killChildTree("SIGTERM");
968
- // Keep the wrapper alive long enough for stubborn adapters to receive
969
- // a forced fallback signal after SIGTERM.
970
- forceKillTimer = setTimeout(() => {
971
- killChildTree("SIGKILL", { force: true });
972
- childExitCode = 1;
973
- }, 1_500);
974
- }, 1_000);
975
- parentWatcher?.unref?.();
976
-
977
- child.on("error", (error) => {
978
- console.error(\`[openclaw] failed to launch ${params.displayName} ACP wrapper: \${error.message}\`);
979
- process.exit(1);
980
- });
981
-
982
- child.on("exit", (code, signal) => {
983
- if (parentWatcher) {
984
- clearInterval(parentWatcher);
985
- }
986
- if (orphanCleanupStarted) {
987
- return;
988
- }
989
- if (forceKillTimer) {
990
- clearTimeout(forceKillTimer);
991
- }
992
- if (code !== null) {
993
- childExitCode = code;
994
- return;
995
- }
996
- childExitCode = signal ? 1 : 0;
997
- });
998
-
999
- child.on("close", () => {
1000
- finishStderrLog();
1001
- process.exit(childExitCode);
1002
- });
1003
- `;
1004
- }
1005
- function buildCodexAcpWrapperScript(installedBinPath) {
1006
- return buildAdapterWrapperScript({
1007
- displayName: "Codex",
1008
- packageSpec: `${CODEX_ACP_PACKAGE}@${CODEX_ACP_PACKAGE_VERSION}`,
1009
- binName: CODEX_ACP_BIN,
1010
- installedBinPath,
1011
- stderrLogFileNamePrefix: "codex-acp-wrapper.stderr",
1012
- openClawWrapperArgs: [OPENCLAW_CODEX_CONFIG_ARG],
1013
- envSetup: `const codexHome = fileURLToPath(new URL("./codex-home/", import.meta.url));
1014
- const codexAuthPath = fileURLToPath(new URL("./codex-home/auth.json", import.meta.url));
1015
- const codexApiKey = (process.env.CODEX_API_KEY || process.env.OPENAI_API_KEY || "").trim();
1016
- let shouldWriteCodexApiKeyAuth = false;
1017
- if (codexApiKey) {
1018
- if (!existsSync(codexAuthPath)) {
1019
- shouldWriteCodexApiKeyAuth = true;
1020
- } else {
1021
- try {
1022
- const existingCodexAuth = JSON.parse(readFileSync(codexAuthPath, "utf8"));
1023
- shouldWriteCodexApiKeyAuth =
1024
- !existingCodexAuth ||
1025
- typeof existingCodexAuth !== "object" ||
1026
- typeof existingCodexAuth.OPENAI_API_KEY === "string";
1027
- } catch {
1028
- shouldWriteCodexApiKeyAuth = true;
1029
- }
1030
- }
1031
- }
1032
- if (shouldWriteCodexApiKeyAuth) {
1033
- writeFileSync(
1034
- codexAuthPath,
1035
- JSON.stringify({
1036
- OPENAI_API_KEY: codexApiKey,
1037
- tokens: null,
1038
- last_refresh: null,
1039
- }) + "\\n",
1040
- { mode: 0o600 },
1041
- );
1042
- }
1043
- const env = {
1044
- ...process.env,
1045
- CODEX_HOME: codexHome,
1046
- };`,
1047
- envConfigSetup: `function isCodexConfigObject(value) {
1048
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1049
- }
1050
-
1051
- function mergeCodexConfig(base, override) {
1052
- const merged = Object.assign(Object.create(null), base);
1053
- for (const [key, value] of Object.entries(override)) {
1054
- const existing = merged[key];
1055
- merged[key] =
1056
- isCodexConfigObject(existing) && isCodexConfigObject(value)
1057
- ? mergeCodexConfig(existing, value)
1058
- : value;
1059
- }
1060
- return merged;
1061
- }
1062
-
1063
- const openClawCodexConfigs = readOpenClawWrapperArgs(
1064
- rawConfiguredArgs,
1065
- ${JSON.stringify(OPENCLAW_CODEX_CONFIG_ARG)},
1066
- );
1067
- if (openClawCodexConfigs.length > 0) {
1068
- let existingCodexConfig = {};
1069
- if (typeof env.CODEX_CONFIG === "string" && env.CODEX_CONFIG.trim()) {
1070
- try {
1071
- const parsedCodexConfig = JSON.parse(env.CODEX_CONFIG);
1072
- if (!parsedCodexConfig || typeof parsedCodexConfig !== "object" || Array.isArray(parsedCodexConfig)) {
1073
- throw new Error("CODEX_CONFIG must be a JSON object");
1074
- }
1075
- existingCodexConfig = parsedCodexConfig;
1076
- } catch {
1077
- console.error("[openclaw] CODEX_CONFIG must be a valid JSON object");
1078
- process.exit(1);
1079
- }
1080
- }
1081
- for (const openClawCodexConfig of openClawCodexConfigs) {
1082
- try {
1083
- const parsedOpenClawCodexConfig = JSON.parse(openClawCodexConfig);
1084
- if (
1085
- !parsedOpenClawCodexConfig ||
1086
- typeof parsedOpenClawCodexConfig !== "object" ||
1087
- Array.isArray(parsedOpenClawCodexConfig)
1088
- ) {
1089
- throw new Error("invalid OpenClaw Codex config");
1090
- }
1091
- existingCodexConfig = mergeCodexConfig(existingCodexConfig, parsedOpenClawCodexConfig);
1092
- } catch {
1093
- console.error("[openclaw] invalid generated Codex ACP startup config");
1094
- process.exit(1);
1095
- }
1096
- }
1097
- env.CODEX_CONFIG = JSON.stringify(existingCodexConfig);
1098
- }`
1099
- });
1100
- }
1101
- function buildClaudeAcpWrapperScript(installedBinPath) {
1102
- return buildAdapterWrapperScript({
1103
- displayName: "Claude",
1104
- packageSpec: `${CLAUDE_ACP_PACKAGE}@${CLAUDE_ACP_PACKAGE_VERSION}`,
1105
- binName: CLAUDE_ACP_BIN,
1106
- installedBinPath,
1107
- envSetup: `const env = {
1108
- ...process.env,
1109
- };`
1110
- });
1111
- }
1112
- async function readSourceCodexConfig(codexHome) {
1113
- try {
1114
- return await fs$1.readFile(path.join(codexHome, "config.toml"), "utf8");
1115
- } catch (error) {
1116
- if (error.code === "ENOENT") return;
1117
- throw error;
1118
- }
1119
- }
1120
- async function prepareIsolatedCodexHome(params) {
1121
- const sourceConfig = await readSourceCodexConfig(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"));
1122
- const trustedProjectPaths = [...sourceConfig ? extractTrustedCodexProjectPaths(sourceConfig) : [], params.workspaceDir];
1123
- const codexHome = path.join(params.baseDir, "codex-home");
1124
- await fs$1.mkdir(codexHome, { recursive: true });
1125
- await fs$1.writeFile(path.join(codexHome, "config.toml"), renderIsolatedCodexConfig({
1126
- sourceConfigToml: sourceConfig,
1127
- projectPaths: trustedProjectPaths
1128
- }), "utf8");
1129
- return codexHome;
1130
- }
1131
- async function writeAdapterWrapper(baseDir, fileName, script) {
1132
- await fs$1.mkdir(baseDir, { recursive: true });
1133
- const wrapperPath = path.join(baseDir, fileName);
1134
- await fs$1.writeFile(wrapperPath, script, { encoding: "utf8" });
1135
- try {
1136
- await fs$1.chmod(wrapperPath, 493);
1137
- } catch {}
1138
- return wrapperPath;
1139
- }
1140
- function buildWrapperCommand(wrapperPath, args = []) {
1141
- return [
1142
- process.execPath,
1143
- wrapperPath,
1144
- ...args
1145
- ];
1146
- }
1147
- function isAcpPackageSpec(value, packageName) {
1148
- const escapedPackageName = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1149
- return new RegExp(`^${escapedPackageName}(?:@.+)?$`, "i").test(value.trim());
1150
- }
1151
- function isAcpBinName(value, binName) {
1152
- const commandName = basename(value);
1153
- const escapedBinName = binName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1154
- return new RegExp(`^${escapedBinName}(?:\\.exe|\\.[cm]?js)?$`, "i").test(commandName);
1155
- }
1156
- function isPackageRunnerCommand(value) {
1157
- return /^(?:npx|npm|pnpm|bunx)(?:\.cmd|\.exe)?$/i.test(basename(value));
1158
- }
1159
- function extractConfiguredAdapterArgs(params) {
1160
- const parts = splitCommandParts(params.configuredCommand ?? []);
1161
- if (!parts.length) return [];
1162
- const packageIndex = parts.findIndex((part) => isAcpPackageSpec(part, params.packageName));
1163
- if (packageIndex >= 0) {
1164
- if (!isPackageRunnerCommand(parts[0] ?? "")) return;
1165
- const afterPackage = parts.slice(packageIndex + 1);
1166
- if (afterPackage[0] === "--" && isAcpBinName(afterPackage[1] ?? "", params.binName)) return afterPackage.slice(2);
1167
- if (isAcpBinName(afterPackage[0] ?? "", params.binName)) return afterPackage.slice(1);
1168
- return afterPackage[0] === "--" ? afterPackage.slice(1) : afterPackage;
1169
- }
1170
- if (isAcpBinName(parts[0] ?? "", params.binName)) return parts.slice(1);
1171
- if (basename(parts[0] ?? "") === "node" && isAcpBinName(parts[1] ?? "", params.binName)) return parts.slice(2);
1172
- }
1173
- function mergeConfigRecords(base, override) {
1174
- const merged = { ...base };
1175
- for (const [key, value] of Object.entries(override)) {
1176
- const existing = merged[key];
1177
- const nextValue = isRecord(existing) && isRecord(value) ? mergeConfigRecords(existing, value) : value;
1178
- Object.defineProperty(merged, key, {
1179
- value: nextValue,
1180
- configurable: true,
1181
- enumerable: true,
1182
- writable: true
1183
- });
1184
- }
1185
- return merged;
1186
- }
1187
- function parseLegacyCodexConfigAssignment(assignment) {
1188
- const separator = assignment.indexOf("=");
1189
- if (separator <= 0) throw new Error(`Invalid legacy Codex ACP config override: ${assignment}`);
1190
- const rawKey = assignment.slice(0, separator).trim();
1191
- const key = rawKey === "use_legacy_landlock" ? "features.use_legacy_landlock" : rawKey;
1192
- const rawValue = assignment.slice(separator + 1).trim();
1193
- try {
1194
- return parse(`${key} = ${rawValue}`);
1195
- } catch {
1196
- const literal = rawValue.replace(/^["']+|["']+$/g, "");
1197
- return parse(`${key} = ${JSON.stringify(literal)}`);
1198
- }
1199
- }
1200
- function migrateLegacyCodexArgs(args) {
1201
- let config = {};
1202
- const forwardedArgs = [];
1203
- let hadOverrides = false;
1204
- for (let index = 0; index < args.length; index += 1) {
1205
- const arg = args[index] ?? "";
1206
- let assignment;
1207
- if (arg === "-c" || arg === "--config") assignment = args[index += 1];
1208
- else if (arg.startsWith("--config=")) assignment = arg.slice(9);
1209
- else if (arg.startsWith("-c=")) assignment = arg.slice(3);
1210
- else if (arg.startsWith("-c") && arg.length > 2) assignment = arg.slice(2);
1211
- else {
1212
- forwardedArgs.push(arg);
1213
- continue;
1214
- }
1215
- if (!assignment) throw new Error(`Missing value for legacy Codex ACP option ${arg}`);
1216
- hadOverrides = true;
1217
- config = mergeConfigRecords(config, parseLegacyCodexConfigAssignment(assignment));
1218
- }
1219
- return {
1220
- config,
1221
- forwardedArgs,
1222
- hadOverrides
1223
- };
1224
- }
1225
- function resolveCodexAdapterLaunch(configuredCommand) {
1226
- const legacyAdapterArgs = extractConfiguredAdapterArgs({
1227
- configuredCommand,
1228
- packageName: LEGACY_CODEX_ACP_PACKAGE,
1229
- binName: CODEX_ACP_BIN
1230
- });
1231
- if (legacyAdapterArgs) {
1232
- const migration = migrateLegacyCodexArgs(legacyAdapterArgs);
1233
- return {
1234
- args: [...migration.hadOverrides ? [OPENCLAW_CODEX_CONFIG_ARG, JSON.stringify(migration.config)] : [], ...migration.forwardedArgs],
1235
- ...migration.hadOverrides ? { migratedConfig: migration.config } : {}
1236
- };
1237
- }
1238
- const maintainedAdapterArgs = extractConfiguredAdapterArgs({
1239
- configuredCommand,
1240
- packageName: CODEX_ACP_PACKAGE,
1241
- binName: CODEX_ACP_BIN
1242
- });
1243
- if (!maintainedAdapterArgs) return;
1244
- return { args: maintainedAdapterArgs };
1245
- }
1246
- async function persistMigratedCodexMcpConfig(params) {
1247
- const mcpServers = params.migratedConfig?.mcp_servers;
1248
- if (!isRecord(mcpServers)) return;
1249
- const configPath = path.join(params.codexHome, "config.toml");
1250
- const merged = mergeConfigRecords(parse(await fs$1.readFile(configPath, "utf8")), { mcp_servers: mcpServers });
1251
- await fs$1.writeFile(configPath, stringify(merged), "utf8");
1252
- }
1253
- function buildClaudeAcpWrapperCommand(wrapperPath, configuredCommand) {
1254
- const configuredAdapterArgs = extractConfiguredAdapterArgs({
1255
- configuredCommand,
1256
- packageName: CLAUDE_ACP_PACKAGE,
1257
- binName: CLAUDE_ACP_BIN
1258
- });
1259
- if (configuredAdapterArgs) return buildWrapperCommand(wrapperPath, configuredAdapterArgs);
1260
- return configuredCommand ?? buildWrapperCommand(wrapperPath);
1261
- }
1262
- /** Prepare ACPX agent commands and isolated auth homes for Codex/Claude adapters. */
1263
- async function prepareAcpxCodexAuthConfig(params) {
1264
- params.logger;
1265
- const codexBaseDir = path.join(params.stateDir, "acpx");
1266
- const configuredCodexCommand = params.pluginConfig.agents.codex;
1267
- const configuredClaudeCommand = params.pluginConfig.agents.claude;
1268
- const codexLaunch = resolveCodexAdapterLaunch(configuredCodexCommand);
1269
- await persistMigratedCodexMcpConfig({
1270
- codexHome: await prepareIsolatedCodexHome({
1271
- baseDir: codexBaseDir,
1272
- workspaceDir: params.pluginConfig.cwd
1273
- }),
1274
- migratedConfig: codexLaunch?.migratedConfig
1275
- });
1276
- const installedCodexBinPath = await (params.resolveInstalledCodexAcpBinPath ?? resolveInstalledCodexAcpBinPath)();
1277
- const installedClaudeBinPath = await (params.resolveInstalledClaudeAcpBinPath ?? resolveInstalledClaudeAcpBinPath)();
1278
- const wrapperPath = await writeAdapterWrapper(codexBaseDir, "codex-acp-wrapper.mjs", buildCodexAcpWrapperScript(installedCodexBinPath));
1279
- const claudeWrapperPath = await writeAdapterWrapper(codexBaseDir, "claude-agent-acp-wrapper.mjs", buildClaudeAcpWrapperScript(installedClaudeBinPath));
1280
- return {
1281
- ...params.pluginConfig,
1282
- agents: {
1283
- ...params.pluginConfig.agents,
1284
- codex: buildWrapperCommand(wrapperPath, codexLaunch?.args ?? [RUN_CONFIGURED_COMMAND_SENTINEL, ...splitCommandParts(configuredCodexCommand ?? [])]),
1285
- claude: buildClaudeAcpWrapperCommand(claudeWrapperPath, configuredClaudeCommand)
1286
- }
1287
- };
1288
- }
1289
- //#endregion
1290
- //#region extensions/acpx/src/service.ts
1291
- /**
1292
- * ACPX plugin service lifecycle. It resolves config, prepares isolated adapter
1293
- * wrappers, registers the ACP backend, and manages startup/cleanup probes.
1294
- */
1295
- var service_exports = /* @__PURE__ */ __exportAll({
1296
- createAcpxRuntimeService: () => createAcpxRuntimeService,
1297
- resolveAcpxTimerTimeoutMs: () => resolveAcpxTimerTimeoutMs
1298
- });
1299
- const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
1300
- const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
1301
- const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-CSNBTQIs.js"));
1302
- /** Convert ACPX timeout seconds into timer-safe milliseconds. */
1303
- function resolveAcpxTimerTimeoutMs(timeoutSeconds) {
1304
- if (timeoutSeconds === void 0) return;
1305
- return finiteSecondsToTimerSafeMilliseconds(timeoutSeconds) ?? 1;
1306
- }
1307
- function createLazyDefaultRuntime(params) {
1308
- let runtime = null;
1309
- let runtimePromise = null;
1310
- async function resolveRuntime() {
1311
- if (runtime) return runtime;
1312
- runtimePromise ??= loadRuntimeModule().then(async (module) => {
1313
- const names = await fs$1.readdir(path.join(params.pluginConfig.stateDir, "sessions")).catch((error) => {
1314
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
1315
- throw error;
1316
- });
1317
- const legacyBareSessionKeys = /* @__PURE__ */ new Set();
1318
- for (const name of names) {
1319
- if (!name.endsWith(".json")) continue;
1320
- const recordId = decodeURIComponent(name.slice(0, -5));
1321
- if (!recordId.startsWith("agent:") && !recordId.startsWith(".openclaw-owner-") && !recordId.includes(":oneshot:")) legacyBareSessionKeys.add(recordId.toLowerCase());
1322
- }
1323
- runtime = new module.AcpxRuntime({
1324
- cwd: params.pluginConfig.cwd,
1325
- openclawLegacyBareSessionKeys: legacyBareSessionKeys,
1326
- openclawGatewayInstanceId: params.gatewayInstanceId,
1327
- openclawProcessLeaseStore: params.processLeaseStore,
1328
- openclawWrapperRoot: params.wrapperRoot,
1329
- sessionStore: module.createFileSessionStore({ stateDir: params.pluginConfig.stateDir }),
1330
- agentRegistry: module.createAgentRegistry({ overrides: params.pluginConfig.agents }),
1331
- probeAgent: params.pluginConfig.probeAgent,
1332
- mcpServers: toAcpMcpServers(params.pluginConfig.mcpServers),
1333
- pluginToolsMcpBridgeEnabled: params.pluginConfig.pluginToolsMcpBridge,
1334
- openclawToolsMcpBridgeEnabled: params.pluginConfig.openClawToolsMcpBridge,
1335
- permissionMode: params.pluginConfig.permissionMode,
1336
- nonInteractivePermissions: params.pluginConfig.nonInteractivePermissions,
1337
- elicitationModes: ["form", "url"],
1338
- timeoutMs: resolveAcpxTimerTimeoutMs(params.pluginConfig.timeoutSeconds)
1339
- });
1340
- return runtime;
1341
- });
1342
- return await runtimePromise;
1343
- }
1344
- return {
1345
- ...createLazyAcpRuntimeProxy(resolveRuntime),
1346
- isHealthy() {
1347
- return runtime?.isHealthy() ?? false;
1348
- }
1349
- };
1350
- }
1351
- function formatDoctorDetail(detail) {
1352
- if (!detail) return null;
1353
- if (typeof detail === "string") return detail.trim() || null;
1354
- if (detail instanceof Error) return formatErrorMessage(detail);
1355
- if (typeof detail === "object") try {
1356
- return JSON.stringify(detail) ?? inspect(detail, {
1357
- breakLength: Infinity,
1358
- depth: 3
1359
- });
1360
- } catch {
1361
- return inspect(detail, {
1362
- breakLength: Infinity,
1363
- depth: 3
1364
- });
1365
- }
1366
- if (typeof detail === "number" || typeof detail === "boolean" || typeof detail === "bigint" || typeof detail === "symbol") return detail.toString();
1367
- return inspect(detail, {
1368
- breakLength: Infinity,
1369
- depth: 3
1370
- });
1371
- }
1372
- function formatDoctorFailureMessage(report) {
1373
- const detailText = report.details?.map(formatDoctorDetail).filter(Boolean).join("; ").trim();
1374
- return detailText ? `${report.message} (${detailText})` : report.message;
1375
- }
1376
- function resolveAllowedAgentsProbeAgent(ctx) {
1377
- for (const agent of ctx.config.acp?.allowedAgents ?? []) {
1378
- const normalized = normalizeLowercaseStringOrEmpty(agent);
1379
- if (normalized) return normalized;
1380
- }
1381
- }
1382
- async function measureAcpxStartup(ctx, name, run) {
1383
- return ctx.startupTrace ? await ctx.startupTrace.measure(name, run) : await run();
1384
- }
1385
- function detailAcpxStartup(ctx, name, metrics) {
1386
- ctx.startupTrace?.detail?.(name, metrics);
1387
- }
1388
- function shouldRunStartupProbe(env = process.env) {
1389
- return env[ENABLE_STARTUP_PROBE_ENV] !== "0";
1390
- }
1391
- function shouldProbeRuntimeAtStartup(env = process.env) {
1392
- return shouldRunStartupProbe(env) && env[SKIP_RUNTIME_PROBE_ENV] !== "1";
1393
- }
1394
- async function withStartupProbeTimeout(params) {
1395
- let timeout;
1396
- const timeoutMs = resolveAcpxTimerTimeoutMs(params.timeoutSeconds) ?? 1;
1397
- try {
1398
- return await Promise.race([params.promise, new Promise((_, reject) => {
1399
- timeout = setTimeout(() => {
1400
- reject(/* @__PURE__ */ new Error(`embedded acpx runtime backend startup probe timed out after ${params.timeoutSeconds}s`));
1401
- }, timeoutMs);
1402
- timeout.unref?.();
1403
- })]);
1404
- } finally {
1405
- if (timeout) clearTimeout(timeout);
1406
- }
1407
- }
1408
- function openGatewayInstanceStateStore(openKeyedStore) {
1409
- return openKeyedStore({
1410
- namespace: ACPX_GATEWAY_INSTANCE_NAMESPACE,
1411
- maxEntries: 1
1412
- });
1413
- }
1414
- async function resolveGatewayInstanceId(openKeyedStore) {
1415
- const store = openGatewayInstanceStateStore(openKeyedStore);
1416
- const existing = normalizeAcpxGatewayInstanceRecord(await store.lookup(ACPX_GATEWAY_INSTANCE_KEY));
1417
- if (existing) return existing.instanceId;
1418
- const next = randomUUID();
1419
- await store.register(ACPX_GATEWAY_INSTANCE_KEY, {
1420
- instanceId: next,
1421
- createdAt: Date.now()
1422
- });
1423
- return next;
1424
- }
1425
- async function reapOpenAcpxProcessLeases(params) {
1426
- const leases = await params.leaseStore.listOpen(params.gatewayInstanceId);
1427
- const inspectedPids = [];
1428
- const terminatedPids = [];
1429
- const legacyWrapperRoots = /* @__PURE__ */ new Set();
1430
- for (const lease of leases) {
1431
- if (lease.rootPid <= 0) {
1432
- legacyWrapperRoots.add(lease.wrapperRoot);
1433
- await params.leaseStore.markState(lease.leaseId, "closing");
1434
- const result = await cleanupOpenClawOwnedAcpxPendingLease({
1435
- leaseId: lease.leaseId,
1436
- gatewayInstanceId: lease.gatewayInstanceId,
1437
- wrapperRoot: lease.wrapperRoot,
1438
- wrapperPath: lease.wrapperPath,
1439
- deps: params.deps
1440
- });
1441
- inspectedPids.push(...result.inspectedPids);
1442
- terminatedPids.push(...result.terminatedPids);
1443
- const retryableEvidenceFailure = result.skippedReason === "ambiguous-root" || result.skippedReason === "process-list-unavailable" || result.skippedReason === "unsupported-platform" || result.skippedReason === "unverified-root" || lease.sessionKey === "openclaw:acpx:probe" && result.skippedReason === "missing-root";
1444
- await params.leaseStore.markState(lease.leaseId, retryableEvidenceFailure ? "open" : result.terminatedPids.length > 0 ? "closed" : "lost");
1445
- continue;
1446
- }
1447
- await params.leaseStore.markState(lease.leaseId, "closing");
1448
- const result = await cleanupOpenClawOwnedAcpxProcessTree({
1449
- rootPid: lease.rootPid,
1450
- expectedLeaseId: lease.leaseId,
1451
- expectedGatewayInstanceId: lease.gatewayInstanceId,
1452
- wrapperRoot: lease.wrapperRoot,
1453
- deps: params.deps
1454
- });
1455
- inspectedPids.push(...result.inspectedPids);
1456
- terminatedPids.push(...result.terminatedPids);
1457
- await params.leaseStore.markState(lease.leaseId, result.skippedReason === "process-list-unavailable" || result.skippedReason === "unsupported-platform" ? "open" : result.terminatedPids.length > 0 ? "closed" : "lost");
1458
- }
1459
- for (const wrapperRoot of legacyWrapperRoots) {
1460
- const legacyResult = await reapStaleOpenClawOwnedAcpxOrphans({
1461
- wrapperRoot,
1462
- deps: params.deps
1463
- });
1464
- inspectedPids.push(...legacyResult.inspectedPids);
1465
- terminatedPids.push(...legacyResult.terminatedPids);
1466
- }
1467
- return {
1468
- inspectedPids,
1469
- terminatedPids
1470
- };
1471
- }
1472
- /** Create the ACPX plugin service that owns runtime registration and cleanup. */
1473
- function createAcpxRuntimeService(params) {
1474
- let runtime = null;
1475
- let lifecycleRevision = 0;
1476
- return {
1477
- id: "acpx-runtime",
1478
- async start(ctx) {
1479
- if (process.env.OPENCLAW_SKIP_ACPX_RUNTIME === "1") {
1480
- ctx.logger.info("skipping embedded acpx runtime backend (OPENCLAW_SKIP_ACPX_RUNTIME=1)");
1481
- return;
1482
- }
1483
- const openKeyedStore = params.openKeyedStore;
1484
- if (!openKeyedStore) throw new Error("ACPX runtime service requires plugin keyed state");
1485
- const basePluginConfig = await measureAcpxStartup(ctx, "config.resolve", () => resolveAcpxPluginConfig({
1486
- rawConfig: params.pluginConfig,
1487
- workspaceDir: ctx.workspaceDir
1488
- }));
1489
- const effectiveBasePluginConfig = {
1490
- ...basePluginConfig,
1491
- probeAgent: basePluginConfig.probeAgent ?? resolveAllowedAgentsProbeAgent(ctx)
1492
- };
1493
- const pluginConfig = await measureAcpxStartup(ctx, "config.prepare-codex-auth", () => prepareAcpxCodexAuthConfig({
1494
- pluginConfig: effectiveBasePluginConfig,
1495
- stateDir: ctx.stateDir,
1496
- logger: ctx.logger
1497
- }));
1498
- const wrapperRoot = path.join(ctx.stateDir, "acpx");
1499
- await measureAcpxStartup(ctx, "filesystem.prepare", async () => {
1500
- await fs$1.mkdir(pluginConfig.stateDir, { recursive: true });
1501
- await fs$1.mkdir(wrapperRoot, { recursive: true });
1502
- });
1503
- const gatewayInstanceId = await measureAcpxStartup(ctx, "gateway-instance-id", () => resolveGatewayInstanceId(openKeyedStore));
1504
- const processLeaseStore = createAcpxProcessLeaseStore({ store: openAcpxProcessLeaseStateStore(openKeyedStore) });
1505
- const startupReap = await measureAcpxStartup(ctx, "process-leases.reap", () => reapOpenAcpxProcessLeases({
1506
- gatewayInstanceId,
1507
- leaseStore: processLeaseStore,
1508
- deps: params.processCleanupDeps
1509
- }));
1510
- if (startupReap.terminatedPids.length > 0) ctx.logger.info(`reaped ${startupReap.terminatedPids.length} stale OpenClaw-owned ACPX process${startupReap.terminatedPids.length === 1 ? "" : "es"}`);
1511
- const startedRuntime = await measureAcpxStartup(ctx, "runtime.create", () => params.runtimeFactory ? params.runtimeFactory({
1512
- pluginConfig,
1513
- gatewayInstanceId,
1514
- processLeaseStore,
1515
- wrapperRoot,
1516
- logger: ctx.logger
1517
- }) : createLazyDefaultRuntime({
1518
- pluginConfig,
1519
- gatewayInstanceId,
1520
- processLeaseStore,
1521
- wrapperRoot,
1522
- logger: ctx.logger
1523
- }));
1524
- runtime = startedRuntime;
1525
- const shouldProbeRuntime = shouldProbeRuntimeAtStartup();
1526
- detailAcpxStartup(ctx, "probe-policy", [["startupProbeEnabledCount", shouldProbeRuntime ? 1 : 0], ["probeAgent", pluginConfig.probeAgent ?? "default"]]);
1527
- await measureAcpxStartup(ctx, "backend.register", () => {
1528
- const backend = {
1529
- runtime: startedRuntime,
1530
- ...shouldProbeRuntime ? { healthy: () => runtime?.isHealthy() ?? false } : {}
1531
- };
1532
- params.backendLifecycle.publish(backend);
1533
- ctx.logger.info(`embedded acpx runtime backend registered (cwd: ${pluginConfig.cwd})`);
1534
- });
1535
- if (!shouldProbeRuntime) return;
1536
- lifecycleRevision += 1;
1537
- const currentRevision = lifecycleRevision;
1538
- try {
1539
- const doctorReport = await measureAcpxStartup(ctx, "probe.availability", () => withStartupProbeTimeout({
1540
- promise: startedRuntime.doctor(),
1541
- timeoutSeconds: pluginConfig.timeoutSeconds ?? 120
1542
- }));
1543
- if (currentRevision !== lifecycleRevision) return;
1544
- if (doctorReport.ok) {
1545
- detailAcpxStartup(ctx, "probe.result", [["healthyCount", 1]]);
1546
- ctx.logger.info("embedded acpx runtime backend ready");
1547
- return;
1548
- }
1549
- detailAcpxStartup(ctx, "probe.result", [["healthyCount", 0]]);
1550
- ctx.logger.warn(`embedded acpx runtime backend probe failed: ${formatDoctorFailureMessage(doctorReport)}`);
1551
- } catch (err) {
1552
- if (currentRevision !== lifecycleRevision) return;
1553
- detailAcpxStartup(ctx, "probe.result", [["healthyCount", 0]]);
1554
- ctx.logger.warn(`embedded acpx runtime setup failed: ${formatErrorMessage(err)}`);
1555
- }
1556
- },
1557
- async stop(_ctx) {
1558
- lifecycleRevision += 1;
1559
- if (runtime) params.backendLifecycle.retract(runtime);
1560
- runtime = null;
1561
- }
1562
- };
1563
- }
1564
- //#endregion
1565
- export { CODEX_ACP_PACKAGE as a, createAcpxRuntimeService, isOpenClawLeaseAwareAcpxProcessCommand as i, cleanupOpenClawOwnedAcpxPendingLease as n, OPENCLAW_CODEX_CONFIG_ARG as o, cleanupOpenClawOwnedAcpxProcessTree as r, resolveAcpxTimerTimeoutMs, service_exports as t };