@openclaw/acpx 2026.9.5 → 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,1171 +0,0 @@
1
- import { _ as splitCommandParts, a as hashAcpxProcessCommand, g as renderAgentCommand, l as readAcpxProcessLeaseIdentity, t as ACPX_PROBE_LEASE_SESSION_KEY, u as withAcpxLeaseArgs } from "./process-lease-B83BGiLj.mjs";
2
- import { AcpRuntimeError } from "../runtime-api.js";
3
- import { t as resolveAcpxSessionResource } from "./session-resource-Dzl0U7kK.mjs";
4
- import { a as CODEX_ACP_PACKAGE, i as isOpenClawLeaseAwareAcpxProcessCommand, n as cleanupOpenClawOwnedAcpxPendingLease, o as OPENCLAW_CODEX_CONFIG_ARG, r as cleanupOpenClawOwnedAcpxProcessTree } from "./service-DZYyo6-7.mjs";
5
- import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
6
- import path, { resolve } from "node:path";
7
- import fs from "node:fs/promises";
8
- import { randomUUID } from "node:crypto";
9
- import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
10
- import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
11
- import { AsyncLocalStorage } from "node:async_hooks";
12
- import { isDeepStrictEqual } from "node:util";
13
- import { ACPX_BACKEND_ID, AcpxRuntime as AcpxRuntime$1, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, decodeAcpxRuntimeHandleState as decodeAcpxRuntimeHandleState$1, encodeAcpxRuntimeHandleState, isRequestedModelUnsupportedError } from "acpx/runtime";
14
- import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
15
- import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
16
- //#region extensions/acpx/src/runtime-generations.ts
17
- var AcpxGenerationRegistry = class {
18
- constructor(sessionStore, delegate, createDelegate) {
19
- this.sessionStore = sessionStore;
20
- this.delegate = delegate;
21
- this.createDelegate = createDelegate;
22
- this.generations = /* @__PURE__ */ new Map();
23
- this.isolatedSessionResources = /* @__PURE__ */ new Set();
24
- this.resetDelegates = /* @__PURE__ */ new Set();
25
- this.retiringDelegates = /* @__PURE__ */ new WeakSet();
26
- this.nextGenerationId = 0;
27
- this.generationOwner = Symbol("acpx-runtime-owner");
28
- this.stopping = false;
29
- }
30
- get isStopping() {
31
- return this.stopping;
32
- }
33
- assertRunning() {
34
- if (this.stopping) throw new AcpRuntimeError("ACP_BACKEND_UNAVAILABLE", "ACP runtime is shut down.");
35
- }
36
- fromCaptured(resource, captured) {
37
- return captured?.owner === this.generationOwner ? captured : this.currentGeneration(resource);
38
- }
39
- prepareFresh(resource) {
40
- const generation = this.generations.get(resource);
41
- if (generation) this.retireGeneration(generation);
42
- else this.sessionStore.markFresh(resource);
43
- }
44
- resolveDelegate(generation) {
45
- this.assertRunning();
46
- if (!generation.delegate) {
47
- generation.delegate = generation.afterReset ? this.createDelegate() : this.delegate;
48
- if (generation.delegate !== this.delegate) this.resetDelegates.add(generation.delegate);
49
- }
50
- return generation.delegate;
51
- }
52
- currentGeneration(resource) {
53
- if (this.stopping) throw new AcpRuntimeError("ACP_BACKEND_UNAVAILABLE", "ACP runtime is shut down.");
54
- let generation = this.generations.get(resource);
55
- if (!generation) {
56
- const fresh = this.sessionStore.isFresh(resource);
57
- const afterReset = fresh || this.isolatedSessionResources.has(resource);
58
- if (afterReset) this.isolatedSessionResources.add(resource);
59
- generation = {
60
- id: ++this.nextGenerationId,
61
- owner: this.generationOwner,
62
- resource,
63
- ensureQueue: new KeyedAsyncQueue(),
64
- retired: false,
65
- activeOperations: 0,
66
- activeRecordOperations: /* @__PURE__ */ new Map(),
67
- closedRecordIds: /* @__PURE__ */ new Set(),
68
- records: /* @__PURE__ */ new Map(),
69
- closeCompleted: false,
70
- afterReset,
71
- awaitPriorWrites: fresh
72
- };
73
- this.generations.set(resource, generation);
74
- }
75
- return generation;
76
- }
77
- retireGeneration(generation) {
78
- generation.retired = true;
79
- if (this.generations.get(generation.resource) === generation) {
80
- this.generations.delete(generation.resource);
81
- this.sessionStore.markFresh(generation.resource);
82
- }
83
- this.releaseRetiredDelegate(generation);
84
- }
85
- releaseRetiredDelegate(generation) {
86
- const delegate = generation.delegate;
87
- if (!generation.retired || generation.activeOperations !== 0 || !delegate || delegate === this.delegate || this.retiringDelegates.has(delegate)) return;
88
- this.retiringDelegates.add(delegate);
89
- delegate.shutdown().then(() => this.resetDelegates.delete(delegate), () => {});
90
- }
91
- retainGenerationOperation(generation, recordId) {
92
- generation.activeOperations += 1;
93
- generation.activeRecordOperations.set(recordId, (generation.activeRecordOperations.get(recordId) ?? 0) + 1);
94
- return () => {
95
- const remaining = (generation.activeRecordOperations.get(recordId) ?? 1) - 1;
96
- if (remaining === 0) {
97
- generation.activeRecordOperations.delete(recordId);
98
- generation.closedRecordIds.delete(recordId);
99
- } else generation.activeRecordOperations.set(recordId, remaining);
100
- generation.activeOperations -= 1;
101
- if (!generation.retired && generation.closeCompleted && generation.activeOperations === 0 && generation.records.size === 0 && this.generations.get(generation.resource) === generation) {
102
- generation.retired = true;
103
- this.generations.delete(generation.resource);
104
- }
105
- this.releaseRetiredDelegate(generation);
106
- };
107
- }
108
- assertCurrentGeneration(generation) {
109
- if (this.stopping || generation.retired) throw new AcpRuntimeError("ACP_TURN_FAILED", "ACP runtime operation was superseded by reset.");
110
- }
111
- async shutdown() {
112
- this.stopping = true;
113
- const errors = (await Promise.allSettled([this.delegate, ...this.resetDelegates].map((delegate) => delegate.shutdown()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
114
- if (errors.length) throw new AggregateError(errors, "ACP runtime shutdown failed.");
115
- this.resetDelegates.clear();
116
- this.generations.clear();
117
- this.isolatedSessionResources.clear();
118
- }
119
- };
120
- //#endregion
121
- //#region extensions/acpx/src/runtime-session-store.ts
122
- /** Generation-bound persistence and process-lease metadata for ACPX resets. */
123
- function withOpenClawLeaseSessionMetadata(record, lease) {
124
- return {
125
- ...record,
126
- openclawLeaseId: lease.leaseId,
127
- openclawGatewayInstanceId: lease.gatewayInstanceId
128
- };
129
- }
130
- function captureGenerationRecord(generation, record) {
131
- if (record.closed || generation.closedRecordIds.has(record.acpxRecordId)) generation.records.delete(record.acpxRecordId);
132
- else generation.records.set(record.acpxRecordId, record);
133
- }
134
- const acpxGenerationKey = Symbol("openclaw.acpxGeneration");
135
- const acpxOperationScope = new AsyncLocalStorage();
136
- function readSessionRecordName(record) {
137
- if (typeof record !== "object" || record === null) return "";
138
- const { name } = record;
139
- return typeof name === "string" ? name.trim() : "";
140
- }
141
- function readRecordAgentCommand(record) {
142
- return record?.agentArgv ?? record?.agentCommand;
143
- }
144
- function readRecordCwd(record) {
145
- if (typeof record !== "object" || record === null) return;
146
- const { cwd } = record;
147
- return typeof cwd === "string" ? cwd.trim() || void 0 : void 0;
148
- }
149
- function readRecordResetOnNextEnsure(record) {
150
- if (typeof record !== "object" || record === null) return false;
151
- const { acpx } = record;
152
- if (typeof acpx !== "object" || acpx === null) return false;
153
- return acpx.reset_on_next_ensure === true;
154
- }
155
- function readRecordAgentPid(record) {
156
- if (typeof record !== "object" || record === null) return;
157
- const { pid, processId } = record;
158
- const rawPid = pid ?? processId;
159
- const numericPid = typeof rawPid === "number" ? rawPid : typeof rawPid === "string" ? parseStrictPositiveInteger(rawPid) : void 0;
160
- return numericPid && Number.isInteger(numericPid) && numericPid > 0 ? numericPid : void 0;
161
- }
162
- function readOpenClawLeaseIdFromRecord(record) {
163
- if (typeof record !== "object" || record === null) return;
164
- const { openclawLeaseId } = record;
165
- return typeof openclawLeaseId === "string" ? openclawLeaseId.trim() || void 0 : void 0;
166
- }
167
- function readOpenClawGatewayInstanceIdFromRecord(record) {
168
- if (typeof record !== "object" || record === null) return;
169
- const { openclawGatewayInstanceId } = record;
170
- return typeof openclawGatewayInstanceId === "string" ? openclawGatewayInstanceId.trim() || void 0 : void 0;
171
- }
172
- function extractGeneratedWrapperPath(command) {
173
- return splitCommandParts(command ?? "").find((part) => (part.split(/[\\/]/).pop() ?? "") === "codex-acp-wrapper.mjs" || (part.split(/[\\/]/).pop() ?? "") === "claude-agent-acp-wrapper.mjs") ?? "";
174
- }
175
- function selectCurrentSessionLease(params) {
176
- const sessionKeys = new Set(normalizeStringEntries(params.sessionKeys));
177
- const candidates = params.leases.filter((lease) => sessionKeys.has(lease.sessionKey));
178
- if (params.rootPid) return candidates.find((lease) => lease.rootPid === params.rootPid);
179
- let selected;
180
- for (const lease of candidates) if (!selected || lease.startedAt > selected.startedAt) selected = lease;
181
- return selected;
182
- }
183
- function createResetAwareSessionStore(baseStore, params) {
184
- const freshSessionKeys = /* @__PURE__ */ new Set();
185
- const stateQueue = new KeyedAsyncQueue();
186
- const pendingWrites = /* @__PURE__ */ new Map();
187
- return {
188
- async load(sessionId) {
189
- const scope = acpxOperationScope.getStore();
190
- if (scope?.closeRecord && (sessionId === scope.generation.resource || sessionId === scope.closeRecord.acpxRecordId)) return scope.closeRecord;
191
- const resource = scope?.generation.resource ?? sessionId.trim();
192
- const pending = pendingWrites.get(sessionId.trim());
193
- if (pending && (scope?.generation.awaitPriorWrites || freshSessionKeys.has(resource))) await Promise.allSettled(pending);
194
- const load = async () => {
195
- if (scope?.generation.retired) return;
196
- const normalized = sessionId.trim();
197
- if (normalized && freshSessionKeys.has(normalized)) return;
198
- const record = await baseStore.load(sessionId);
199
- if (scope?.generation.retired || freshSessionKeys.has(scope?.generation.resource ?? normalized)) return;
200
- if (scope && record) captureGenerationRecord(scope.generation, record);
201
- if (!record || !params?.leaseStore || !params.gatewayInstanceId) return record;
202
- const sessionName = readSessionRecordName(record) || normalized;
203
- const lease = selectCurrentSessionLease({
204
- leases: await params.leaseStore.listOpen(params.gatewayInstanceId),
205
- sessionKeys: [sessionName, normalized],
206
- rootPid: readRecordAgentPid(record)
207
- });
208
- if (!lease) return record;
209
- if (scope?.generation.retired) return;
210
- const leasedRecord = withOpenClawLeaseSessionMetadata(record, lease);
211
- if (scope) captureGenerationRecord(scope.generation, leasedRecord);
212
- return leasedRecord;
213
- };
214
- return await load();
215
- },
216
- async save(record) {
217
- const scope = acpxOperationScope.getStore();
218
- if (scope) captureGenerationRecord(scope.generation, record);
219
- const resource = record.acpxRecordId;
220
- const retiredCloseRecord = scope?.generation.retired && record.closed && record.acpx?.reset_on_next_ensure === true ? scope.closeRecord : void 0;
221
- const writeRecord = async () => {
222
- if (scope?.generation.retired) {
223
- if (!retiredCloseRecord) return;
224
- const persisted = await baseStore.load(record.acpxRecordId);
225
- if (!persisted || persisted.acpxRecordId !== retiredCloseRecord.acpxRecordId || persisted.acpSessionId !== retiredCloseRecord.acpSessionId || persisted.createdAt !== retiredCloseRecord.createdAt) return;
226
- await baseStore.save(record);
227
- return;
228
- }
229
- let recordToSave = record;
230
- const launch = params?.launchScope?.getStore();
231
- const sessionName = readSessionRecordName(record);
232
- const agentCommand = readRecordAgentCommand(record);
233
- const leasedCommand = launch?.leasedCommand ?? agentCommand;
234
- const leaseIdentity = launch ?? readAcpxProcessLeaseIdentity(leasedCommand);
235
- if (params?.leaseStore && params.gatewayInstanceId && params.wrapperRoot && (!launch || sessionName === launch.sessionKey) && leasedCommand && leaseIdentity?.gatewayInstanceId === params.gatewayInstanceId && isOpenClawLeaseAwareAcpxProcessCommand({
236
- command: leasedCommand,
237
- wrapperRoot: params.wrapperRoot
238
- })) {
239
- const existing = await params.leaseStore.load(leaseIdentity.leaseId);
240
- if (scope?.generation.retired) return;
241
- if (!existing || existing.gatewayInstanceId === leaseIdentity.gatewayInstanceId && existing.sessionKey === sessionName && existing.wrapperRoot === params.wrapperRoot) {
242
- const adoptingLease = Boolean(launch && !isDeepStrictEqual(splitCommandParts(launch.resolvedCommand), splitCommandParts(launch.leasedCommand)));
243
- const persistedCommand = launch && !adoptingLease ? launch.resolvedCommand : leasedCommand;
244
- recordToSave = withOpenClawLeaseSessionMetadata({
245
- ...adoptingLease ? {
246
- ...record,
247
- pid: void 0,
248
- processId: void 0,
249
- agentStartedAt: void 0
250
- } : record,
251
- agentCommand: renderAgentCommand(persistedCommand),
252
- agentArgv: Array.isArray(persistedCommand) ? persistedCommand : void 0
253
- }, leaseIdentity);
254
- }
255
- }
256
- if (scope?.generation.retired) return;
257
- await baseStore.save(recordToSave);
258
- if (scope && !scope.generation.retired) scope.generation.awaitPriorWrites = false;
259
- if (sessionName && !scope?.generation.retired) freshSessionKeys.delete(sessionName);
260
- };
261
- const writes = pendingWrites.get(resource) ?? /* @__PURE__ */ new Set();
262
- const previous = [...writes];
263
- const write = scope?.generation.awaitPriorWrites || retiredCloseRecord ? Promise.allSettled(previous).then(() => stateQueue.enqueue(resource, writeRecord)) : writeRecord();
264
- writes.add(write);
265
- pendingWrites.set(resource, writes);
266
- try {
267
- await write;
268
- } finally {
269
- writes.delete(write);
270
- if (writes.size === 0 && pendingWrites.get(resource) === writes) pendingWrites.delete(resource);
271
- }
272
- },
273
- loadForClose: (sessionKey) => baseStore.load(sessionKey),
274
- isFresh: (sessionKey) => freshSessionKeys.has(sessionKey),
275
- markFresh(sessionKey) {
276
- const normalized = sessionKey.trim();
277
- if (normalized) freshSessionKeys.add(normalized);
278
- }
279
- };
280
- }
281
- //#endregion
282
- //#region extensions/acpx/src/runtime-process-cleanup.ts
283
- /** Capture OpenClaw wrapper cleanup ownership before a backend close can yield. */
284
- async function prepareAcpxProcessCleanup(params) {
285
- const { leaseStore, gatewayInstanceId, wrapperRoot, deps } = params;
286
- const rootPid = readRecordAgentPid(params.record);
287
- const rootCommand = params.command ? renderAgentCommand(params.command) : void 0;
288
- const identity = readAcpxProcessLeaseIdentity(params.command);
289
- const leaseId = readOpenClawLeaseIdFromRecord(params.record) ?? identity?.leaseId;
290
- const expectedGatewayInstanceId = readOpenClawGatewayInstanceIdFromRecord(params.record) ?? identity?.gatewayInstanceId;
291
- const sessionKeys = [params.sessionKey, readSessionRecordName(params.record)];
292
- const openLeases = rootPid && gatewayInstanceId && leaseStore ? await leaseStore.listOpen(gatewayInstanceId) : [];
293
- const selectedLease = rootPid ? selectCurrentSessionLease({
294
- leases: openLeases,
295
- sessionKeys,
296
- rootPid
297
- }) : void 0;
298
- const loadedLease = leaseId ? await leaseStore?.load(leaseId) : void 0;
299
- const ownedLease = selectedLease ?? (loadedLease && loadedLease.gatewayInstanceId === gatewayInstanceId && (!rootPid || loadedLease.rootPid === rootPid) && sessionKeys.includes(loadedLease.sessionKey) ? loadedLease : void 0);
300
- const lease = ownedLease ? { ...ownedLease } : void 0;
301
- return async () => {
302
- if (lease && lease.gatewayInstanceId === gatewayInstanceId) {
303
- await leaseStore?.markState(lease.leaseId, "closing");
304
- const result = lease.rootPid > 0 ? await cleanupOpenClawOwnedAcpxProcessTree({
305
- rootPid: lease.rootPid,
306
- rootCommand,
307
- expectedLeaseId: lease.leaseId,
308
- expectedGatewayInstanceId: lease.gatewayInstanceId,
309
- wrapperRoot: lease.wrapperRoot,
310
- deps
311
- }) : await cleanupOpenClawOwnedAcpxPendingLease({
312
- leaseId: lease.leaseId,
313
- gatewayInstanceId: lease.gatewayInstanceId,
314
- wrapperRoot: lease.wrapperRoot,
315
- wrapperPath: lease.wrapperPath,
316
- deps
317
- });
318
- await leaseStore?.markState(lease.leaseId, result.skippedReason === "process-list-unavailable" || result.skippedReason === "unsupported-platform" || lease.rootPid <= 0 && (result.skippedReason === "ambiguous-root" || result.skippedReason === "unverified-root") ? "open" : result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
319
- return;
320
- }
321
- if (!rootPid || !rootCommand) return;
322
- await cleanupOpenClawOwnedAcpxProcessTree({
323
- rootPid,
324
- rootCommand,
325
- ...leaseId ? { expectedLeaseId: leaseId } : {},
326
- ...expectedGatewayInstanceId ? { expectedGatewayInstanceId } : {},
327
- wrapperRoot,
328
- deps
329
- });
330
- };
331
- }
332
- //#endregion
333
- //#region extensions/acpx/src/session-owner.ts
334
- function requireAcpxOwnerMigration(sessionKey) {
335
- throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", `ACP session "${sessionKey}" has an unqualified or unverifiable backend locator. Stop the Gateway and run "openclaw doctor --fix" to migrate ownership without losing history, then restart.`, { detailCode: "SESSION_OWNER_MIGRATION_REQUIRED" });
336
- }
337
- function assertAcpxSessionOwnerLocator(target, legacyBareSessionKeys) {
338
- const resource = resolveAcpxSessionResource(target);
339
- const qualified = resource === target.sessionKey.trim().toLowerCase();
340
- const persisted = target.persistedHandle;
341
- if (!qualified && (legacyBareSessionKeys?.has(target.sessionKey.trim().toLowerCase()) || legacyBareSessionKeys?.has(resource) && !persisted)) requireAcpxOwnerMigration(target.sessionKey);
342
- if (persisted) {
343
- const decoded = decodeAcpxRuntimeHandleState$1(persisted.runtimeSessionName);
344
- if (!qualified && !decoded || decoded && (decoded.name !== resource || persisted.acpxRecordId && decoded.acpxRecordId !== persisted.acpxRecordId)) requireAcpxOwnerMigration(target.sessionKey);
345
- }
346
- return resource;
347
- }
348
- /** Preserve physical oneshot record IDs and the upstream-encoded runtime handle. */
349
- function toAcpxResourceInput(input) {
350
- const sessionKey = assertAcpxSessionOwnerLocator({
351
- ...input.handle,
352
- persistedHandle: input.handle
353
- });
354
- return {
355
- ...input,
356
- handle: {
357
- ...input.handle,
358
- sessionKey
359
- }
360
- };
361
- }
362
- //#endregion
363
- //#region extensions/acpx/src/runtime.ts
364
- /**
365
- * OpenClaw ACPX runtime adapter. It wraps the upstream acpx runtime with
366
- * OpenClaw session metadata, lease tracking, model scoping, and cleanup policy.
367
- */
368
- const ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME = "openclaw-plugin-tools";
369
- const ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME = "openclaw-tools";
370
- const OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV = "OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY";
371
- const CODEX_WRAPPER_STDERR_LOG_PREFIX = "codex-acp-wrapper.stderr";
372
- function safeDiagnosticFilePart(value) {
373
- return value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || "unknown";
374
- }
375
- function codexWrapperStderrLogFileName(leaseId) {
376
- return `${CODEX_WRAPPER_STDERR_LOG_PREFIX}.${safeDiagnosticFilePart(leaseId)}.log`;
377
- }
378
- function compactDiagnosticText(value) {
379
- return value.replace(/\s+/g, " ").trim();
380
- }
381
- function isGenericInternalAcpErrorMessage(message) {
382
- return message.trim() === "Internal error";
383
- }
384
- function isGenericInternalAcpError(error) {
385
- return error instanceof Error && isGenericInternalAcpErrorMessage(error.message);
386
- }
387
- async function readCodexWrapperStderrTail(params) {
388
- if (!params.wrapperRoot || !params.leaseId) return "";
389
- try {
390
- const text = await fs.readFile(path.join(params.wrapperRoot, codexWrapperStderrLogFileName(params.leaseId)), "utf8");
391
- return compactDiagnosticText(redactSensitiveText(sliceUtf16Safe(text, -6e3)));
392
- } catch {
393
- return "";
394
- }
395
- }
396
- const OPENCLAW_BRIDGE_EXECUTABLE = "openclaw";
397
- const OPENCLAW_BRIDGE_SUBCOMMAND = "acp";
398
- const CODEX_ACP_AGENT_ID = "codex";
399
- const CODEX_ACP_OPENCLAW_PREFIX = "openai/";
400
- const CLAUDE_ACP_OPENCLAW_PREFIX = /^(?:anthropic|amazon-bedrock)\//i;
401
- const CODEX_ACP_THINKING_ALIASES = /* @__PURE__ */ new Map([
402
- ["off", void 0],
403
- ["minimal", "low"],
404
- ["low", "low"],
405
- ["medium", "medium"],
406
- ["high", "high"],
407
- ["x-high", "xhigh"],
408
- ["x_high", "xhigh"],
409
- ["extra-high", "xhigh"],
410
- ["extra_high", "xhigh"],
411
- ["extra high", "xhigh"],
412
- ["xhigh", "xhigh"]
413
- ]);
414
- function normalizeAgentName(value) {
415
- const normalized = value?.trim().toLowerCase();
416
- return normalized ? normalized : void 0;
417
- }
418
- function readAgentFromSessionKey(sessionKey) {
419
- const normalized = sessionKey?.trim();
420
- if (!normalized) return;
421
- return normalizeAgentName(/^agent:(?<agent>[^:]+):/i.exec(normalized)?.groups?.agent);
422
- }
423
- function readAgentFromHandle(handle) {
424
- return normalizeAgentName(decodeAcpxRuntimeHandleState(handle.runtimeSessionName)?.agent) ?? readAgentFromSessionKey(handle.sessionKey);
425
- }
426
- function basename(value) {
427
- return value.split(/[\\/]/).pop() ?? value;
428
- }
429
- function isEnvAssignment(value) {
430
- return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value);
431
- }
432
- function unwrapEnvCommand(parts) {
433
- const command = parts.at(0);
434
- if (!command || basename(command) !== "env") return parts;
435
- let index = 1;
436
- while (true) {
437
- const part = parts.at(index);
438
- if (!part || !isEnvAssignment(part)) break;
439
- index += 1;
440
- }
441
- return parts.slice(index);
442
- }
443
- function matchesExecutableName(value, executableName) {
444
- const normalized = basename(value).toLowerCase();
445
- return normalized === executableName || normalized === `${executableName}.exe`;
446
- }
447
- function matchesPackageSpec(value, packageName) {
448
- const normalized = value.trim().toLowerCase();
449
- return normalized === packageName || normalized.startsWith(`${packageName}@`);
450
- }
451
- function stripModuleExtension(value) {
452
- return value.replace(/\.[cm]?js$/i, "").toLowerCase();
453
- }
454
- function isAcpCommand(command, params) {
455
- if (!command) return false;
456
- const parts = unwrapEnvCommand(splitCommandParts(command));
457
- if (!parts.length) return false;
458
- if (parts.some((part) => matchesPackageSpec(part, params.packageName))) return true;
459
- const commandName = basename(parts[0] ?? "");
460
- if (matchesExecutableName(commandName, params.executableName)) return true;
461
- if (!matchesExecutableName(commandName, "node")) return false;
462
- const scriptName = stripModuleExtension(basename(parts[1] ?? ""));
463
- return scriptName === params.executableName || scriptName === `${params.executableName}-wrapper`;
464
- }
465
- function isOpenClawBridgeCommand(command) {
466
- if (!command) return false;
467
- const parts = unwrapEnvCommand(splitCommandParts(command));
468
- if (basename(parts[0] ?? "") === OPENCLAW_BRIDGE_EXECUTABLE) return parts[1] === OPENCLAW_BRIDGE_SUBCOMMAND;
469
- if (basename(parts[0] ?? "") !== "node") return false;
470
- const scriptName = basename(parts[1] ?? "");
471
- return /^openclaw(?:\.[cm]?js)?$/i.test(scriptName) && parts[2] === OPENCLAW_BRIDGE_SUBCOMMAND;
472
- }
473
- function isCodexAcpCommand(command) {
474
- return isAcpCommand(command, {
475
- packageName: CODEX_ACP_PACKAGE,
476
- executableName: "codex-acp"
477
- });
478
- }
479
- function isClaudeAcpCommand(command) {
480
- return isAcpCommand(command, {
481
- packageName: "@agentclientprotocol/claude-agent-acp",
482
- executableName: "claude-agent-acp"
483
- });
484
- }
485
- function failUnsupportedCodexAcpModel(rawModel) {
486
- throw new AcpRuntimeError("ACP_INVALID_RUNTIME_OPTION", `Codex ACP model "${rawModel}" is not supported. Use openai/<model> or <model>/<reasoning-effort>.`);
487
- }
488
- const WIRE_TIMEOUT_CONFIG_KEYS = /* @__PURE__ */ new Set(["timeout", "timeout_seconds"]);
489
- function assertSupportedRuntimeSessionMode(mode) {
490
- if (mode === "persistent" || mode === "oneshot") return;
491
- throw new AcpRuntimeError("ACP_INVALID_RUNTIME_OPTION", `Unsupported ACP runtime session mode ${JSON.stringify(mode)}. Expected one of: persistent, oneshot.`);
492
- }
493
- function failUnsupportedCodexAcpThinking(rawThinking) {
494
- throw new AcpRuntimeError("ACP_INVALID_RUNTIME_OPTION", `Codex ACP thinking level "${rawThinking}" is not supported. Use off, minimal, low, medium, high, or xhigh.`);
495
- }
496
- function normalizeCodexAcpReasoningEffort(rawThinking) {
497
- const normalized = rawThinking?.trim().toLowerCase();
498
- if (!normalized) return;
499
- if (!CODEX_ACP_THINKING_ALIASES.has(normalized)) failUnsupportedCodexAcpThinking(rawThinking ?? "");
500
- return CODEX_ACP_THINKING_ALIASES.get(normalized);
501
- }
502
- function isCodexAcpReasoningEffortAlias(value) {
503
- const normalized = value?.trim().toLowerCase();
504
- return Boolean(normalized && CODEX_ACP_THINKING_ALIASES.has(normalized));
505
- }
506
- function classifyCodexAcpModelRequest(rawModel, rawThinking) {
507
- const raw = rawModel?.trim();
508
- const thinkingReasoningEffort = normalizeCodexAcpReasoningEffort(rawThinking);
509
- const thinkingOnlyOverride = thinkingReasoningEffort ? { reasoningEffort: thinkingReasoningEffort } : void 0;
510
- if (!raw) return {
511
- kind: "override",
512
- override: thinkingOnlyOverride ?? {}
513
- };
514
- let value = raw;
515
- let hadOpenAiQualifier = false;
516
- if (value.toLowerCase().startsWith(CODEX_ACP_OPENCLAW_PREFIX)) {
517
- value = value.slice(7);
518
- hadOpenAiQualifier = true;
519
- }
520
- let model = value.trim();
521
- let modelReasoningEffort;
522
- const slashIndex = value.lastIndexOf("/");
523
- if (slashIndex >= 0 && isCodexAcpReasoningEffortAlias(value.slice(slashIndex + 1))) {
524
- modelReasoningEffort = normalizeCodexAcpReasoningEffort(value.slice(slashIndex + 1));
525
- model = value.slice(0, slashIndex).trim();
526
- }
527
- if (hadOpenAiQualifier && (!model || model.includes("/"))) failUnsupportedCodexAcpModel(raw);
528
- if (!model || model.includes("/")) return thinkingOnlyOverride ? {
529
- kind: "unsupported",
530
- thinkingOverride: thinkingOnlyOverride
531
- } : { kind: "unsupported" };
532
- const reasoningEffort = rawThinking?.trim() ? thinkingReasoningEffort : modelReasoningEffort;
533
- return {
534
- kind: "override",
535
- override: {
536
- model,
537
- ...reasoningEffort ? { reasoningEffort } : {}
538
- }
539
- };
540
- }
541
- function withCodexSessionModel(input, override) {
542
- const next = { ...input };
543
- if (override?.model) next.model = override.model;
544
- else delete next.model;
545
- return next;
546
- }
547
- function normalizeClaudeAcpModelOverride(rawModel) {
548
- const raw = rawModel?.trim();
549
- if (!raw) return;
550
- const prefix = raw.match(CLAUDE_ACP_OPENCLAW_PREFIX);
551
- if (!prefix) return raw;
552
- return raw.slice(prefix[0].length).trim() || void 0;
553
- }
554
- function withAcpxSessionOptions(input) {
555
- const existingOptions = input.sessionOptions;
556
- const model = input.model?.trim() || existingOptions?.model;
557
- const sessionOptions = model ? {
558
- ...existingOptions,
559
- model
560
- } : existingOptions;
561
- const { modelExplicit: _modelExplicit, thinkingExplicit: _thinkingExplicit, ...rest } = input;
562
- return {
563
- ...rest,
564
- ...sessionOptions ? { sessionOptions } : {}
565
- };
566
- }
567
- function isAcpModelCapabilityMissingError(error) {
568
- return isRequestedModelUnsupportedError(error) && error.reason === "missing-capability";
569
- }
570
- async function ensureDelegateSessionWithModelFallback(delegate, input) {
571
- try {
572
- return await delegate.ensureSession(withAcpxSessionOptions(input));
573
- } catch (error) {
574
- if (input.modelExplicit || !input.model || !isAcpModelCapabilityMissingError(error)) throw error;
575
- return {
576
- ...await delegate.ensureSession(withAcpxSessionOptions({
577
- ...input,
578
- model: void 0
579
- })),
580
- appliedModel: { kind: "dropped" }
581
- };
582
- }
583
- }
584
- function appendCodexAcpConfigOverrides(command, override) {
585
- const config = {
586
- ...override.model ? { model: override.model } : {},
587
- ...override.reasoningEffort ? { model_reasoning_effort: override.reasoningEffort } : {}
588
- };
589
- if (Object.keys(config).length === 0) return command;
590
- return [
591
- ...splitCommandParts(command),
592
- OPENCLAW_CODEX_CONFIG_ARG,
593
- JSON.stringify(config)
594
- ];
595
- }
596
- function resolveAgentCommand(params) {
597
- const normalizedAgentName = normalizeAgentName(params.agentName);
598
- if (!normalizedAgentName) return;
599
- return splitCommandParts(params.agentRegistry.resolve(normalizedAgentName));
600
- }
601
- function withManagedToolsMcpSessionEnv(params) {
602
- const sessionKey = params.sessionKey.trim();
603
- if (!params.pluginToolsEnabled && !params.openclawToolsEnabled || !sessionKey || !params.mcpServers?.length) return params.mcpServers;
604
- let changed = false;
605
- const nextServers = params.mcpServers.map((server) => {
606
- const isManagedPluginTools = params.pluginToolsEnabled && server.name === ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME;
607
- const isManagedOpenClawTools = params.openclawToolsEnabled && server.name === ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME;
608
- if (!isManagedPluginTools && !isManagedOpenClawTools || !("command" in server)) return server;
609
- changed = true;
610
- const env = [...server.env.filter((entry) => entry.name !== OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV), {
611
- name: OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV,
612
- value: sessionKey
613
- }];
614
- return {
615
- ...server,
616
- env,
617
- args: params.agentId ? [
618
- ...server.args,
619
- "--openclaw-agent-id",
620
- params.agentId
621
- ] : server.args
622
- };
623
- });
624
- return changed ? nextServers : params.mcpServers;
625
- }
626
- /** OpenClaw-managed ACP runtime implementation backed by the upstream acpx runtime. */
627
- var AcpxRuntime = class {
628
- constructor(options, testOptions) {
629
- this.ownerAwareSessions = 1;
630
- this.launchCommandScope = new AsyncLocalStorage();
631
- this.sessionScope = new AsyncLocalStorage();
632
- this.probeQueue = new KeyedAsyncQueue();
633
- this.launchLeaseScope = new AsyncLocalStorage();
634
- this.legacyBareSessionKeys = new Set(options.openclawLegacyBareSessionKeys);
635
- const { openclawProcessCleanup, ...delegateTestOptions } = testOptions ?? {};
636
- this.processCleanupDeps = openclawProcessCleanup;
637
- this.wrapperRoot = options.openclawWrapperRoot;
638
- this.gatewayInstanceId = options.openclawGatewayInstanceId;
639
- this.processLeaseStore = options.openclawProcessLeaseStore;
640
- this.pluginToolsMcpBridgeEnabled = options.pluginToolsMcpBridgeEnabled === true;
641
- this.openclawToolsMcpBridgeEnabled = options.openclawToolsMcpBridgeEnabled === true;
642
- this.managedToolsMcpBridgeEnabled = this.pluginToolsMcpBridgeEnabled || this.openclawToolsMcpBridgeEnabled;
643
- this.cwd = options.cwd;
644
- this.sessionStore = createResetAwareSessionStore(options.sessionStore, {
645
- gatewayInstanceId: this.gatewayInstanceId,
646
- leaseStore: this.processLeaseStore,
647
- launchScope: this.launchLeaseScope,
648
- wrapperRoot: this.wrapperRoot
649
- });
650
- this.agentRegistry = options.agentRegistry;
651
- this.scopedAgentRegistry = {
652
- resolve: (agentName) => {
653
- const launch = this.launchCommandScope.getStore();
654
- return launch && launch.agent === normalizeAgentName(agentName) && launch.command ? launch.command : this.agentRegistry.resolve(agentName);
655
- },
656
- list: () => this.agentRegistry.list()
657
- };
658
- this.createDelegate = () => new AcpxRuntime$1({
659
- ...options,
660
- sessionStore: this.sessionStore,
661
- agentRegistry: this.scopedAgentRegistry,
662
- mcpServers: (context) => {
663
- const servers = typeof options.mcpServers === "function" ? options.mcpServers(context) : options.mcpServers ?? [];
664
- if (isOpenClawBridgeCommand(context.agentArgv ?? context.agentCommand)) return [];
665
- const target = this.sessionScope.getStore();
666
- if (!this.managedToolsMcpBridgeEnabled) return servers;
667
- if (!target) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP tool bridge has no session owner");
668
- return withManagedToolsMcpSessionEnv({
669
- pluginToolsEnabled: this.pluginToolsMcpBridgeEnabled,
670
- openclawToolsEnabled: this.openclawToolsMcpBridgeEnabled,
671
- mcpServers: servers,
672
- ...target
673
- });
674
- },
675
- processLifecycle: {
676
- onBeforeSpawn: async (launch) => {
677
- await options.processLifecycle?.onBeforeSpawn?.(launch);
678
- await this.recordProcessLaunch(launch);
679
- },
680
- onSpawned: async (process) => {
681
- await this.recordProcessLaunch(process);
682
- await options.processLifecycle?.onSpawned?.(process);
683
- },
684
- onSpawnFailed: options.processLifecycle?.onSpawnFailed,
685
- onExit: options.processLifecycle?.onExit
686
- }
687
- }, delegateTestOptions);
688
- this.delegate = this.createDelegate();
689
- this.generationRegistry = new AcpxGenerationRegistry(this.sessionStore, this.delegate, this.createDelegate);
690
- this.probeAgent = normalizeAgentName(options.probeAgent) ?? "codex";
691
- const probeCommand = resolveAgentCommand({
692
- agentName: this.probeAgent,
693
- agentRegistry: this.agentRegistry
694
- });
695
- this.probeCommand = probeCommand;
696
- }
697
- async runInGeneration(target, scope, run) {
698
- const release = this.generationRegistry.retainGenerationOperation(scope.generation, scope.recordId ?? target.acpxRecordId ?? scope.generation.resource);
699
- try {
700
- return await this.sessionScope.run(target, () => acpxOperationScope.run(scope, run));
701
- } finally {
702
- release();
703
- }
704
- }
705
- resolveDelegateForSession(params) {
706
- const generation = acpxOperationScope.getStore()?.generation ?? this.generationRegistry.currentGeneration(resolveAcpxSessionResource(params));
707
- return this.generationRegistry.resolveDelegate(generation);
708
- }
709
- generationForHandle(handle) {
710
- const resource = assertAcpxSessionOwnerLocator({
711
- ...handle,
712
- persistedHandle: handle
713
- }, this.legacyBareSessionKeys);
714
- const capturedGeneration = handle[acpxGenerationKey];
715
- return this.generationRegistry.fromCaptured(resource, capturedGeneration);
716
- }
717
- async loadOperationSnapshotForHandle(handle, allowRetired = false, generation = this.generationForHandle(handle)) {
718
- const resource = generation.resource;
719
- if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
720
- const ownedRecord = generation.records.get(handle.acpxRecordId ?? resource);
721
- if (ownedRecord && (handle.acpxRecordId && ownedRecord.acpxRecordId !== handle.acpxRecordId || handle.backendSessionId && ownedRecord.acpSessionId && ownedRecord.acpSessionId !== handle.backendSessionId)) throw new AcpRuntimeError("ACP_TURN_FAILED", "ACP handle no longer owns this runtime generation.");
722
- let record = allowRetired ? generation.retired ? ownedRecord : await this.sessionStore.loadForClose(handle.acpxRecordId ?? resource) : await acpxOperationScope.run({ generation }, () => this.sessionStore.load(handle.acpxRecordId ?? resource));
723
- if (allowRetired && generation.retired && ownedRecord) record = ownedRecord;
724
- if (allowRetired && record) captureGenerationRecord(generation, record);
725
- if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
726
- 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.");
727
- const command = readRecordAgentCommand(record) ?? resolveAgentCommand({
728
- agentName: readAgentFromHandle(handle),
729
- agentRegistry: this.agentRegistry
730
- });
731
- const identity = readAcpxProcessLeaseIdentity(command);
732
- if (identity && this.processLeaseStore && this.gatewayInstanceId && this.wrapperRoot) {
733
- const lease = await this.processLeaseStore.load(identity.leaseId);
734
- if (identity.gatewayInstanceId !== this.gatewayInstanceId) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another gateway`);
735
- 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`);
736
- }
737
- if (!allowRetired) this.generationRegistry.assertCurrentGeneration(generation);
738
- return {
739
- record,
740
- command,
741
- generation
742
- };
743
- }
744
- resolveDelegateForOperationSnapshot(handle, snapshot) {
745
- return acpxOperationScope.run({
746
- generation: snapshot.generation,
747
- recordId: snapshot.record?.acpxRecordId
748
- }, () => this.resolveDelegateForSession({
749
- command: snapshot.command,
750
- sessionKey: handle.sessionKey,
751
- agentId: handle.agentId
752
- }));
753
- }
754
- async readReusablePersistentSessionCommand(params) {
755
- if (params.mode !== "persistent" || !params.command) return;
756
- const existing = await this.sessionStore.load(params.sessionKey);
757
- if (!existing || readRecordResetOnNextEnsure(existing)) return;
758
- const recordCwd = readRecordCwd(existing);
759
- if (!recordCwd || resolve(recordCwd) !== resolve(params.cwd?.trim() || this.cwd)) return;
760
- const recordCommand = readRecordAgentCommand(existing);
761
- if (!recordCommand) return;
762
- const leaseIdentity = readAcpxProcessLeaseIdentity(recordCommand);
763
- if (leaseIdentity && leaseIdentity.gatewayInstanceId !== this.gatewayInstanceId) return;
764
- const stableRecordCommand = leaseIdentity ? withAcpxLeaseArgs({
765
- command: params.command,
766
- leaseId: leaseIdentity.leaseId,
767
- gatewayInstanceId: leaseIdentity.gatewayInstanceId
768
- }) : params.command;
769
- if (!isDeepStrictEqual(splitCommandParts(recordCommand), splitCommandParts(stableRecordCommand))) return;
770
- return !params.resumeSessionId || existing.acpSessionId === params.resumeSessionId ? recordCommand : void 0;
771
- }
772
- async runWithLaunchLease(params) {
773
- if (!params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
774
- command: params.command,
775
- wrapperRoot: this.wrapperRoot
776
- })) return await this.launchCommandScope.run({
777
- agent: normalizeAgentName(params.agent) ?? params.agent,
778
- command: params.reusableCommand ?? params.command
779
- }, params.run);
780
- const reusableIdentity = readAcpxProcessLeaseIdentity(params.reusableCommand);
781
- const leaseId = reusableIdentity?.gatewayInstanceId === this.gatewayInstanceId ? reusableIdentity.leaseId : params.finalizeCompletedProbe ? `probe-${hashAcpxProcessCommand(`${this.gatewayInstanceId}\0${extractGeneratedWrapperPath(params.command)}`)}` : randomUUID();
782
- const leasedCommand = withAcpxLeaseArgs({
783
- command: params.command,
784
- leaseId,
785
- gatewayInstanceId: this.gatewayInstanceId
786
- });
787
- const launch = {
788
- leaseId,
789
- gatewayInstanceId: this.gatewayInstanceId,
790
- sessionKey: params.sessionKey,
791
- wrapperRoot: this.wrapperRoot,
792
- resolvedCommand: params.reusableCommand ?? leasedCommand,
793
- leasedCommand
794
- };
795
- const result = await this.launchLeaseScope.run(launch, () => this.launchCommandScope.run({
796
- agent: normalizeAgentName(params.agent) ?? params.agent,
797
- command: launch.resolvedCommand
798
- }, params.run));
799
- if (params.finalizeCompletedProbe) await cleanupOpenClawOwnedAcpxPendingLease({
800
- leaseId,
801
- gatewayInstanceId: launch.gatewayInstanceId,
802
- wrapperRoot: launch.wrapperRoot,
803
- wrapperPath: extractGeneratedWrapperPath(leasedCommand),
804
- deps: this.processCleanupDeps
805
- });
806
- return result;
807
- }
808
- async recordProcessLaunch(process) {
809
- const command = [process.command, ...process.args];
810
- const identity = readAcpxProcessLeaseIdentity(command);
811
- if (!identity || !this.processLeaseStore || !this.wrapperRoot) return;
812
- const sessionKey = process.scope.kind === "runtime-session" ? process.scope.sessionKey : ACPX_PROBE_LEASE_SESSION_KEY;
813
- const existing = await this.processLeaseStore.load(identity.leaseId);
814
- if (identity.gatewayInstanceId !== this.gatewayInstanceId || existing && (existing.gatewayInstanceId !== identity.gatewayInstanceId || existing.sessionKey !== sessionKey || existing.wrapperRoot !== this.wrapperRoot)) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP process lease belongs to another owner");
815
- if (!isOpenClawLeaseAwareAcpxProcessCommand({
816
- command,
817
- wrapperRoot: this.wrapperRoot
818
- })) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP process lease has no owned wrapper");
819
- await this.processLeaseStore.save({
820
- ...identity,
821
- sessionKey,
822
- wrapperRoot: this.wrapperRoot,
823
- wrapperPath: extractGeneratedWrapperPath(command),
824
- rootPid: "pid" in process ? process.pid : 0,
825
- commandHash: hashAcpxProcessCommand(command),
826
- startedAt: "startedAt" in process ? Date.parse(process.startedAt) : Date.now(),
827
- state: "open"
828
- });
829
- }
830
- async withCodexWrapperDiagnostics(params) {
831
- try {
832
- return await params.run();
833
- } catch (error) {
834
- if (!isCodexAcpCommand(params.command) || !isGenericInternalAcpError(error)) throw error;
835
- const stderrTail = params.handle ? await this.readCodexTurnFailureStderr({ handle: params.handle }) : await readCodexWrapperStderrTail({
836
- wrapperRoot: this.wrapperRoot,
837
- leaseId: this.launchLeaseScope.getStore()?.leaseId
838
- });
839
- if (!stderrTail) throw error;
840
- throw new AcpRuntimeError(params.fallbackCode, `Internal error: ${stderrTail}`, { cause: error });
841
- }
842
- }
843
- async readCodexTurnFailureStderr(params) {
844
- const record = await this.sessionStore.load(params.handle.acpxRecordId ?? resolveAcpxSessionResource(params.handle));
845
- return readCodexWrapperStderrTail({
846
- wrapperRoot: this.wrapperRoot,
847
- leaseId: readOpenClawLeaseIdFromRecord(record)
848
- });
849
- }
850
- async shutdown() {
851
- await this.generationRegistry.shutdown();
852
- }
853
- isHealthy() {
854
- return this.delegate.isHealthy();
855
- }
856
- async probeAvailability() {
857
- await this.probeQueue.enqueue(this.probeAgent, () => this.runWithLaunchLease({
858
- agent: this.probeAgent,
859
- sessionKey: ACPX_PROBE_LEASE_SESSION_KEY,
860
- command: this.probeCommand,
861
- finalizeCompletedProbe: true,
862
- run: () => this.delegate.probeAvailability()
863
- }));
864
- }
865
- async doctor() {
866
- return await this.probeQueue.enqueue(this.probeAgent, () => this.runWithLaunchLease({
867
- agent: this.probeAgent,
868
- sessionKey: ACPX_PROBE_LEASE_SESSION_KEY,
869
- command: this.probeCommand,
870
- finalizeCompletedProbe: true,
871
- run: () => this.delegate.doctor()
872
- }));
873
- }
874
- async ensureSession(input) {
875
- const resource = assertAcpxSessionOwnerLocator(input, this.legacyBareSessionKeys);
876
- const generation = this.generationRegistry.currentGeneration(resource);
877
- return await generation.ensureQueue.enqueue(resource + "\0" + generation.id, () => this.runInGeneration(input, { generation }, async () => {
878
- this.generationRegistry.assertCurrentGeneration(generation);
879
- const handle = {
880
- ...await this.ensureSessionUnlocked(input),
881
- [acpxGenerationKey]: generation
882
- };
883
- if (generation.retired && !this.generationRegistry.isStopping) await this.close({
884
- handle,
885
- reason: "superseded-initialization",
886
- discardPersistentState: true
887
- });
888
- this.generationRegistry.assertCurrentGeneration(generation);
889
- return handle;
890
- }));
891
- }
892
- async ensureSessionUnlocked(logicalInput) {
893
- assertSupportedRuntimeSessionMode(logicalInput.mode);
894
- const command = resolveAgentCommand({
895
- agentName: logicalInput.agent,
896
- agentRegistry: this.agentRegistry
897
- });
898
- const delegate = this.resolveDelegateForSession({
899
- command,
900
- sessionKey: logicalInput.sessionKey,
901
- agentId: logicalInput.agentId
902
- });
903
- const logicalTarget = {
904
- sessionKey: logicalInput.sessionKey,
905
- agentId: logicalInput.agentId
906
- };
907
- const input = {
908
- ...logicalInput,
909
- sessionKey: resolveAcpxSessionResource(logicalInput)
910
- };
911
- const isCodexAcp = normalizeAgentName(input.agent) === CODEX_ACP_AGENT_ID && isCodexAcpCommand(command);
912
- const dropInheritedCodexMax = isCodexAcp && input.thinking === "max" && input.thinkingExplicit === false;
913
- const effectiveInput = dropInheritedCodexMax ? { ...input } : input;
914
- if (dropInheritedCodexMax) delete effectiveInput.thinking;
915
- const claudeModelOverride = isClaudeAcpCommand(command) ? normalizeClaudeAcpModelOverride(input.model) : void 0;
916
- const codexClassification = isCodexAcp ? classifyCodexAcpModelRequest(effectiveInput.model, effectiveInput.thinking) : void 0;
917
- if (codexClassification?.kind === "unsupported" && input.modelExplicit) failUnsupportedCodexAcpModel(input.model ?? "");
918
- const classifiedCodexOverride = codexClassification?.kind === "override" ? codexClassification.override : codexClassification?.thinkingOverride;
919
- const codexModelOverride = classifiedCodexOverride && Object.keys(classifiedCodexOverride).length > 0 ? classifiedCodexOverride : void 0;
920
- const requestedModel = effectiveInput.model?.trim();
921
- const appliedModel = isCodexAcp && requestedModel ? codexModelOverride?.model ? {
922
- kind: "applied",
923
- model: requestedModel
924
- } : { kind: "dropped" } : void 0;
925
- const ensureInput = isCodexAcp ? withCodexSessionModel(effectiveInput, codexModelOverride) : claudeModelOverride ? {
926
- ...effectiveInput,
927
- model: claudeModelOverride
928
- } : effectiveInput;
929
- const stableLaunchCommand = codexModelOverride && command ? appendCodexAcpConfigOverrides(command, codexModelOverride) : command;
930
- const reusableCommand = await this.readReusablePersistentSessionCommand({
931
- sessionKey: input.sessionKey,
932
- mode: input.mode,
933
- cwd: input.cwd,
934
- command: stableLaunchCommand,
935
- resumeSessionId: input.resumeSessionId
936
- });
937
- return {
938
- ...await this.runWithLaunchLease({
939
- agent: ensureInput.agent,
940
- sessionKey: ensureInput.sessionKey,
941
- command: stableLaunchCommand,
942
- reusableCommand,
943
- run: () => this.withCodexWrapperDiagnostics({
944
- command: stableLaunchCommand,
945
- fallbackCode: "ACP_SESSION_INIT_FAILED",
946
- run: () => codexModelOverride ? delegate.ensureSession(withAcpxSessionOptions(ensureInput)) : ensureDelegateSessionWithModelFallback(delegate, ensureInput)
947
- })
948
- }),
949
- ...logicalTarget,
950
- ...appliedModel ? { appliedModel } : {},
951
- ...dropInheritedCodexMax ? { appliedThinking: { kind: "dropped" } } : {}
952
- };
953
- }
954
- async *runTurn(input) {
955
- const turn = this.startTurn(input);
956
- turn.result.catch(() => {});
957
- let completed = false;
958
- try {
959
- yield* turn.events;
960
- const result = await turn.result;
961
- completed = true;
962
- yield result.status === "failed" ? {
963
- type: "error",
964
- ...result.error
965
- } : {
966
- type: "done",
967
- ...result.stopReason ? { stopReason: result.stopReason } : {}
968
- };
969
- } finally {
970
- if (!completed) {
971
- await turn.cancel({ reason: "stream-closed" }).catch(() => {});
972
- await turn.closeStream({ reason: "stream-closed" }).catch(() => {});
973
- await turn.result.catch(() => {});
974
- }
975
- }
976
- }
977
- startTurn(input) {
978
- const withTurnDiagnostics = (command, run) => this.withCodexWrapperDiagnostics({
979
- command,
980
- handle: input.handle,
981
- fallbackCode: "ACP_TURN_FAILED",
982
- run
983
- });
984
- const turnPromise = this.loadOperationSnapshotForHandle(input.handle).then((snapshot) => {
985
- const { command, generation } = snapshot;
986
- this.generationRegistry.assertCurrentGeneration(generation);
987
- const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
988
- return this.sessionScope.run(input.handle, () => acpxOperationScope.run({ generation }, () => withTurnDiagnostics(command, async () => {
989
- const release = this.generationRegistry.retainGenerationOperation(generation, snapshot.record?.acpxRecordId ?? input.handle.acpxRecordId ?? generation.resource);
990
- try {
991
- const turn = delegate.startTurn({
992
- ...toAcpxResourceInput(input),
993
- timeoutMs: 0
994
- });
995
- turn.result.then(release, release);
996
- return {
997
- command,
998
- turn
999
- };
1000
- } catch (error) {
1001
- release();
1002
- throw error;
1003
- }
1004
- })));
1005
- });
1006
- return {
1007
- requestId: input.requestId,
1008
- get promptStarted() {
1009
- return turnPromise.then(({ turn }) => turn.promptStarted);
1010
- },
1011
- events: { async *[Symbol.asyncIterator]() {
1012
- const { command, turn } = await turnPromise;
1013
- try {
1014
- yield* turn.events;
1015
- } catch (error) {
1016
- if (!isGenericInternalAcpError(error)) throw error;
1017
- await withTurnDiagnostics(command, () => Promise.reject(error));
1018
- }
1019
- } },
1020
- result: turnPromise.then(({ command, turn }) => withTurnDiagnostics(command, async () => {
1021
- const result = await turn.result;
1022
- if (result.status !== "failed" || !isCodexAcpCommand(command) || !isGenericInternalAcpErrorMessage(result.error.message)) return result;
1023
- const stderrTail = await this.readCodexTurnFailureStderr({ handle: input.handle });
1024
- if (!stderrTail) return result;
1025
- return {
1026
- status: "failed",
1027
- error: {
1028
- ...result.error,
1029
- code: "ACP_TURN_FAILED",
1030
- message: `Internal error: ${stderrTail}`
1031
- }
1032
- };
1033
- })),
1034
- cancel(inputArgs) {
1035
- return turnPromise.then(({ turn }) => turn.cancel(inputArgs));
1036
- },
1037
- closeStream(inputArgs) {
1038
- return turnPromise.then(({ turn }) => turn.closeStream(inputArgs));
1039
- }
1040
- };
1041
- }
1042
- async getCapabilities(input) {
1043
- const capabilities = await this.delegate.getCapabilities(input?.handle ? toAcpxResourceInput({ handle: input.handle }) : input);
1044
- return {
1045
- ...capabilities,
1046
- controls: capabilities.controls.filter((control) => control !== "session/set_model")
1047
- };
1048
- }
1049
- async getStatus(input) {
1050
- const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1051
- return this.runInGeneration(input.handle, {
1052
- generation: snapshot.generation,
1053
- recordId: snapshot.record?.acpxRecordId
1054
- }, () => this.resolveDelegateForOperationSnapshot(input.handle, snapshot).getStatus(toAcpxResourceInput(input)));
1055
- }
1056
- async setMode(input) {
1057
- const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1058
- await this.runInGeneration(input.handle, {
1059
- generation: snapshot.generation,
1060
- recordId: snapshot.record?.acpxRecordId
1061
- }, () => this.resolveDelegateForOperationSnapshot(input.handle, snapshot).setMode(toAcpxResourceInput(input)));
1062
- }
1063
- async setConfigOption(input) {
1064
- const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1065
- return await this.runInGeneration(input.handle, {
1066
- generation: snapshot.generation,
1067
- recordId: snapshot.record?.acpxRecordId
1068
- }, () => this.setConfigOptionUnlocked(input, snapshot));
1069
- }
1070
- async setConfigOptionUnlocked(logicalInput, snapshot) {
1071
- const { command } = snapshot;
1072
- const delegate = this.resolveDelegateForOperationSnapshot(logicalInput.handle, snapshot);
1073
- const input = toAcpxResourceInput(logicalInput);
1074
- const key = input.key.trim().toLowerCase();
1075
- const isCodexAcp = isCodexAcpCommand(command);
1076
- if (WIRE_TIMEOUT_CONFIG_KEYS.has(key) && (isCodexAcp || isClaudeAcpCommand(command))) return;
1077
- if (isCodexAcp) {
1078
- if (key === "model") {
1079
- const classification = classifyCodexAcpModelRequest(input.value);
1080
- if (classification.kind === "unsupported") failUnsupportedCodexAcpModel(input.value);
1081
- const { override } = classification;
1082
- const modelResult = override.model ? await delegate.setConfigOption({
1083
- ...input,
1084
- key: "model",
1085
- value: override.model
1086
- }) : void 0;
1087
- this.generationRegistry.assertCurrentGeneration(snapshot.generation);
1088
- if (override.reasoningEffort) return await delegate.setConfigOption({
1089
- ...input,
1090
- key: "reasoning_effort",
1091
- value: override.reasoningEffort
1092
- });
1093
- return modelResult;
1094
- }
1095
- if (key === "thinking" || key === "thought_level" || key === "reasoning_effort") {
1096
- const classification = classifyCodexAcpModelRequest(void 0, input.value);
1097
- const reasoningEffort = classification.kind === "override" ? classification.override.reasoningEffort : void 0;
1098
- if (!reasoningEffort) throw new AcpRuntimeError("ACP_BACKEND_UNSUPPORTED_CONTROL", "Clearing Codex reasoning effort on an existing session is unsupported. Choose a supported explicit effort; the current effort is unchanged.");
1099
- return await delegate.setConfigOption({
1100
- ...input,
1101
- key: "reasoning_effort",
1102
- value: reasoningEffort
1103
- });
1104
- }
1105
- }
1106
- if (isClaudeAcpCommand(command) && key === "model") return await delegate.setConfigOption({
1107
- ...input,
1108
- value: normalizeClaudeAcpModelOverride(input.value) ?? input.value
1109
- });
1110
- return await delegate.setConfigOption(input);
1111
- }
1112
- async cancel(input) {
1113
- const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1114
- await this.runInGeneration(input.handle, {
1115
- generation: snapshot.generation,
1116
- recordId: snapshot.record?.acpxRecordId
1117
- }, () => this.resolveDelegateForOperationSnapshot(input.handle, snapshot).cancel(toAcpxResourceInput(input)));
1118
- }
1119
- async prepareFreshSession(input) {
1120
- const resource = assertAcpxSessionOwnerLocator(input, this.legacyBareSessionKeys);
1121
- this.generationRegistry.prepareFresh(resource);
1122
- this.legacyBareSessionKeys.delete(resource);
1123
- }
1124
- async close(input) {
1125
- const generation = this.generationForHandle(input.handle);
1126
- await this.runInGeneration(input.handle, { generation }, async () => {
1127
- const snapshot = await this.loadOperationSnapshotForHandle(input.handle, true, generation);
1128
- const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
1129
- await acpxOperationScope.run({
1130
- generation,
1131
- closeRecord: snapshot.record
1132
- }, async () => {
1133
- if (input.discardPersistentState && decodeAcpxRuntimeHandleState(input.handle.runtimeSessionName)?.mode !== "oneshot") {
1134
- this.generationRegistry.retireGeneration(generation);
1135
- this.legacyBareSessionKeys.delete(generation.resource);
1136
- }
1137
- const cleanup = await prepareAcpxProcessCleanup({
1138
- record: snapshot.record,
1139
- command: snapshot.command,
1140
- sessionKey: resolveAcpxSessionResource(input.handle),
1141
- gatewayInstanceId: this.gatewayInstanceId,
1142
- wrapperRoot: this.wrapperRoot,
1143
- leaseStore: this.processLeaseStore,
1144
- deps: this.processCleanupDeps
1145
- }).catch((error) => async () => {
1146
- throw error;
1147
- });
1148
- try {
1149
- await delegate.close(toAcpxResourceInput(input));
1150
- } finally {
1151
- await cleanup();
1152
- }
1153
- const recordId = snapshot.record?.acpxRecordId ?? input.handle.acpxRecordId ?? generation.resource;
1154
- const currentRecord = generation.records.get(recordId);
1155
- if (!currentRecord || currentRecord.acpSessionId === snapshot.record?.acpSessionId && currentRecord.createdAt === snapshot.record?.createdAt) {
1156
- generation.records.delete(recordId);
1157
- if (generation.activeRecordOperations.has(recordId)) generation.closedRecordIds.add(recordId);
1158
- }
1159
- generation.closeCompleted = true;
1160
- });
1161
- });
1162
- }
1163
- };
1164
- /** Test-only hooks for ACPX runtime behavior that is otherwise private. */
1165
- const testing = {
1166
- appendCodexAcpConfigOverrides,
1167
- isClaudeAcpCommand,
1168
- isCodexAcpCommand
1169
- };
1170
- //#endregion
1171
- export { ACPX_BACKEND_ID, AcpxRuntime, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, testing };