@openclaw/acpx 2026.7.2-beta.4 → 2026.7.2-beta.7

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,5 +1,5 @@
1
1
  import { t as AcpxPluginConfigSchema } from "./config-schema-lrk5nlcV.js";
2
- import "./process-lease-CU1cFI4I.js";
2
+ import { _ as splitCommandParts } from "./process-lease-DSLDgiNl.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
5
5
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -13,54 +13,6 @@ const CODEX_ACP_BIN = "codex-acp";
13
13
  const LEGACY_CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
14
14
  const OPENCLAW_CODEX_CONFIG_ARG = "--openclaw-codex-config";
15
15
  //#endregion
16
- //#region extensions/acpx/src/command-line.ts
17
- /**
18
- * Small shell-command helpers for ACPX-launched processes. Splitting supports
19
- * simple quoted command strings from config without invoking a shell parser.
20
- */
21
- /** Quote one command argument for display or config serialization. */
22
- function quoteCommandPart(value) {
23
- return JSON.stringify(value);
24
- }
25
- /** Split a command string into argv-like parts using simple quote/backslash rules. */
26
- function splitCommandParts(value) {
27
- const parts = [];
28
- let current = "";
29
- let quote = null;
30
- let escaping = false;
31
- for (const ch of value) {
32
- if (escaping) {
33
- current += ch;
34
- escaping = false;
35
- continue;
36
- }
37
- if (ch === "\\" && quote !== "'") {
38
- escaping = true;
39
- continue;
40
- }
41
- if (quote) {
42
- if (ch === quote) quote = null;
43
- else current += ch;
44
- continue;
45
- }
46
- if (ch === "'" || ch === "\"") {
47
- quote = ch;
48
- continue;
49
- }
50
- if (/\s/.test(ch)) {
51
- if (current) {
52
- parts.push(current);
53
- current = "";
54
- }
55
- continue;
56
- }
57
- current += ch;
58
- }
59
- if (escaping) current += "\\";
60
- if (current) parts.push(current);
61
- return parts;
62
- }
63
- //#endregion
64
16
  //#region extensions/acpx/src/config.ts
65
17
  /**
66
18
  * Resolves ACPX plugin config from raw user configuration. It locates the
@@ -439,7 +391,11 @@ async function cleanupOpenClawOwnedAcpxProcessTree(params) {
439
391
  try {
440
392
  processes = await (params.deps?.listProcesses ?? listPlatformProcesses)();
441
393
  } catch {
442
- processes = [];
394
+ return {
395
+ inspectedPids: [],
396
+ terminatedPids: [],
397
+ skippedReason: "process-list-unavailable"
398
+ };
443
399
  }
444
400
  const listedTree = collectProcessTree(processes, rootPid);
445
401
  if (listedTree.length === 0) return {
@@ -510,4 +466,4 @@ async function reapStaleOpenClawOwnedAcpxOrphans(params) {
510
466
  };
511
467
  }
512
468
  //#endregion
513
- export { resolveAcpxPluginRoot as a, splitCommandParts as c, LEGACY_CODEX_ACP_PACKAGE as d, OPENCLAW_CODEX_CONFIG_ARG as f, resolveAcpxPluginConfig as i, CODEX_ACP_BIN as l, isOpenClawLeaseAwareAcpxProcessCommand as n, toAcpMcpServers as o, reapStaleOpenClawOwnedAcpxOrphans as r, quoteCommandPart as s, cleanupOpenClawOwnedAcpxProcessTree as t, CODEX_ACP_PACKAGE as u };
469
+ export { resolveAcpxPluginRoot as a, CODEX_ACP_PACKAGE as c, resolveAcpxPluginConfig as i, LEGACY_CODEX_ACP_PACKAGE as l, isOpenClawLeaseAwareAcpxProcessCommand as n, toAcpMcpServers as o, reapStaleOpenClawOwnedAcpxOrphans as r, CODEX_ACP_BIN as s, cleanupOpenClawOwnedAcpxProcessTree as t, OPENCLAW_CODEX_CONFIG_ARG as u };
@@ -200,7 +200,7 @@ function createLazyAcpRuntimeProxy(resolveRuntime) {
200
200
  * immediately, then imports the heavier service only when a session needs it.
201
201
  */
202
202
  const ACPX_BACKEND_ID = "acpx";
203
- const loadServiceModule = createLazyRuntimeModule(() => import("./service-DOsKw_R1.js"));
203
+ const loadServiceModule = createLazyRuntimeModule(() => import("./service-CBZSAymH.js"));
204
204
  async function startRealService(state) {
205
205
  if (state.realRuntime) return state.realRuntime;
206
206
  if (!state.ctx) throw new Error("ACPX runtime service is not started");
@@ -1,2 +1,2 @@
1
- import { t as createAcpxRuntimeService } from "./register.runtime-Dajn8myc.js";
1
+ import { t as createAcpxRuntimeService } from "./register.runtime-BbS2JTTv.js";
2
2
  export { createAcpxRuntimeService };
@@ -1,6 +1,6 @@
1
- import { a as hashAcpxProcessCommand, l as withAcpxLeaseEnvironment, r as createAcpxProcessLeaseId } from "./process-lease-CU1cFI4I.js";
1
+ import { _ as splitCommandParts, a as hashAcpxProcessCommand, l as readAcpxProcessLeaseIdentity, r as createAcpxProcessLeaseId, u as withAcpxLeaseEnvironment } from "./process-lease-DSLDgiNl.js";
2
2
  import { AcpRuntimeError } from "./runtime-api.js";
3
- import { c as splitCommandParts, f as OPENCLAW_CODEX_CONFIG_ARG, n as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxProcessTree, u as CODEX_ACP_PACKAGE } from "./process-reaper-BIOtp_Ek.js";
3
+ import { c as CODEX_ACP_PACKAGE, n as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxProcessTree, u as OPENCLAW_CODEX_CONFIG_ARG } from "./process-reaper-DFwbcdPa.js";
4
4
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
5
5
  import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -128,29 +128,42 @@ function createResetAwareSessionStore(baseStore, params) {
128
128
  let recordToSave = record;
129
129
  const launch = params?.launchScope?.getStore();
130
130
  const sessionName = readSessionRecordName(record);
131
- const rootPid = readRecordAgentPid(record);
132
131
  const agentCommand = readRecordAgentCommand(record);
133
- const stableAgentCommand = launch?.stableCommand ?? agentCommand;
134
- if (launch && params?.leaseStore && sessionName === launch.sessionKey && rootPid && stableAgentCommand) {
135
- const lease = {
136
- leaseId: launch.leaseId,
137
- gatewayInstanceId: launch.gatewayInstanceId,
138
- sessionKey: launch.sessionKey,
139
- wrapperRoot: launch.wrapperRoot,
140
- wrapperPath: extractGeneratedWrapperPath(stableAgentCommand),
141
- rootPid,
142
- commandHash: hashAcpxProcessCommand(stableAgentCommand),
143
- startedAt: Date.now(),
144
- state: "open"
145
- };
146
- await params.leaseStore.save(lease);
147
- recordToSave = withOpenClawLeaseSessionMetadata({
148
- ...record,
149
- agentCommand: stableAgentCommand
150
- }, {
151
- openclawLeaseId: launch.leaseId,
152
- openclawGatewayInstanceId: launch.gatewayInstanceId
153
- });
132
+ const leasedCommand = launch?.leasedCommand ?? agentCommand;
133
+ const leaseIdentity = launch ?? readAcpxProcessLeaseIdentity(leasedCommand);
134
+ if (params?.leaseStore && params.gatewayInstanceId && params.wrapperRoot && (!launch || sessionName === launch.sessionKey) && leasedCommand && leaseIdentity?.gatewayInstanceId === params.gatewayInstanceId && isOpenClawLeaseAwareAcpxProcessCommand({
135
+ command: leasedCommand,
136
+ wrapperRoot: params.wrapperRoot
137
+ })) {
138
+ const existing = await params.leaseStore.load(leaseIdentity.leaseId);
139
+ if (!existing || existing.gatewayInstanceId === leaseIdentity.gatewayInstanceId && existing.sessionKey === sessionName && existing.wrapperRoot === params.wrapperRoot) {
140
+ const lifecycleRecord = Boolean(launch && launch.resolvedCommand !== launch.leasedCommand) ? {
141
+ ...record,
142
+ pid: void 0,
143
+ processId: void 0,
144
+ agentStartedAt: void 0
145
+ } : record;
146
+ const rootPid = readRecordAgentPid(lifecycleRecord);
147
+ if (rootPid) await params.leaseStore.save({
148
+ leaseId: leaseIdentity.leaseId,
149
+ gatewayInstanceId: leaseIdentity.gatewayInstanceId,
150
+ sessionKey: sessionName,
151
+ wrapperRoot: params.wrapperRoot,
152
+ wrapperPath: extractGeneratedWrapperPath(leasedCommand),
153
+ rootPid,
154
+ ...existing?.rootPid === rootPid && existing.processGroupId ? { processGroupId: existing.processGroupId } : {},
155
+ commandHash: hashAcpxProcessCommand(leasedCommand),
156
+ startedAt: existing?.rootPid === rootPid ? existing.startedAt : Date.now(),
157
+ state: "open"
158
+ });
159
+ recordToSave = withOpenClawLeaseSessionMetadata({
160
+ ...lifecycleRecord,
161
+ agentCommand: leasedCommand
162
+ }, {
163
+ openclawLeaseId: leaseIdentity.leaseId,
164
+ openclawGatewayInstanceId: leaseIdentity.gatewayInstanceId
165
+ });
166
+ }
154
167
  }
155
168
  await baseStore.save(recordToSave);
156
169
  if (sessionName) freshSessionKeys.delete(sessionName);
@@ -362,6 +375,9 @@ function quoteShellArg(value) {
362
375
  if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) return value;
363
376
  return `'${value.replace(/'/g, "'\\''")}'`;
364
377
  }
378
+ function normalizeAgentCommand(command) {
379
+ return (Array.isArray(command) ? command.map((part) => quoteShellArg(part)).join(" ") : command).trim() || void 0;
380
+ }
365
381
  function appendCodexAcpConfigOverrides(command, override) {
366
382
  const config = {
367
383
  ...override.model ? { model: override.model } : {},
@@ -373,7 +389,7 @@ function appendCodexAcpConfigOverrides(command, override) {
373
389
  function createModelScopedAgentRegistry(params) {
374
390
  return {
375
391
  resolve(agentName) {
376
- const command = params.agentRegistry.resolve(agentName);
392
+ const command = normalizeAgentCommand(params.agentRegistry.resolve(agentName)) ?? "";
377
393
  const override = params.scope.getStore();
378
394
  if (!override || normalizeAgentName(agentName) !== CODEX_ACP_AGENT_ID || !isCodexAcpCommand(command)) return params.leaseCommand(command);
379
395
  return params.leaseCommand(appendCodexAcpConfigOverrides(command, override));
@@ -386,8 +402,7 @@ function createModelScopedAgentRegistry(params) {
386
402
  function resolveAgentCommand(params) {
387
403
  const normalizedAgentName = normalizeAgentName(params.agentName);
388
404
  if (!normalizedAgentName) return;
389
- const resolvedCommand = params.agentRegistry.resolve(normalizedAgentName);
390
- return typeof resolvedCommand === "string" ? resolvedCommand.trim() || void 0 : void 0;
405
+ return normalizeAgentCommand(params.agentRegistry.resolve(normalizedAgentName));
391
406
  }
392
407
  function shouldUseBridgeSafeDelegateForCommand(command) {
393
408
  return isOpenClawBridgeCommand(command);
@@ -422,6 +437,9 @@ var AcpxRuntime = class {
422
437
  this.codexAcpModelOverrideScope = new AsyncLocalStorage();
423
438
  this.managedToolsSessionDelegates = /* @__PURE__ */ new Map();
424
439
  this.launchLeaseScope = new AsyncLocalStorage();
440
+ this.ensureSessionTails = /* @__PURE__ */ new Map();
441
+ this.processLeaseTransitionTails = /* @__PURE__ */ new Map();
442
+ this.processLeaseOperationCounts = /* @__PURE__ */ new Map();
425
443
  const { openclawProcessCleanup, ...delegateTestOptions } = testOptions ?? {};
426
444
  this.processCleanupDeps = openclawProcessCleanup;
427
445
  this.wrapperRoot = options.openclawWrapperRoot;
@@ -434,7 +452,8 @@ var AcpxRuntime = class {
434
452
  this.sessionStore = createResetAwareSessionStore(options.sessionStore, {
435
453
  gatewayInstanceId: this.gatewayInstanceId,
436
454
  leaseStore: this.processLeaseStore,
437
- launchScope: this.launchLeaseScope
455
+ launchScope: this.launchLeaseScope,
456
+ wrapperRoot: this.wrapperRoot
438
457
  });
439
458
  this.agentRegistry = options.agentRegistry;
440
459
  this.scopedAgentRegistry = createModelScopedAgentRegistry({
@@ -519,47 +538,206 @@ var AcpxRuntime = class {
519
538
  commandWithLaunchLease(command) {
520
539
  const launch = this.launchLeaseScope.getStore();
521
540
  if (!launch) return command;
522
- launch.stableCommand = command;
541
+ if (command === launch.stableCommand) return launch.resolvedCommand;
523
542
  return withAcpxLeaseEnvironment({
524
543
  command,
525
544
  leaseId: launch.leaseId,
526
545
  gatewayInstanceId: launch.gatewayInstanceId
527
546
  });
528
547
  }
529
- async canReuseStablePersistentSession(params) {
530
- if (params.mode !== "persistent" || !params.command) return false;
548
+ async readReusablePersistentSessionCommand(params) {
549
+ if (params.mode !== "persistent" || !params.command) return;
531
550
  const existing = await this.sessionStore.load(params.sessionKey);
532
- if (!existing || readRecordResetOnNextEnsure(existing)) return false;
551
+ if (!existing || readRecordResetOnNextEnsure(existing)) return;
533
552
  const recordCwd = readRecordCwd(existing);
534
- if (!recordCwd || resolve(recordCwd) !== resolve(params.cwd?.trim() || this.cwd)) return false;
535
- if (readRecordAgentCommand(existing) !== params.command) return false;
553
+ if (!recordCwd || resolve(recordCwd) !== resolve(params.cwd?.trim() || this.cwd)) return;
554
+ const recordCommand = readRecordAgentCommand(existing);
555
+ if (!recordCommand) return;
556
+ const leaseIdentity = readAcpxProcessLeaseIdentity(recordCommand);
557
+ if (leaseIdentity && leaseIdentity.gatewayInstanceId !== this.gatewayInstanceId) return;
558
+ if (recordCommand !== (leaseIdentity ? withAcpxLeaseEnvironment({
559
+ command: params.command,
560
+ leaseId: leaseIdentity.leaseId,
561
+ gatewayInstanceId: leaseIdentity.gatewayInstanceId
562
+ }) : params.command)) return;
536
563
  const existingSessionId = typeof existing === "object" && existing !== null ? existing.acpSessionId : void 0;
537
- return !params.resumeSessionId || existingSessionId === params.resumeSessionId;
564
+ return !params.resumeSessionId || existingSessionId === params.resumeSessionId ? recordCommand : void 0;
538
565
  }
539
566
  async runWithLaunchLease(params) {
540
- if (params.enabled === false || !params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
567
+ if (!params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
541
568
  command: params.command,
542
569
  wrapperRoot: this.wrapperRoot
543
570
  })) return await params.run();
571
+ const processLeaseStore = this.processLeaseStore;
572
+ const reusableIdentity = readAcpxProcessLeaseIdentity(params.reusableCommand);
573
+ const canReuseLeaseIdentity = reusableIdentity?.gatewayInstanceId === this.gatewayInstanceId;
574
+ const leaseId = canReuseLeaseIdentity ? reusableIdentity.leaseId : createAcpxProcessLeaseId();
575
+ const leasedCommand = withAcpxLeaseEnvironment({
576
+ command: params.command,
577
+ leaseId,
578
+ gatewayInstanceId: this.gatewayInstanceId
579
+ });
544
580
  const launch = {
545
- leaseId: createAcpxProcessLeaseId(),
581
+ leaseId,
546
582
  gatewayInstanceId: this.gatewayInstanceId,
547
583
  sessionKey: params.sessionKey,
548
584
  wrapperRoot: this.wrapperRoot,
549
- stableCommand: params.command
585
+ stableCommand: params.command,
586
+ resolvedCommand: params.reusableCommand ?? leasedCommand,
587
+ leasedCommand
550
588
  };
551
- await this.processLeaseStore.save({
552
- leaseId: launch.leaseId,
553
- gatewayInstanceId: launch.gatewayInstanceId,
554
- sessionKey: launch.sessionKey,
555
- wrapperRoot: launch.wrapperRoot,
556
- wrapperPath: extractGeneratedWrapperPath(params.command),
557
- rootPid: 0,
558
- commandHash: hashAcpxProcessCommand(params.command),
559
- startedAt: Date.now(),
560
- state: "open"
589
+ await this.retainProcessLeaseOperation(launch, async () => {
590
+ const reusableLease = canReuseLeaseIdentity ? await processLeaseStore.load(launch.leaseId) : void 0;
591
+ if (reusableLease && (reusableLease.gatewayInstanceId !== launch.gatewayInstanceId || reusableLease.sessionKey !== launch.sessionKey || reusableLease.wrapperRoot !== launch.wrapperRoot)) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", `ACPX process lease ${launch.leaseId} belongs to another session`);
592
+ if (!(!reusableLease && (!params.reusableCommand || params.reusableCommand === leasedCommand))) return;
593
+ await processLeaseStore.save({
594
+ leaseId: launch.leaseId,
595
+ gatewayInstanceId: launch.gatewayInstanceId,
596
+ sessionKey: launch.sessionKey,
597
+ wrapperRoot: launch.wrapperRoot,
598
+ wrapperPath: extractGeneratedWrapperPath(leasedCommand),
599
+ rootPid: 0,
600
+ commandHash: hashAcpxProcessCommand(leasedCommand),
601
+ startedAt: Date.now(),
602
+ state: "open"
603
+ });
604
+ });
605
+ try {
606
+ const result = await this.launchLeaseScope.run(launch, params.run);
607
+ await this.finalizeProcessLeaseForSession(params.sessionKey, launch);
608
+ return result;
609
+ } catch (error) {
610
+ await this.retirePendingProcessLease(launch);
611
+ throw error;
612
+ }
613
+ }
614
+ async prepareProcessLeaseForOperation(handle) {
615
+ if (!this.processLeaseStore || !this.gatewayInstanceId || !this.wrapperRoot) return;
616
+ const processLeaseStore = this.processLeaseStore;
617
+ const wrapperRoot = this.wrapperRoot;
618
+ const record = await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
619
+ const recordPid = readRecordAgentPid(record);
620
+ const command = readAgentCommandFromRecord(record);
621
+ const identity = readAcpxProcessLeaseIdentity(command);
622
+ if (!command || !isOpenClawLeaseAwareAcpxProcessCommand({
623
+ command,
624
+ wrapperRoot
625
+ })) return;
626
+ if (!identity) return;
627
+ if (identity.gatewayInstanceId !== this.gatewayInstanceId) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another gateway`);
628
+ await this.retainProcessLeaseOperation(identity, async () => {
629
+ const existing = await processLeaseStore.load(identity.leaseId);
630
+ if (!existing) {
631
+ await processLeaseStore.save({
632
+ leaseId: identity.leaseId,
633
+ gatewayInstanceId: identity.gatewayInstanceId,
634
+ sessionKey: handle.sessionKey,
635
+ wrapperRoot,
636
+ wrapperPath: extractGeneratedWrapperPath(command),
637
+ rootPid: recordPid ?? 0,
638
+ commandHash: hashAcpxProcessCommand(command),
639
+ startedAt: Date.now(),
640
+ state: "open"
641
+ });
642
+ return;
643
+ }
644
+ if (existing.gatewayInstanceId !== identity.gatewayInstanceId || existing.sessionKey !== handle.sessionKey || existing.wrapperRoot !== wrapperRoot) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another session`);
561
645
  });
562
- return await this.launchLeaseScope.run(launch, params.run);
646
+ return identity;
647
+ }
648
+ async runSerializedProcessLeaseTransition(leaseId, run) {
649
+ const previous = this.processLeaseTransitionTails.get(leaseId) ?? Promise.resolve();
650
+ let release;
651
+ const current = new Promise((resolve) => {
652
+ release = resolve;
653
+ });
654
+ const tail = previous.then(() => current);
655
+ this.processLeaseTransitionTails.set(leaseId, tail);
656
+ await previous;
657
+ try {
658
+ return await run();
659
+ } finally {
660
+ release();
661
+ if (this.processLeaseTransitionTails.get(leaseId) === tail) this.processLeaseTransitionTails.delete(leaseId);
662
+ }
663
+ }
664
+ async retainProcessLeaseOperation(identity, prepare) {
665
+ await this.runSerializedProcessLeaseTransition(identity.leaseId, async () => {
666
+ await prepare();
667
+ this.processLeaseOperationCounts.set(identity.leaseId, (this.processLeaseOperationCounts.get(identity.leaseId) ?? 0) + 1);
668
+ });
669
+ }
670
+ async releaseProcessLeaseOperation(identity, finalize) {
671
+ if (!identity) return;
672
+ await this.runSerializedProcessLeaseTransition(identity.leaseId, async () => {
673
+ const count = this.processLeaseOperationCounts.get(identity.leaseId) ?? 0;
674
+ if (count > 1) {
675
+ this.processLeaseOperationCounts.set(identity.leaseId, count - 1);
676
+ return;
677
+ }
678
+ if (count === 0) return;
679
+ this.processLeaseOperationCounts.delete(identity.leaseId);
680
+ await finalize();
681
+ });
682
+ }
683
+ async finalizeProcessLeaseForOperation(handle, identity) {
684
+ await this.finalizeProcessLeaseForSession(handle.acpxRecordId ?? handle.sessionKey, identity);
685
+ }
686
+ async finalizeProcessLeaseForSession(sessionId, identity) {
687
+ if (!identity || !this.processLeaseStore) return;
688
+ const processLeaseStore = this.processLeaseStore;
689
+ await this.releaseProcessLeaseOperation(identity, async () => {
690
+ const lease = await processLeaseStore.load(identity.leaseId);
691
+ if (!lease || lease.gatewayInstanceId !== identity.gatewayInstanceId) return;
692
+ if (lease.rootPid <= 0) {
693
+ await processLeaseStore.markState(identity.leaseId, "lost");
694
+ return;
695
+ }
696
+ try {
697
+ const recordIdentity = readAcpxProcessLeaseIdentity(readAgentCommandFromRecord(await this.sessionStore.load(sessionId)));
698
+ if (recordIdentity?.leaseId !== identity.leaseId || recordIdentity.gatewayInstanceId !== identity.gatewayInstanceId) await processLeaseStore.markState(identity.leaseId, "lost");
699
+ } catch {}
700
+ });
701
+ }
702
+ async retirePendingProcessLease(identity) {
703
+ if (!identity || !this.processLeaseStore) return;
704
+ const processLeaseStore = this.processLeaseStore;
705
+ await this.releaseProcessLeaseOperation(identity, async () => {
706
+ const lease = await processLeaseStore.load(identity.leaseId);
707
+ if (lease?.gatewayInstanceId === identity.gatewayInstanceId && lease.rootPid <= 0) await processLeaseStore.markState(identity.leaseId, "lost");
708
+ });
709
+ }
710
+ async runWithProcessLeaseForHandle(handle, run) {
711
+ const identity = await this.prepareProcessLeaseForOperation(handle);
712
+ try {
713
+ return await run();
714
+ } finally {
715
+ await this.finalizeProcessLeaseForOperation(handle, identity);
716
+ }
717
+ }
718
+ async finalizeProcessLeaseAfter(handle, identityPromise, resultPromise) {
719
+ try {
720
+ return await resultPromise;
721
+ } finally {
722
+ await this.finalizeProcessLeaseForOperation(handle, await identityPromise);
723
+ }
724
+ }
725
+ async runSerializedSessionEnsure(sessionKey, run) {
726
+ const key = sessionKey.trim() || sessionKey;
727
+ const previous = this.ensureSessionTails.get(key) ?? Promise.resolve();
728
+ let release;
729
+ const current = new Promise((resolve) => {
730
+ release = resolve;
731
+ });
732
+ const tail = previous.then(() => current);
733
+ this.ensureSessionTails.set(key, tail);
734
+ await previous;
735
+ try {
736
+ return await run();
737
+ } finally {
738
+ release();
739
+ if (this.ensureSessionTails.get(key) === tail) this.ensureSessionTails.delete(key);
740
+ }
563
741
  }
564
742
  async withCodexWrapperDiagnostics(params) {
565
743
  try {
@@ -602,7 +780,7 @@ var AcpxRuntime = class {
602
780
  wrapperRoot: lease.wrapperRoot,
603
781
  deps: this.processCleanupDeps
604
782
  });
605
- await this.processLeaseStore?.markState(lease.leaseId, result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
783
+ await this.processLeaseStore?.markState(lease.leaseId, result.skippedReason === "process-list-unavailable" ? "open" : result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
606
784
  return;
607
785
  }
608
786
  const rootCommand = readAgentCommandFromRecord(record) ?? resolveAgentCommand({
@@ -630,6 +808,9 @@ var AcpxRuntime = class {
630
808
  return this.probeDelegate.doctor();
631
809
  }
632
810
  async ensureSession(input) {
811
+ return await this.runSerializedSessionEnsure(input.sessionKey, () => this.ensureSessionUnlocked(input));
812
+ }
813
+ async ensureSessionUnlocked(input) {
633
814
  assertSupportedRuntimeSessionMode(input.mode);
634
815
  const command = resolveAgentCommand({
635
816
  agentName: input.agent,
@@ -655,7 +836,7 @@ var AcpxRuntime = class {
655
836
  model: claudeModelOverride
656
837
  } : input;
657
838
  const stableLaunchCommand = codexModelOverride && command ? appendCodexAcpConfigOverrides(command, codexModelOverride) : command;
658
- const shouldStartWithLease = !await this.canReuseStablePersistentSession({
839
+ const reusableCommand = await this.readReusablePersistentSessionCommand({
659
840
  sessionKey: input.sessionKey,
660
841
  mode: input.mode,
661
842
  cwd: input.cwd,
@@ -665,7 +846,7 @@ var AcpxRuntime = class {
665
846
  const handle = !codexModelOverride ? await this.runWithLaunchLease({
666
847
  sessionKey: ensureInput.sessionKey,
667
848
  command: stableLaunchCommand,
668
- enabled: shouldStartWithLease,
849
+ reusableCommand,
669
850
  run: () => this.withCodexWrapperDiagnostics({
670
851
  command: stableLaunchCommand,
671
852
  fallbackCode: "ACP_SESSION_INIT_FAILED",
@@ -674,7 +855,7 @@ var AcpxRuntime = class {
674
855
  }) : await this.runWithLaunchLease({
675
856
  sessionKey: input.sessionKey,
676
857
  command: stableLaunchCommand,
677
- enabled: shouldStartWithLease,
858
+ reusableCommand,
678
859
  run: () => this.codexAcpModelOverrideScope.run(codexModelOverride, () => this.withCodexWrapperDiagnostics({
679
860
  command: stableLaunchCommand,
680
861
  fallbackCode: "ACP_SESSION_INIT_FAILED",
@@ -687,9 +868,11 @@ var AcpxRuntime = class {
687
868
  } : handle;
688
869
  }
689
870
  async *runTurn(input) {
690
- const command = await this.resolveCommandForHandle(input.handle);
691
- const delegate = await this.resolveDelegateForHandle(input.handle);
871
+ const turnLease = await this.prepareProcessLeaseForOperation(input.handle);
872
+ let command;
692
873
  try {
874
+ command = await this.resolveCommandForHandle(input.handle);
875
+ const delegate = await this.resolveDelegateForHandle(input.handle);
693
876
  for await (const event of delegate.runTurn(withOpenClawManagedTurnTimeout(input))) {
694
877
  if (event.type !== "error" || !isCodexAcpCommand(command) || !isGenericInternalAcpErrorMessage(event.message)) {
695
878
  yield event;
@@ -711,11 +894,18 @@ var AcpxRuntime = class {
711
894
  const stderrTail = await this.readCodexTurnFailureStderr({ handle: input.handle });
712
895
  if (!stderrTail) throw error;
713
896
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
897
+ } finally {
898
+ await this.finalizeProcessLeaseForOperation(input.handle, turnLease);
714
899
  }
715
900
  }
716
901
  startTurn(input) {
717
902
  const readCodexTurnFailureStderr = () => this.readCodexTurnFailureStderr({ handle: input.handle });
718
- const turnPromise = Promise.all([this.resolveCommandForHandle(input.handle), this.resolveDelegateForHandle(input.handle)]).then(async ([command, delegate]) => {
903
+ const turnLeasePromise = this.prepareProcessLeaseForOperation(input.handle);
904
+ const turnPromise = Promise.all([
905
+ turnLeasePromise,
906
+ this.resolveCommandForHandle(input.handle),
907
+ this.resolveDelegateForHandle(input.handle)
908
+ ]).then(async ([, command, delegate]) => {
719
909
  try {
720
910
  return {
721
911
  command,
@@ -756,7 +946,7 @@ var AcpxRuntime = class {
756
946
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
757
947
  }
758
948
  } },
759
- result: turnPromise.then(async ({ command, turn }) => {
949
+ result: this.finalizeProcessLeaseAfter(input.handle, turnLeasePromise, turnPromise.then(async ({ command, turn }) => {
760
950
  try {
761
951
  const result = await turn.result;
762
952
  if (result.status !== "failed" || !isCodexAcpCommand(command) || !isGenericInternalAcpErrorMessage(result.error.message)) return result;
@@ -776,7 +966,7 @@ var AcpxRuntime = class {
776
966
  if (!stderrTail) throw error;
777
967
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
778
968
  }
779
- }),
969
+ })),
780
970
  cancel(inputArgs) {
781
971
  return turnPromise.then(({ turn }) => turn.cancel(inputArgs));
782
972
  },
@@ -792,9 +982,13 @@ var AcpxRuntime = class {
792
982
  return (await this.resolveDelegateForHandle(input.handle)).getStatus(input);
793
983
  }
794
984
  async setMode(input) {
795
- await (await this.resolveDelegateForHandle(input.handle)).setMode(input);
985
+ const delegate = await this.resolveDelegateForHandle(input.handle);
986
+ await this.runWithProcessLeaseForHandle(input.handle, () => delegate.setMode(input));
796
987
  }
797
988
  async setConfigOption(input) {
989
+ await this.runWithProcessLeaseForHandle(input.handle, () => this.setConfigOptionUnlocked(input));
990
+ }
991
+ async setConfigOptionUnlocked(input) {
798
992
  const delegate = await this.resolveDelegateForHandle(input.handle);
799
993
  const command = await this.resolveCommandForHandle(input.handle);
800
994
  const key = input.key.trim().toLowerCase();
@@ -846,21 +1040,29 @@ var AcpxRuntime = class {
846
1040
  this.sessionStore.markFresh(input.sessionKey);
847
1041
  }
848
1042
  async close(input) {
849
- const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
850
- let closeSucceeded;
851
- const delegate = this.resolveDelegateForLoadedRecord(input.handle, record);
1043
+ const closeLease = await this.prepareProcessLeaseForOperation(input.handle);
1044
+ let cleanupSucceeded = false;
852
1045
  try {
853
- await delegate.close({
854
- handle: input.handle,
855
- reason: input.reason,
856
- discardPersistentState: input.discardPersistentState
857
- });
858
- closeSucceeded = true;
1046
+ const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
1047
+ let closeSucceeded;
1048
+ const delegate = this.resolveDelegateForLoadedRecord(input.handle, record);
1049
+ try {
1050
+ await delegate.close({
1051
+ handle: input.handle,
1052
+ reason: input.reason,
1053
+ discardPersistentState: input.discardPersistentState
1054
+ });
1055
+ closeSucceeded = true;
1056
+ } finally {
1057
+ await this.cleanupProcessTreeForRecord(input.handle, record);
1058
+ cleanupSucceeded = true;
1059
+ }
1060
+ if (closeSucceeded) this.releaseManagedToolsDelegateForSession(input.handle.sessionKey);
1061
+ if (closeSucceeded && input.discardPersistentState) this.sessionStore.markFresh(input.handle.sessionKey);
859
1062
  } finally {
860
- await this.cleanupProcessTreeForRecord(input.handle, record);
1063
+ if (cleanupSucceeded) await this.finalizeProcessLeaseForOperation(input.handle, closeLease);
1064
+ else await this.retirePendingProcessLease(closeLease);
861
1065
  }
862
- if (closeSucceeded) this.releaseManagedToolsDelegateForSession(input.handle.sessionKey);
863
- if (closeSucceeded && input.discardPersistentState) this.sessionStore.markFresh(input.handle.sessionKey);
864
1066
  }
865
1067
  };
866
1068
  /** Test-only hooks for ACPX runtime behavior that is otherwise private. */
@@ -870,6 +1072,7 @@ const testing = {
870
1072
  classifyCodexAcpModelRequest,
871
1073
  isClaudeAcpCommand,
872
1074
  isCodexAcpCommand,
1075
+ normalizeAgentCommand,
873
1076
  normalizeClaudeAcpModelOverride
874
1077
  };
875
1078
  //#endregion
@@ -1,8 +1,8 @@
1
- import { n as createLazyAcpRuntimeProxy } from "./register.runtime-Dajn8myc.js";
1
+ import { n as createLazyAcpRuntimeProxy } from "./register.runtime-BbS2JTTv.js";
2
2
  import "./config-schema-lrk5nlcV.js";
3
- import { c as openAcpxProcessLeaseStateStore, d as ACPX_GATEWAY_INSTANCE_NAMESPACE, i as createAcpxProcessLeaseStore, m as normalizeAcpxGatewayInstanceRecord, n as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, t as OPENCLAW_ACPX_LEASE_ID_ARG, u as ACPX_GATEWAY_INSTANCE_KEY } from "./process-lease-CU1cFI4I.js";
3
+ import { _ as splitCommandParts, c as openAcpxProcessLeaseStateStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, g as quoteCommandPart, h as normalizeAcpxGatewayInstanceRecord, i as createAcpxProcessLeaseStore, n as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, t as OPENCLAW_ACPX_LEASE_ID_ARG } from "./process-lease-DSLDgiNl.js";
4
4
  import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "./runtime-api.js";
5
- import { a as resolveAcpxPluginRoot, c as splitCommandParts, d as LEGACY_CODEX_ACP_PACKAGE, f as OPENCLAW_CODEX_CONFIG_ARG, i as resolveAcpxPluginConfig, l as CODEX_ACP_BIN, o as toAcpMcpServers, r as reapStaleOpenClawOwnedAcpxOrphans, s as quoteCommandPart, t as cleanupOpenClawOwnedAcpxProcessTree, u as CODEX_ACP_PACKAGE } from "./process-reaper-BIOtp_Ek.js";
5
+ import { a as resolveAcpxPluginRoot, c as CODEX_ACP_PACKAGE, i as resolveAcpxPluginConfig, l as LEGACY_CODEX_ACP_PACKAGE, o as toAcpMcpServers, r as reapStaleOpenClawOwnedAcpxOrphans, s as CODEX_ACP_BIN, t as cleanupOpenClawOwnedAcpxProcessTree, u as OPENCLAW_CODEX_CONFIG_ARG } from "./process-reaper-DFwbcdPa.js";
6
6
  import { createRequire } from "node:module";
7
7
  import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
8
8
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
@@ -1026,7 +1026,7 @@ async function prepareAcpxCodexAuthConfig(params) {
1026
1026
  const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
1027
1027
  const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
1028
1028
  const ACPX_BACKEND_ID = "acpx";
1029
- const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-CN4JQkdV.js"));
1029
+ const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-By_lR8uk.js"));
1030
1030
  /** Convert ACPX timeout seconds into timer-safe milliseconds. */
1031
1031
  function resolveAcpxTimerTimeoutMs(timeoutSeconds) {
1032
1032
  if (timeoutSeconds === void 0) return;
@@ -1179,7 +1179,7 @@ async function reapOpenAcpxProcessLeases(params) {
1179
1179
  });
1180
1180
  inspectedPids.push(...result.inspectedPids);
1181
1181
  terminatedPids.push(...result.terminatedPids);
1182
- await params.leaseStore.markState(lease.leaseId, result.terminatedPids.length > 0 ? "closed" : "lost");
1182
+ await params.leaseStore.markState(lease.leaseId, result.skippedReason === "process-list-unavailable" ? "open" : result.terminatedPids.length > 0 ? "closed" : "lost");
1183
1183
  }
1184
1184
  return {
1185
1185
  inspectedPids,