@openclaw/acpx 2026.10.5-beta.3 → 2026.10.10-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/.setup/{harness-attempt-CRe4WIFt.mjs → harness-attempt-lu4_bMfd.mjs} +2 -2
- package/dist/.setup/{pi-session-catalog-runtime-7jc50ZJ4.mjs → pi-session-catalog-runtime-BAM1pEMw.mjs} +43 -170
- package/dist/.setup/{register.runtime-DfAHoa8o.mjs → register.runtime-CdG6bROH.mjs} +1 -1
- package/dist/.setup/{service-De_oVTL3.mjs → service-5eVJSjDE.mjs} +326 -313
- package/dist/.setup/{session-owner-migration-DECUY80a.mjs → session-owner-migration-duvwZfxk.mjs} +4 -4
- package/dist/doctor-contract-api.js +3 -3
- package/dist/index.js +6 -8
- package/dist/register.runtime.js +1 -2
- package/package.json +6 -6
- package/skills/acp-router/SKILL.md +12 -5
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { a as isClaudeAcpCommand, c as renderAgentCommand, d as CODEX_ACP_BIN, f as CODEX_ACP_PACKAGE, i as toAcpMcpServers, l as resolveAgentCommand, m as OPENCLAW_CODEX_CONFIG_ARG, n as resolveAcpxPluginRoot, o as isCodexAcpCommand, p as LEGACY_CODEX_ACP_PACKAGE, r as resolveOpenClawRoot, s as isOpenClawBridgeCommand, t as resolveAcpxPluginConfig, u as splitCommandParts } from "./config-D0ATuiVJ.mjs";
|
|
2
2
|
import { AcpRuntimeError } from "../runtime-api.js";
|
|
3
3
|
import { t as resolveAcpxSessionResource } from "./session-resource-DKJsUJPu.mjs";
|
|
4
|
-
import { a as hashAcpxProcessCommand, adoptAcpxStateDirectory, c as withAcpxLeaseArgs, d as normalizeAcpxGatewayInstanceRecord, i as createAcpxProcessLeaseStore, l as ACPX_GATEWAY_INSTANCE_KEY, n as OPENCLAW_ACPX_LEASE_ID_ARG, o as openAcpxProcessLeaseStateStore, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, s as readAcpxProcessLeaseIdentity, t as ACPX_PROBE_LEASE_SESSION_KEY, u as ACPX_GATEWAY_INSTANCE_NAMESPACE } from "./session-owner-migration-
|
|
4
|
+
import { a as hashAcpxProcessCommand, adoptAcpxStateDirectory, c as withAcpxLeaseArgs, d as normalizeAcpxGatewayInstanceRecord, i as createAcpxProcessLeaseStore, l as ACPX_GATEWAY_INSTANCE_KEY, n as OPENCLAW_ACPX_LEASE_ID_ARG, o as openAcpxProcessLeaseStateStore, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, s as readAcpxProcessLeaseIdentity, t as ACPX_PROBE_LEASE_SESSION_KEY, u as ACPX_GATEWAY_INSTANCE_NAMESPACE } from "./session-owner-migration-duvwZfxk.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
|
-
import { AcpxRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, isRequestedModelUnsupportedError } from "acpx/runtime";
|
|
7
6
|
import { finiteSecondsToTimerSafeMilliseconds, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
8
7
|
import fs from "node:fs";
|
|
9
|
-
import path
|
|
8
|
+
import path from "node:path";
|
|
10
9
|
import { asOptionalObjectRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
10
|
import os, { availableParallelism } from "node:os";
|
|
12
11
|
import { randomUUID } from "node:crypto";
|
|
13
12
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
14
13
|
import { escapeRegExp, sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
15
14
|
import fs$1 from "node:fs/promises";
|
|
15
|
+
import { AcpxRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, isRequestedModelUnsupportedError } from "acpx/runtime";
|
|
16
16
|
import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
|
|
17
17
|
import { tryReadJson } from "@openclaw/fs-safe/json";
|
|
18
18
|
import { parse, stringify } from "smol-toml";
|
|
@@ -22,244 +22,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
22
22
|
import { isDeepStrictEqual } from "node:util";
|
|
23
23
|
import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
24
24
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
25
|
-
//#region extensions/acpx/src/
|
|
26
|
-
function stripTomlComment(line) {
|
|
27
|
-
let quote = null;
|
|
28
|
-
let escaping = false;
|
|
29
|
-
for (let index = 0; index < line.length; index += 1) {
|
|
30
|
-
const ch = line[index];
|
|
31
|
-
if (escaping) {
|
|
32
|
-
escaping = false;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (quote === "\"" && ch === "\\") {
|
|
36
|
-
escaping = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (quote) {
|
|
40
|
-
if (ch === quote) quote = null;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (ch === "'" || ch === "\"") {
|
|
44
|
-
quote = ch;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (ch === "#") return line.slice(0, index);
|
|
48
|
-
}
|
|
49
|
-
return line;
|
|
50
|
-
}
|
|
51
|
-
function parseTomlString(value) {
|
|
52
|
-
const trimmed = value.trim();
|
|
53
|
-
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) try {
|
|
54
|
-
return JSON.parse(trimmed);
|
|
55
|
-
} catch {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
59
|
-
}
|
|
60
|
-
function parseTomlDottedKey(value) {
|
|
61
|
-
const parts = [];
|
|
62
|
-
let current = "";
|
|
63
|
-
let quote = null;
|
|
64
|
-
let escaping = false;
|
|
65
|
-
for (const ch of value.trim()) {
|
|
66
|
-
if (escaping) {
|
|
67
|
-
current += ch;
|
|
68
|
-
escaping = false;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (quote === "\"" && ch === "\\") {
|
|
72
|
-
current += ch;
|
|
73
|
-
escaping = true;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (quote) {
|
|
77
|
-
current += ch;
|
|
78
|
-
if (ch === quote) quote = null;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (ch === "'" || ch === "\"") {
|
|
82
|
-
quote = ch;
|
|
83
|
-
current += ch;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (ch === ".") {
|
|
87
|
-
parts.push(current.trim());
|
|
88
|
-
current = "";
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
current += ch;
|
|
92
|
-
}
|
|
93
|
-
if (current.trim()) parts.push(current.trim());
|
|
94
|
-
return parts.map((part) => parseTomlString(part) ?? part);
|
|
95
|
-
}
|
|
96
|
-
function parseProjectHeader(line) {
|
|
97
|
-
const parts = parseTableHeader(line);
|
|
98
|
-
return parts?.length === 2 && parts[0] === "projects" ? parts[1] : void 0;
|
|
99
|
-
}
|
|
100
|
-
function parseTrustedInlineProjectEntries(value) {
|
|
101
|
-
const trusted = [];
|
|
102
|
-
for (const match of value.matchAll(/(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*\{(?<body>[^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g)) {
|
|
103
|
-
const key = match.groups?.key;
|
|
104
|
-
const body = match.groups?.body;
|
|
105
|
-
if (!key || !body || !/\btrust_level\s*=\s*["']trusted["']/.test(body)) continue;
|
|
106
|
-
const projectPath = parseTomlString(key) ?? key.trim();
|
|
107
|
-
if (projectPath) trusted.push(projectPath);
|
|
108
|
-
}
|
|
109
|
-
return trusted;
|
|
110
|
-
}
|
|
111
|
-
function extractTrustedCodexProjectPaths(configToml) {
|
|
112
|
-
const trusted = /* @__PURE__ */ new Set();
|
|
113
|
-
let currentProjectPath;
|
|
114
|
-
let inProjectsTable = false;
|
|
115
|
-
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
116
|
-
const line = stripTomlComment(rawLine).trim();
|
|
117
|
-
if (!line) continue;
|
|
118
|
-
if (line.startsWith("[")) {
|
|
119
|
-
currentProjectPath = parseProjectHeader(line);
|
|
120
|
-
inProjectsTable = line === "[projects]";
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (currentProjectPath && /^trust_level\s*=\s*["']trusted["']\s*$/.test(line)) {
|
|
124
|
-
trusted.add(currentProjectPath);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const assignment = /^(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*(?<value>.+)$/.exec(line);
|
|
128
|
-
const rawKey = assignment?.groups?.key;
|
|
129
|
-
const rawValue = assignment?.groups?.value;
|
|
130
|
-
if (!rawKey || rawValue === void 0) continue;
|
|
131
|
-
const key = parseTomlString(rawKey) ?? rawKey;
|
|
132
|
-
const value = rawValue.trim();
|
|
133
|
-
if (inProjectsTable && /^\{.*\}$/.test(value)) {
|
|
134
|
-
if (/\btrust_level\s*=\s*["']trusted["']/.test(value) && key) trusted.add(key);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (key === "projects" || inProjectsTable) for (const projectPath of parseTrustedInlineProjectEntries(value)) trusted.add(projectPath);
|
|
138
|
-
}
|
|
139
|
-
return Array.from(trusted);
|
|
140
|
-
}
|
|
141
|
-
const INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
142
|
-
"model",
|
|
143
|
-
"model_provider",
|
|
144
|
-
"model_reasoning_effort",
|
|
145
|
-
"sandbox_mode"
|
|
146
|
-
]);
|
|
147
|
-
const INHERITED_MODEL_PROVIDER_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
148
|
-
"name",
|
|
149
|
-
"base_url",
|
|
150
|
-
"wire_api",
|
|
151
|
-
"env_key",
|
|
152
|
-
"env_key_instructions",
|
|
153
|
-
"requires_openai_auth",
|
|
154
|
-
"request_max_retries",
|
|
155
|
-
"stream_max_retries",
|
|
156
|
-
"stream_idle_timeout_ms"
|
|
157
|
-
]);
|
|
158
|
-
function parseTableHeader(line) {
|
|
159
|
-
const trimmed = line.trim();
|
|
160
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]") || trimmed.startsWith("[[")) return;
|
|
161
|
-
return parseTomlDottedKey(trimmed.slice(1, -1));
|
|
162
|
-
}
|
|
163
|
-
function isInheritedModelProviderTable(parts) {
|
|
164
|
-
return parts?.[0] === "model_providers" && parts.length === 2;
|
|
165
|
-
}
|
|
166
|
-
function parseTopLevelAssignmentKey(line) {
|
|
167
|
-
return /^(?<key>[A-Za-z0-9_-]+)\s*=\s*(?<value>.+)$/.exec(line)?.groups?.key;
|
|
168
|
-
}
|
|
169
|
-
function extractInheritedCodexRuntimeConfig(configToml) {
|
|
170
|
-
const inheritedLines = [];
|
|
171
|
-
let inAnyTable = false;
|
|
172
|
-
let inInheritedTable = false;
|
|
173
|
-
let pendingInheritedTableHeader = "";
|
|
174
|
-
function flushInheritedTableHeader() {
|
|
175
|
-
if (!pendingInheritedTableHeader) return;
|
|
176
|
-
if (inheritedLines.length > 0 && inheritedLines[inheritedLines.length - 1] !== "") inheritedLines.push("");
|
|
177
|
-
inheritedLines.push(pendingInheritedTableHeader);
|
|
178
|
-
pendingInheritedTableHeader = "";
|
|
179
|
-
}
|
|
180
|
-
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
181
|
-
const trimmedLine = rawLine.trim();
|
|
182
|
-
const semanticLine = stripTomlComment(rawLine).trim();
|
|
183
|
-
if (trimmedLine.startsWith("[")) {
|
|
184
|
-
const tableParts = parseTableHeader(trimmedLine);
|
|
185
|
-
inAnyTable = true;
|
|
186
|
-
inInheritedTable = isInheritedModelProviderTable(tableParts);
|
|
187
|
-
if (inInheritedTable) pendingInheritedTableHeader = rawLine.trimEnd();
|
|
188
|
-
else pendingInheritedTableHeader = "";
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (inInheritedTable) {
|
|
192
|
-
if (!semanticLine) continue;
|
|
193
|
-
const key = parseTopLevelAssignmentKey(semanticLine);
|
|
194
|
-
if (!key || !INHERITED_MODEL_PROVIDER_CONFIG_KEYS.has(key)) continue;
|
|
195
|
-
flushInheritedTableHeader();
|
|
196
|
-
inheritedLines.push(rawLine.trimEnd());
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (inAnyTable) continue;
|
|
200
|
-
const key = parseTopLevelAssignmentKey(semanticLine);
|
|
201
|
-
if (!key) continue;
|
|
202
|
-
if (!INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS.has(key)) continue;
|
|
203
|
-
inheritedLines.push(rawLine.trimEnd());
|
|
204
|
-
}
|
|
205
|
-
return inheritedLines.join("\n");
|
|
206
|
-
}
|
|
207
|
-
function renderIsolatedCodexConfig(params) {
|
|
208
|
-
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));
|
|
209
|
-
return [
|
|
210
|
-
"# Generated by OpenClaw for Codex ACP sessions.",
|
|
211
|
-
params.sourceConfigToml ? extractInheritedCodexRuntimeConfig(params.sourceConfigToml) : "",
|
|
212
|
-
...normalized.flatMap((projectPath) => [
|
|
213
|
-
"",
|
|
214
|
-
`[projects.${JSON.stringify(projectPath)}]`,
|
|
215
|
-
"trust_level = \"trusted\""
|
|
216
|
-
]),
|
|
217
|
-
""
|
|
218
|
-
].filter((line, index, lines) => !(line === "" && lines[index - 1] === "")).join("\n");
|
|
219
|
-
}
|
|
220
|
-
//#endregion
|
|
221
|
-
//#region extensions/acpx/src/codex-auth-bridge.ts
|
|
222
|
-
/**
|
|
223
|
-
* Prepares isolated Codex and Claude ACP wrapper commands for ACPX. The bridge
|
|
224
|
-
* copies safe auth/config state into plugin-owned homes and redacts diagnostics.
|
|
225
|
-
*/
|
|
226
|
-
const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
|
|
227
|
-
const CLAUDE_ACP_BIN = "claude-agent-acp";
|
|
25
|
+
//#region extensions/acpx/src/adapter-wrapper-script.ts
|
|
228
26
|
const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
|
|
229
|
-
const requireFromHere$1 = createRequire(import.meta.url);
|
|
230
|
-
function readSelfManifest() {
|
|
231
|
-
const manifestPath = path.join(resolveAcpxPluginRoot(import.meta.url), "package.json");
|
|
232
|
-
return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
233
|
-
}
|
|
234
|
-
function readManifestDependencyVersion(packageName) {
|
|
235
|
-
const version = readSelfManifest().dependencies?.[packageName];
|
|
236
|
-
if (typeof version !== "string" || version.trim() === "") throw new Error(`Missing ${packageName} dependency version in @openclaw/acpx manifest`);
|
|
237
|
-
return version;
|
|
238
|
-
}
|
|
239
|
-
const CODEX_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CODEX_ACP_PACKAGE);
|
|
240
|
-
const CLAUDE_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CLAUDE_ACP_PACKAGE);
|
|
241
|
-
function basename(value) {
|
|
242
|
-
return value.split(/[\\/]/).pop() ?? value;
|
|
243
|
-
}
|
|
244
|
-
function resolvePackageBinPath(packageJsonPath, manifest, binName) {
|
|
245
|
-
const { bin } = manifest;
|
|
246
|
-
const relativeBinPath = typeof bin === "string" ? bin : bin && typeof bin === "object" ? bin[binName] : void 0;
|
|
247
|
-
if (typeof relativeBinPath !== "string" || relativeBinPath.trim() === "") return;
|
|
248
|
-
return path.resolve(path.dirname(packageJsonPath), relativeBinPath);
|
|
249
|
-
}
|
|
250
|
-
async function resolveInstalledAcpPackageBinPath(packageName, binName) {
|
|
251
|
-
try {
|
|
252
|
-
const packageJsonPath = requireFromHere$1.resolve(`${packageName}/package.json`);
|
|
253
|
-
const manifest = await tryReadJson(packageJsonPath);
|
|
254
|
-
if (manifest?.name !== packageName) return;
|
|
255
|
-
const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
|
|
256
|
-
if (!binPath) return;
|
|
257
|
-
await fs$1.access(binPath);
|
|
258
|
-
return binPath;
|
|
259
|
-
} catch {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
27
|
const DIAGNOSTIC_REDACTION_RULES = [
|
|
264
28
|
{
|
|
265
29
|
source: String.raw`(authorization\s*[:=]\s*bearer\s+)[^\s'"<>]+`,
|
|
@@ -375,6 +139,7 @@ import { spawn } from "node:child_process";
|
|
|
375
139
|
import { StringDecoder } from "node:string_decoder";
|
|
376
140
|
import { fileURLToPath } from "node:url";
|
|
377
141
|
|
|
142
|
+
function main() {
|
|
378
143
|
${params.envSetup}
|
|
379
144
|
const stderrLogFileNamePrefix = ${params.stderrLogFileNamePrefix ? JSON.stringify(params.stderrLogFileNamePrefix) : "undefined"};
|
|
380
145
|
const stderrLogMaxChars = 256 * 1024;
|
|
@@ -602,7 +367,8 @@ const args =
|
|
|
602
367
|
|
|
603
368
|
if (!command) {
|
|
604
369
|
console.error("[openclaw] missing configured ${params.displayName} ACP command");
|
|
605
|
-
process.
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
return;
|
|
606
372
|
}
|
|
607
373
|
|
|
608
374
|
const child = spawn(command, args, {
|
|
@@ -620,6 +386,18 @@ child.stderr?.on("data", (chunk) => {
|
|
|
620
386
|
let forceKillTimer;
|
|
621
387
|
let orphanCleanupStarted = false;
|
|
622
388
|
let childExitCode = 1;
|
|
389
|
+
let childFailed = false;
|
|
390
|
+
let childClosed = false;
|
|
391
|
+
|
|
392
|
+
function finishWrapper() {
|
|
393
|
+
if (!childClosed || forceKillTimer) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
for (const [signal, listener] of signalListeners) {
|
|
397
|
+
process.off(signal, listener);
|
|
398
|
+
}
|
|
399
|
+
process.exitCode = childFailed ? 1 : childExitCode;
|
|
400
|
+
}
|
|
623
401
|
|
|
624
402
|
function killChildTree(signal, options = {}) {
|
|
625
403
|
if (!child.pid || (!options.force && child.killed)) {
|
|
@@ -638,10 +416,11 @@ function killChildTree(signal, options = {}) {
|
|
|
638
416
|
child.kill(signal);
|
|
639
417
|
}
|
|
640
418
|
|
|
419
|
+
const signalListeners = new Map();
|
|
641
420
|
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
421
|
+
const listener = () => killChildTree(signal);
|
|
422
|
+
signalListeners.set(signal, listener);
|
|
423
|
+
process.on(signal, listener);
|
|
645
424
|
}
|
|
646
425
|
|
|
647
426
|
const originalParentPid = process.ppid;
|
|
@@ -669,27 +448,28 @@ const parentWatcher =
|
|
|
669
448
|
// Keep the wrapper alive long enough for stubborn adapters to receive
|
|
670
449
|
// a forced fallback signal after SIGTERM.
|
|
671
450
|
forceKillTimer = setTimeout(() => {
|
|
672
|
-
|
|
673
|
-
|
|
451
|
+
try {
|
|
452
|
+
killChildTree("SIGKILL", { force: true });
|
|
453
|
+
} finally {
|
|
454
|
+
forceKillTimer = undefined;
|
|
455
|
+
childExitCode = 1;
|
|
456
|
+
finishWrapper();
|
|
457
|
+
}
|
|
674
458
|
}, 1_500);
|
|
675
459
|
}, 1_000);
|
|
676
460
|
parentWatcher?.unref?.();
|
|
677
461
|
|
|
678
462
|
child.on("error", (error) => {
|
|
679
463
|
console.error(\`[openclaw] failed to launch ${params.displayName} ACP wrapper: \${error.message}\`);
|
|
680
|
-
|
|
464
|
+
childFailed = true;
|
|
681
465
|
});
|
|
682
466
|
|
|
683
467
|
child.on("exit", (code, signal) => {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
468
|
+
// Descendants can retain stdio after the direct child exits. Keep watching
|
|
469
|
+
// the wrapper parent until close, including after this status notification.
|
|
687
470
|
if (orphanCleanupStarted) {
|
|
688
471
|
return;
|
|
689
472
|
}
|
|
690
|
-
if (forceKillTimer) {
|
|
691
|
-
clearTimeout(forceKillTimer);
|
|
692
|
-
}
|
|
693
473
|
if (code !== null) {
|
|
694
474
|
childExitCode = code;
|
|
695
475
|
return;
|
|
@@ -699,10 +479,254 @@ child.on("exit", (code, signal) => {
|
|
|
699
479
|
|
|
700
480
|
child.on("close", () => {
|
|
701
481
|
finishStderrLog();
|
|
702
|
-
|
|
482
|
+
if (parentWatcher) {
|
|
483
|
+
clearInterval(parentWatcher);
|
|
484
|
+
}
|
|
485
|
+
childClosed = true;
|
|
486
|
+
finishWrapper();
|
|
703
487
|
});
|
|
488
|
+
}
|
|
489
|
+
main();
|
|
704
490
|
`;
|
|
705
491
|
}
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region extensions/acpx/src/codex-trust-config.ts
|
|
494
|
+
function stripTomlComment(line) {
|
|
495
|
+
let quote = null;
|
|
496
|
+
let escaping = false;
|
|
497
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
498
|
+
const ch = line[index];
|
|
499
|
+
if (escaping) {
|
|
500
|
+
escaping = false;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (quote === "\"" && ch === "\\") {
|
|
504
|
+
escaping = true;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (quote) {
|
|
508
|
+
if (ch === quote) quote = null;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (ch === "'" || ch === "\"") {
|
|
512
|
+
quote = ch;
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (ch === "#") return line.slice(0, index);
|
|
516
|
+
}
|
|
517
|
+
return line;
|
|
518
|
+
}
|
|
519
|
+
function parseTomlString(value) {
|
|
520
|
+
const trimmed = value.trim();
|
|
521
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) try {
|
|
522
|
+
return JSON.parse(trimmed);
|
|
523
|
+
} catch {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
527
|
+
}
|
|
528
|
+
function parseTomlDottedKey(value) {
|
|
529
|
+
const parts = [];
|
|
530
|
+
let current = "";
|
|
531
|
+
let quote = null;
|
|
532
|
+
let escaping = false;
|
|
533
|
+
for (const ch of value.trim()) {
|
|
534
|
+
if (escaping) {
|
|
535
|
+
current += ch;
|
|
536
|
+
escaping = false;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (quote === "\"" && ch === "\\") {
|
|
540
|
+
current += ch;
|
|
541
|
+
escaping = true;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (quote) {
|
|
545
|
+
current += ch;
|
|
546
|
+
if (ch === quote) quote = null;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (ch === "'" || ch === "\"") {
|
|
550
|
+
quote = ch;
|
|
551
|
+
current += ch;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (ch === ".") {
|
|
555
|
+
parts.push(current.trim());
|
|
556
|
+
current = "";
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
current += ch;
|
|
560
|
+
}
|
|
561
|
+
if (current.trim()) parts.push(current.trim());
|
|
562
|
+
return parts.map((part) => parseTomlString(part) ?? part);
|
|
563
|
+
}
|
|
564
|
+
function parseProjectHeader(line) {
|
|
565
|
+
const parts = parseTableHeader(line);
|
|
566
|
+
return parts?.length === 2 && parts[0] === "projects" ? parts[1] : void 0;
|
|
567
|
+
}
|
|
568
|
+
function parseTrustedInlineProjectEntries(value) {
|
|
569
|
+
const trusted = [];
|
|
570
|
+
for (const match of value.matchAll(/(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*\{(?<body>[^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g)) {
|
|
571
|
+
const key = match.groups?.key;
|
|
572
|
+
const body = match.groups?.body;
|
|
573
|
+
if (!key || !body || !/\btrust_level\s*=\s*["']trusted["']/.test(body)) continue;
|
|
574
|
+
const projectPath = parseTomlString(key) ?? key.trim();
|
|
575
|
+
if (projectPath) trusted.push(projectPath);
|
|
576
|
+
}
|
|
577
|
+
return trusted;
|
|
578
|
+
}
|
|
579
|
+
function extractTrustedCodexProjectPaths(configToml) {
|
|
580
|
+
const trusted = /* @__PURE__ */ new Set();
|
|
581
|
+
let currentProjectPath;
|
|
582
|
+
let inProjectsTable = false;
|
|
583
|
+
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
584
|
+
const line = stripTomlComment(rawLine).trim();
|
|
585
|
+
if (!line) continue;
|
|
586
|
+
if (line.startsWith("[")) {
|
|
587
|
+
currentProjectPath = parseProjectHeader(line);
|
|
588
|
+
inProjectsTable = line === "[projects]";
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (currentProjectPath && /^trust_level\s*=\s*["']trusted["']\s*$/.test(line)) {
|
|
592
|
+
trusted.add(currentProjectPath);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const assignment = /^(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*(?<value>.+)$/.exec(line);
|
|
596
|
+
const rawKey = assignment?.groups?.key;
|
|
597
|
+
const rawValue = assignment?.groups?.value;
|
|
598
|
+
if (!rawKey || rawValue === void 0) continue;
|
|
599
|
+
const key = parseTomlString(rawKey) ?? rawKey;
|
|
600
|
+
const value = rawValue.trim();
|
|
601
|
+
if (inProjectsTable && /^\{.*\}$/.test(value)) {
|
|
602
|
+
if (/\btrust_level\s*=\s*["']trusted["']/.test(value) && key) trusted.add(key);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (key === "projects" || inProjectsTable) for (const projectPath of parseTrustedInlineProjectEntries(value)) trusted.add(projectPath);
|
|
606
|
+
}
|
|
607
|
+
return Array.from(trusted);
|
|
608
|
+
}
|
|
609
|
+
const INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
610
|
+
"model",
|
|
611
|
+
"model_provider",
|
|
612
|
+
"model_reasoning_effort",
|
|
613
|
+
"sandbox_mode"
|
|
614
|
+
]);
|
|
615
|
+
const INHERITED_MODEL_PROVIDER_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
616
|
+
"name",
|
|
617
|
+
"base_url",
|
|
618
|
+
"wire_api",
|
|
619
|
+
"env_key",
|
|
620
|
+
"env_key_instructions",
|
|
621
|
+
"requires_openai_auth",
|
|
622
|
+
"request_max_retries",
|
|
623
|
+
"stream_max_retries",
|
|
624
|
+
"stream_idle_timeout_ms"
|
|
625
|
+
]);
|
|
626
|
+
function parseTableHeader(line) {
|
|
627
|
+
const trimmed = line.trim();
|
|
628
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]") || trimmed.startsWith("[[")) return;
|
|
629
|
+
return parseTomlDottedKey(trimmed.slice(1, -1));
|
|
630
|
+
}
|
|
631
|
+
function isInheritedModelProviderTable(parts) {
|
|
632
|
+
return parts?.[0] === "model_providers" && parts.length === 2;
|
|
633
|
+
}
|
|
634
|
+
function parseTopLevelAssignmentKey(line) {
|
|
635
|
+
return /^(?<key>[A-Za-z0-9_-]+)\s*=\s*(?<value>.+)$/.exec(line)?.groups?.key;
|
|
636
|
+
}
|
|
637
|
+
function extractInheritedCodexRuntimeConfig(configToml) {
|
|
638
|
+
const inheritedLines = [];
|
|
639
|
+
let inAnyTable = false;
|
|
640
|
+
let inInheritedTable = false;
|
|
641
|
+
let pendingInheritedTableHeader = "";
|
|
642
|
+
function flushInheritedTableHeader() {
|
|
643
|
+
if (!pendingInheritedTableHeader) return;
|
|
644
|
+
if (inheritedLines.length > 0 && inheritedLines[inheritedLines.length - 1] !== "") inheritedLines.push("");
|
|
645
|
+
inheritedLines.push(pendingInheritedTableHeader);
|
|
646
|
+
pendingInheritedTableHeader = "";
|
|
647
|
+
}
|
|
648
|
+
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
649
|
+
const trimmedLine = rawLine.trim();
|
|
650
|
+
const semanticLine = stripTomlComment(rawLine).trim();
|
|
651
|
+
if (trimmedLine.startsWith("[")) {
|
|
652
|
+
const tableParts = parseTableHeader(trimmedLine);
|
|
653
|
+
inAnyTable = true;
|
|
654
|
+
inInheritedTable = isInheritedModelProviderTable(tableParts);
|
|
655
|
+
if (inInheritedTable) pendingInheritedTableHeader = rawLine.trimEnd();
|
|
656
|
+
else pendingInheritedTableHeader = "";
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (inInheritedTable) {
|
|
660
|
+
if (!semanticLine) continue;
|
|
661
|
+
const key = parseTopLevelAssignmentKey(semanticLine);
|
|
662
|
+
if (!key || !INHERITED_MODEL_PROVIDER_CONFIG_KEYS.has(key)) continue;
|
|
663
|
+
flushInheritedTableHeader();
|
|
664
|
+
inheritedLines.push(rawLine.trimEnd());
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (inAnyTable) continue;
|
|
668
|
+
const key = parseTopLevelAssignmentKey(semanticLine);
|
|
669
|
+
if (!key) continue;
|
|
670
|
+
if (!INHERITED_TOP_LEVEL_CODEX_CONFIG_KEYS.has(key)) continue;
|
|
671
|
+
inheritedLines.push(rawLine.trimEnd());
|
|
672
|
+
}
|
|
673
|
+
return inheritedLines.join("\n");
|
|
674
|
+
}
|
|
675
|
+
function renderIsolatedCodexConfig(params) {
|
|
676
|
+
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));
|
|
677
|
+
return [
|
|
678
|
+
"# Generated by OpenClaw for Codex ACP sessions.",
|
|
679
|
+
params.sourceConfigToml ? extractInheritedCodexRuntimeConfig(params.sourceConfigToml) : "",
|
|
680
|
+
...normalized.flatMap((projectPath) => [
|
|
681
|
+
"",
|
|
682
|
+
`[projects.${JSON.stringify(projectPath)}]`,
|
|
683
|
+
"trust_level = \"trusted\""
|
|
684
|
+
]),
|
|
685
|
+
""
|
|
686
|
+
].filter((line, index, lines) => !(line === "" && lines[index - 1] === "")).join("\n");
|
|
687
|
+
}
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region extensions/acpx/src/codex-auth-bridge.ts
|
|
690
|
+
/**
|
|
691
|
+
* Prepares isolated Codex and Claude ACP wrapper commands for ACPX. The bridge
|
|
692
|
+
* copies safe auth/config state into plugin-owned homes and redacts diagnostics.
|
|
693
|
+
*/
|
|
694
|
+
const CLAUDE_ACP_PACKAGE = "@agentclientprotocol/claude-agent-acp";
|
|
695
|
+
const CLAUDE_ACP_BIN = "claude-agent-acp";
|
|
696
|
+
const requireFromHere$1 = createRequire(import.meta.url);
|
|
697
|
+
function readSelfManifest() {
|
|
698
|
+
const manifestPath = path.join(resolveAcpxPluginRoot(import.meta.url), "package.json");
|
|
699
|
+
return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
700
|
+
}
|
|
701
|
+
function readManifestDependencyVersion(packageName) {
|
|
702
|
+
const version = readSelfManifest().dependencies?.[packageName];
|
|
703
|
+
if (typeof version !== "string" || version.trim() === "") throw new Error(`Missing ${packageName} dependency version in @openclaw/acpx manifest`);
|
|
704
|
+
return version;
|
|
705
|
+
}
|
|
706
|
+
const CODEX_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CODEX_ACP_PACKAGE);
|
|
707
|
+
const CLAUDE_ACP_PACKAGE_VERSION = readManifestDependencyVersion(CLAUDE_ACP_PACKAGE);
|
|
708
|
+
function basename(value) {
|
|
709
|
+
return value.split(/[\\/]/).pop() ?? value;
|
|
710
|
+
}
|
|
711
|
+
function resolvePackageBinPath(packageJsonPath, manifest, binName) {
|
|
712
|
+
const { bin } = manifest;
|
|
713
|
+
const relativeBinPath = typeof bin === "string" ? bin : bin && typeof bin === "object" ? bin[binName] : void 0;
|
|
714
|
+
if (typeof relativeBinPath !== "string" || relativeBinPath.trim() === "") return;
|
|
715
|
+
return path.resolve(path.dirname(packageJsonPath), relativeBinPath);
|
|
716
|
+
}
|
|
717
|
+
async function resolveInstalledAcpPackageBinPath(packageName, binName) {
|
|
718
|
+
try {
|
|
719
|
+
const packageJsonPath = requireFromHere$1.resolve(`${packageName}/package.json`);
|
|
720
|
+
const manifest = await tryReadJson(packageJsonPath);
|
|
721
|
+
if (manifest?.name !== packageName) return;
|
|
722
|
+
const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
|
|
723
|
+
if (!binPath) return;
|
|
724
|
+
await fs$1.access(binPath);
|
|
725
|
+
return binPath;
|
|
726
|
+
} catch {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
706
730
|
function buildCodexAcpWrapperScript(installedBinPath) {
|
|
707
731
|
return buildAdapterWrapperScript({
|
|
708
732
|
displayName: "Codex",
|
|
@@ -776,7 +800,8 @@ if (openClawCodexConfigs.length > 0) {
|
|
|
776
800
|
existingCodexConfig = parsedCodexConfig;
|
|
777
801
|
} catch {
|
|
778
802
|
console.error("[openclaw] CODEX_CONFIG must be a valid JSON object");
|
|
779
|
-
process.
|
|
803
|
+
process.exitCode = 1;
|
|
804
|
+
return;
|
|
780
805
|
}
|
|
781
806
|
}
|
|
782
807
|
for (const openClawCodexConfig of openClawCodexConfigs) {
|
|
@@ -792,7 +817,8 @@ if (openClawCodexConfigs.length > 0) {
|
|
|
792
817
|
existingCodexConfig = mergeCodexConfig(existingCodexConfig, parsedOpenClawCodexConfig);
|
|
793
818
|
} catch {
|
|
794
819
|
console.error("[openclaw] invalid generated Codex ACP startup config");
|
|
795
|
-
process.
|
|
820
|
+
process.exitCode = 1;
|
|
821
|
+
return;
|
|
796
822
|
}
|
|
797
823
|
}
|
|
798
824
|
env.CODEX_CONFIG = JSON.stringify(existingCodexConfig);
|
|
@@ -979,7 +1005,7 @@ async function prepareAcpxCodexAuthConfig(params) {
|
|
|
979
1005
|
...params.pluginConfig,
|
|
980
1006
|
agents: {
|
|
981
1007
|
...params.pluginConfig.agents,
|
|
982
|
-
codex: buildWrapperCommand(wrapperPath, codexLaunch?.args ?? [
|
|
1008
|
+
codex: buildWrapperCommand(wrapperPath, codexLaunch?.args ?? ["--openclaw-run-configured", ...splitCommandParts(configuredCodexCommand ?? [])]),
|
|
983
1009
|
claude: buildClaudeAcpWrapperCommand(claudeWrapperPath, configuredClaudeCommand)
|
|
984
1010
|
}
|
|
985
1011
|
};
|
|
@@ -1452,7 +1478,6 @@ var AcpxRuntimeProbe = class {
|
|
|
1452
1478
|
const runtime = this.params.createRuntime(agent);
|
|
1453
1479
|
try {
|
|
1454
1480
|
const report = await this.params.runWithLease(agent, () => runtime.doctor());
|
|
1455
|
-
this.params.assertRunning();
|
|
1456
1481
|
this.health = {
|
|
1457
1482
|
agent,
|
|
1458
1483
|
ok: report.ok
|
|
@@ -1536,7 +1561,6 @@ function createResetAwareSessionStore(baseStore, params) {
|
|
|
1536
1561
|
const normalized = sessionId.trim();
|
|
1537
1562
|
if (normalized && freshSessionKeys.has(normalized)) return;
|
|
1538
1563
|
const record = await baseStore.load(sessionId);
|
|
1539
|
-
if (scope?.generation.retired || freshSessionKeys.has(scope?.generation.resource ?? normalized)) return;
|
|
1540
1564
|
if (scope && record) captureGenerationRecord(scope.generation, record);
|
|
1541
1565
|
if (!record || !params?.leaseStore || !params.gatewayInstanceId) return record;
|
|
1542
1566
|
const sessionName = readSessionRecordName(record) || normalized;
|
|
@@ -1546,7 +1570,6 @@ function createResetAwareSessionStore(baseStore, params) {
|
|
|
1546
1570
|
rootPid: readRecordAgentPid(record)
|
|
1547
1571
|
});
|
|
1548
1572
|
if (!lease) return record;
|
|
1549
|
-
if (scope?.generation.retired) return;
|
|
1550
1573
|
const leasedRecord = withOpenClawLeaseSessionMetadata(record, lease);
|
|
1551
1574
|
if (scope) captureGenerationRecord(scope.generation, leasedRecord);
|
|
1552
1575
|
return leasedRecord;
|
|
@@ -1575,7 +1598,6 @@ function createResetAwareSessionStore(baseStore, params) {
|
|
|
1575
1598
|
wrapperRoot: params.wrapperRoot
|
|
1576
1599
|
})) {
|
|
1577
1600
|
const existing = await params.leaseStore.load(leaseIdentity.leaseId);
|
|
1578
|
-
if (scope?.generation.retired) return;
|
|
1579
1601
|
if (!existing || existing.gatewayInstanceId === leaseIdentity.gatewayInstanceId && existing.sessionKey === sessionName && existing.wrapperRoot === params.wrapperRoot) {
|
|
1580
1602
|
const adoptingLease = Boolean(launch && !isDeepStrictEqual(splitCommandParts(launch.resolvedCommand), splitCommandParts(launch.leasedCommand)));
|
|
1581
1603
|
const persistedCommand = launch && !adoptingLease ? launch.resolvedCommand : leasedCommand;
|
|
@@ -1952,7 +1974,7 @@ var AcpxRuntime$1 = class {
|
|
|
1952
1974
|
});
|
|
1953
1975
|
}
|
|
1954
1976
|
async runInGeneration(target, scope, run) {
|
|
1955
|
-
const release = this.generationRegistry.retainGenerationOperation(scope.generation,
|
|
1977
|
+
const release = this.generationRegistry.retainGenerationOperation(scope.generation, target.acpxRecordId ?? scope.generation.resource);
|
|
1956
1978
|
try {
|
|
1957
1979
|
return await this.sessionScope.run(resolveBridgeSession(target), () => acpxOperationScope.run(scope, run));
|
|
1958
1980
|
} finally {
|
|
@@ -1969,13 +1991,9 @@ var AcpxRuntime$1 = class {
|
|
|
1969
1991
|
}
|
|
1970
1992
|
async loadOperationSnapshotForHandle(handle, generation, allowRetired = false) {
|
|
1971
1993
|
const resource = generation.resource;
|
|
1972
|
-
if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
|
|
1973
1994
|
const ownedRecord = generation.records.get(handle.acpxRecordId ?? resource);
|
|
1974
|
-
|
|
1975
|
-
let record = allowRetired ? generation.retired ? ownedRecord : await this.sessionStore.loadForClose(handle.acpxRecordId ?? resource) : await acpxOperationScope.run({ generation }, () => this.sessionStore.load(handle.acpxRecordId ?? resource));
|
|
1976
|
-
if (allowRetired && generation.retired && ownedRecord) record = ownedRecord;
|
|
1995
|
+
const record = allowRetired ? generation.retired ? ownedRecord : await this.sessionStore.loadForClose(handle.acpxRecordId ?? resource) : await acpxOperationScope.run({ generation }, () => this.sessionStore.load(handle.acpxRecordId ?? resource));
|
|
1977
1996
|
if (allowRetired && record) captureGenerationRecord(generation, record);
|
|
1978
|
-
if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
|
|
1979
1997
|
if (record && (handle.acpxRecordId && handle.acpxRecordId !== record.acpxRecordId || handle.backendSessionId && record.acpSessionId && handle.backendSessionId !== record.acpSessionId)) throw new AcpRuntimeError("ACP_TURN_FAILED", "ACP handle no longer owns this runtime record.");
|
|
1980
1998
|
const command = readRecordAgentCommand(record) ?? resolveAgentCommand({
|
|
1981
1999
|
agentName: readAgentFromHandle(handle),
|
|
@@ -1987,7 +2005,6 @@ var AcpxRuntime$1 = class {
|
|
|
1987
2005
|
if (identity.gatewayInstanceId !== this.gatewayInstanceId) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another gateway`);
|
|
1988
2006
|
if (lease && (lease.gatewayInstanceId !== identity.gatewayInstanceId || lease.sessionKey !== resolveAcpxSessionResource(handle) || lease.wrapperRoot !== this.wrapperRoot)) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another session`);
|
|
1989
2007
|
}
|
|
1990
|
-
if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
|
|
1991
2008
|
return {
|
|
1992
2009
|
record,
|
|
1993
2010
|
command,
|
|
@@ -1999,32 +2016,38 @@ var AcpxRuntime$1 = class {
|
|
|
1999
2016
|
return await this.runInGeneration(handle, { generation }, async () => {
|
|
2000
2017
|
const snapshot = await this.loadOperationSnapshotForHandle(handle, generation);
|
|
2001
2018
|
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2002
|
-
return await
|
|
2003
|
-
generation,
|
|
2004
|
-
recordId: snapshot.record?.acpxRecordId
|
|
2005
|
-
}, () => run(snapshot));
|
|
2019
|
+
return await run(snapshot);
|
|
2006
2020
|
});
|
|
2007
2021
|
}
|
|
2008
2022
|
resolveDelegateForOperationSnapshot(handle, snapshot) {
|
|
2009
2023
|
return this.generationRegistry.resolveDelegate(snapshot.generation, snapshot.generation.nativeTools ?? resolveBridgeSession(handle)?.native === true);
|
|
2010
2024
|
}
|
|
2011
|
-
async
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
if (
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
if (!
|
|
2027
|
-
|
|
2025
|
+
async readReusableSession(input, command) {
|
|
2026
|
+
const { persistedHandle, sessionKey, resumeSessionId } = input;
|
|
2027
|
+
const oneShot = input.mode === "oneshot";
|
|
2028
|
+
const decoded = persistedHandle && decodeAcpxRuntimeHandleState(persistedHandle.runtimeSessionName);
|
|
2029
|
+
if (!command || oneShot && (resumeSessionId || persistedHandle?.backend !== "acpx" || !persistedHandle.acpxRecordId || decoded?.mode !== "oneshot" || decoded.name !== sessionKey || decoded.acpxRecordId !== persistedHandle.acpxRecordId || decoded.agent !== normalizeOptionalLowercaseString(input.agent))) return;
|
|
2030
|
+
const recordId = oneShot ? persistedHandle?.acpxRecordId : sessionKey;
|
|
2031
|
+
if (!recordId) return;
|
|
2032
|
+
const record = await this.sessionStore.load(recordId);
|
|
2033
|
+
if (!record || readRecordResetOnNextEnsure(record)) return;
|
|
2034
|
+
if (oneShot && (record.acpxRecordId !== recordId || readSessionRecordName(record) !== sessionKey || record.acpSessionId !== (persistedHandle?.backendSessionId ?? decoded?.backendSessionId) || !record.agentCapabilities?.loadSession && !record.agentCapabilities?.sessionCapabilities?.resume || record.lastPromptAt !== void 0 || record.messages.some((message) => typeof message !== "string" && "User" in message))) return;
|
|
2035
|
+
const recordCwd = readRecordCwd(record);
|
|
2036
|
+
const recordCommand = readRecordAgentCommand(record);
|
|
2037
|
+
if (!recordCwd || !recordCommand || path.resolve(recordCwd) !== path.resolve(input.cwd?.trim() || this.cwd) || !oneShot && resumeSessionId && record.acpSessionId !== resumeSessionId) return;
|
|
2038
|
+
const lease = readAcpxProcessLeaseIdentity(recordCommand);
|
|
2039
|
+
const ownLease = !lease || lease.gatewayInstanceId === this.gatewayInstanceId;
|
|
2040
|
+
if (!oneShot && !ownLease) return;
|
|
2041
|
+
const expectedCommand = lease ? withAcpxLeaseArgs({
|
|
2042
|
+
command,
|
|
2043
|
+
leaseId: lease.leaseId,
|
|
2044
|
+
gatewayInstanceId: lease.gatewayInstanceId
|
|
2045
|
+
}) : command;
|
|
2046
|
+
if (!isDeepStrictEqual(splitCommandParts(recordCommand), splitCommandParts(expectedCommand))) return;
|
|
2047
|
+
return {
|
|
2048
|
+
reusableCommand: ownLease ? recordCommand : void 0,
|
|
2049
|
+
resumeSessionId: oneShot ? record.acpSessionId : void 0
|
|
2050
|
+
};
|
|
2028
2051
|
}
|
|
2029
2052
|
async runWithLaunchLease(params) {
|
|
2030
2053
|
if (!params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
|
|
@@ -2111,7 +2134,6 @@ var AcpxRuntime$1 = class {
|
|
|
2111
2134
|
sessionKey: resource,
|
|
2112
2135
|
agent: input.agent
|
|
2113
2136
|
});
|
|
2114
|
-
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2115
2137
|
generation.admitted ||= Boolean(handle);
|
|
2116
2138
|
return handle ? {
|
|
2117
2139
|
...handle,
|
|
@@ -2183,26 +2205,28 @@ var AcpxRuntime$1 = class {
|
|
|
2183
2205
|
model: claudeModelOverride
|
|
2184
2206
|
} : effectiveInput;
|
|
2185
2207
|
const stableLaunchCommand = codexModelOverride && command ? appendCodexAcpConfigOverrides(command, codexModelOverride) : command;
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
});
|
|
2208
|
+
const restoration = await this.readReusableSession(input, stableLaunchCommand);
|
|
2209
|
+
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2210
|
+
const restoredInput = restoration?.resumeSessionId ? {
|
|
2211
|
+
...ensureInput,
|
|
2212
|
+
resumeSessionId: restoration.resumeSessionId
|
|
2213
|
+
} : ensureInput;
|
|
2193
2214
|
return {
|
|
2194
2215
|
...await this.runWithLaunchLease({
|
|
2195
|
-
agent:
|
|
2216
|
+
agent: restoredInput.agent,
|
|
2196
2217
|
sessionKey: ensureInput.sessionKey,
|
|
2197
2218
|
command: stableLaunchCommand,
|
|
2198
|
-
reusableCommand,
|
|
2219
|
+
reusableCommand: restoration?.reusableCommand,
|
|
2199
2220
|
run: () => this.withCodexWrapperDiagnostics({
|
|
2200
2221
|
command: stableLaunchCommand,
|
|
2201
2222
|
fallbackCode: "ACP_SESSION_INIT_FAILED",
|
|
2202
|
-
run: () =>
|
|
2223
|
+
run: () => {
|
|
2203
2224
|
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2204
|
-
return delegate.ensureSession(request)
|
|
2205
|
-
|
|
2225
|
+
return codexModelOverride ? delegate.ensureSession(withAcpxSessionOptions(restoredInput)) : ensureSessionWithModelRef((request) => {
|
|
2226
|
+
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2227
|
+
return delegate.ensureSession(request);
|
|
2228
|
+
}, restoredInput);
|
|
2229
|
+
}
|
|
2206
2230
|
})
|
|
2207
2231
|
}),
|
|
2208
2232
|
...logicalTarget,
|
|
@@ -2242,7 +2266,6 @@ var AcpxRuntime$1 = class {
|
|
|
2242
2266
|
});
|
|
2243
2267
|
const turnPromise = this.runWithOperationSnapshot(input.handle, (snapshot) => {
|
|
2244
2268
|
const { command, generation } = snapshot;
|
|
2245
|
-
this.generationRegistry.assertCurrentGeneration(generation);
|
|
2246
2269
|
const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
|
|
2247
2270
|
return this.sessionScope.run(resolveBridgeSession(input.handle), () => acpxOperationScope.run({ generation }, () => withTurnDiagnostics(command, async () => {
|
|
2248
2271
|
const release = this.generationRegistry.retainGenerationOperation(generation, snapshot.record?.acpxRecordId ?? input.handle.acpxRecordId ?? generation.resource);
|
|
@@ -2504,9 +2527,7 @@ async function reapOpenAcpxProcessLeases(params) {
|
|
|
2504
2527
|
for (const lease of leases) {
|
|
2505
2528
|
const pending = lease.rootPid <= 0;
|
|
2506
2529
|
if (pending) legacyWrapperRoots.add(lease.wrapperRoot);
|
|
2507
|
-
assertCurrent?.();
|
|
2508
2530
|
await params.leaseStore.markState(lease.leaseId, "closing");
|
|
2509
|
-
assertCurrent?.();
|
|
2510
2531
|
const result = pending ? await cleanupOpenClawOwnedAcpxPendingLease({
|
|
2511
2532
|
leaseId: lease.leaseId,
|
|
2512
2533
|
gatewayInstanceId: lease.gatewayInstanceId,
|
|
@@ -2523,11 +2544,9 @@ async function reapOpenAcpxProcessLeases(params) {
|
|
|
2523
2544
|
inspectedPids.push(...result.inspectedPids);
|
|
2524
2545
|
terminatedPids.push(...result.terminatedPids);
|
|
2525
2546
|
const retryableEvidenceFailure = result.skippedReason === "process-list-unavailable" || result.skippedReason === "unsupported-platform" || pending && (result.skippedReason === "ambiguous-root" || result.skippedReason === "unverified-root" || lease.sessionKey === "openclaw:acpx:probe" && result.skippedReason === "missing-root");
|
|
2526
|
-
assertCurrent?.();
|
|
2527
2547
|
await params.leaseStore.markState(lease.leaseId, retryableEvidenceFailure ? "open" : result.terminatedPids.length > 0 ? "closed" : "lost");
|
|
2528
2548
|
}
|
|
2529
2549
|
for (const wrapperRoot of legacyWrapperRoots) {
|
|
2530
|
-
assertCurrent?.();
|
|
2531
2550
|
const legacyResult = await reapStaleOpenClawOwnedAcpxOrphans({
|
|
2532
2551
|
wrapperRoot,
|
|
2533
2552
|
assertCurrent
|
|
@@ -2556,11 +2575,9 @@ function createAcpxRuntimeService(params) {
|
|
|
2556
2575
|
assertCurrent();
|
|
2557
2576
|
recoveryPromise ??= measureAcpxStartup(ctx, "process-leases.reap", async () => {
|
|
2558
2577
|
const result = await recover(assertCurrent);
|
|
2559
|
-
assertCurrent();
|
|
2560
2578
|
if (result.terminatedPids.length > 0) ctx.logger.info(`reaped ${result.terminatedPids.length} stale OpenClaw-owned ACPX processes`);
|
|
2561
2579
|
});
|
|
2562
2580
|
await recoveryPromise;
|
|
2563
|
-
assertCurrent();
|
|
2564
2581
|
};
|
|
2565
2582
|
return {
|
|
2566
2583
|
id: "acpx-runtime",
|
|
@@ -2626,14 +2643,11 @@ function createAcpxRuntimeService(params) {
|
|
|
2626
2643
|
ctx.logger.info(`embedded acpx runtime backend registered (cwd: ${pluginConfig.cwd})`);
|
|
2627
2644
|
});
|
|
2628
2645
|
if (!shouldProbeRuntime) return;
|
|
2629
|
-
lifecycleRevision += 1;
|
|
2630
|
-
const currentRevision = lifecycleRevision;
|
|
2631
2646
|
try {
|
|
2632
2647
|
const timeoutSeconds = pluginConfig.timeoutSeconds ?? 120;
|
|
2633
2648
|
const doctorReport = await measureAcpxStartup(ctx, "probe.availability", () => raceWithTimeout(startedRuntime.doctor(), resolveAcpxTimerTimeoutMs(timeoutSeconds) ?? 1, () => {
|
|
2634
2649
|
throw new Error(`embedded acpx runtime backend startup check timed out after ${timeoutSeconds}s`);
|
|
2635
2650
|
}, { ref: false }));
|
|
2636
|
-
if (currentRevision !== lifecycleRevision) return;
|
|
2637
2651
|
if (doctorReport.ok) {
|
|
2638
2652
|
ctx.startupTrace?.detail?.("probe.result", [["healthyCount", 1]]);
|
|
2639
2653
|
ctx.logger.info("embedded acpx runtime backend ready");
|
|
@@ -2642,7 +2656,6 @@ function createAcpxRuntimeService(params) {
|
|
|
2642
2656
|
ctx.startupTrace?.detail?.("probe.result", [["healthyCount", 0]]);
|
|
2643
2657
|
ctx.logger.warn(`embedded acpx runtime backend check failed: ${formatDoctorFailureMessage(doctorReport)}`);
|
|
2644
2658
|
} catch (err) {
|
|
2645
|
-
if (currentRevision !== lifecycleRevision) return;
|
|
2646
2659
|
ctx.startupTrace?.detail?.("probe.result", [["healthyCount", 0]]);
|
|
2647
2660
|
ctx.logger.warn(`embedded acpx runtime setup failed: ${formatErrorMessage(err)}`);
|
|
2648
2661
|
}
|