@openclaw/acpx 2026.7.2-beta.5 → 2026.8.1-beta.2

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,11 +1,11 @@
1
- import { a as hashAcpxProcessCommand, l as withAcpxLeaseEnvironment, r as createAcpxProcessLeaseId } from "./process-lease-CU1cFI4I.js";
1
+ import { d as withAcpxLeaseEnvironment, i as createAcpxProcessLeaseId, o as hashAcpxProcessCommand, t as ACPX_PROBE_LEASE_SESSION_KEY, u as readAcpxProcessLeaseIdentity, v as splitCommandParts } from "./process-lease-Cwvj7WGe.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 { d as OPENCLAW_CODEX_CONFIG_ARG, l as CODEX_ACP_PACKAGE, n as cleanupOpenClawOwnedAcpxProcessTree, r as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxPendingLease } from "./process-reaper-DzVuCxl3.js";
4
4
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
5
5
  import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
6
- import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
7
- import fs from "node:fs/promises";
8
6
  import path, { resolve } from "node:path";
7
+ import fs from "node:fs/promises";
8
+ import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
10
  import { ACPX_BACKEND_ID, AcpxRuntime as AcpxRuntime$1, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isRequestedModelUnsupportedError } from "acpx/runtime";
11
11
  import { redactSensitiveText } from "openclaw/plugin-sdk/security-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,10 @@ 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();
443
+ this.uncertainProcessLeaseIds = /* @__PURE__ */ new Set();
425
444
  const { openclawProcessCleanup, ...delegateTestOptions } = testOptions ?? {};
426
445
  this.processCleanupDeps = openclawProcessCleanup;
427
446
  this.wrapperRoot = options.openclawWrapperRoot;
@@ -434,7 +453,8 @@ var AcpxRuntime = class {
434
453
  this.sessionStore = createResetAwareSessionStore(options.sessionStore, {
435
454
  gatewayInstanceId: this.gatewayInstanceId,
436
455
  leaseStore: this.processLeaseStore,
437
- launchScope: this.launchLeaseScope
456
+ launchScope: this.launchLeaseScope,
457
+ wrapperRoot: this.wrapperRoot
438
458
  });
439
459
  this.agentRegistry = options.agentRegistry;
440
460
  this.scopedAgentRegistry = createModelScopedAgentRegistry({
@@ -458,6 +478,7 @@ var AcpxRuntime = class {
458
478
  agentName: normalizeAgentName(options.probeAgent) ?? "codex",
459
479
  agentRegistry: this.agentRegistry
460
480
  });
481
+ this.probeCommand = probeCommand;
461
482
  const useBridgeSafeProbe = this.managedToolsMcpBridgeEnabled || shouldUseBridgeSafeDelegateForCommand(probeCommand);
462
483
  this.probeDelegate = useBridgeSafeProbe ? this.bridgeSafeDelegate : this.delegate;
463
484
  }
@@ -489,77 +510,244 @@ var AcpxRuntime = class {
489
510
  if (!normalizedSessionKey) return;
490
511
  this.managedToolsSessionDelegates.delete(normalizedSessionKey);
491
512
  }
492
- async resolveDelegateForHandle(handle) {
513
+ async loadOperationSnapshotForHandle(handle) {
493
514
  const record = await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
494
- return this.resolveDelegateForLoadedRecord(handle, record);
515
+ return {
516
+ record,
517
+ command: readAgentCommandFromRecord(record) ?? resolveAgentCommand({
518
+ agentName: readAgentFromHandle(handle),
519
+ agentRegistry: this.agentRegistry
520
+ })
521
+ };
495
522
  }
496
- resolveDelegateForLoadedRecord(handle, record) {
497
- const recordCommand = readAgentCommandFromRecord(record);
498
- if (recordCommand) return this.resolveDelegateForSession({
499
- command: recordCommand,
500
- sessionKey: handle.sessionKey
501
- });
502
- const command = resolveAgentCommand({
503
- agentName: readAgentFromHandle(handle),
504
- agentRegistry: this.agentRegistry
505
- });
523
+ resolveDelegateForOperationSnapshot(handle, snapshot) {
506
524
  return this.resolveDelegateForSession({
507
- command,
525
+ command: snapshot.command,
508
526
  sessionKey: handle.sessionKey
509
527
  });
510
528
  }
511
- async resolveCommandForHandle(handle) {
512
- const recordCommand = readAgentCommandFromRecord(await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey));
513
- if (recordCommand) return recordCommand;
514
- return resolveAgentCommand({
515
- agentName: readAgentFromHandle(handle),
516
- agentRegistry: this.agentRegistry
517
- });
518
- }
519
529
  commandWithLaunchLease(command) {
520
530
  const launch = this.launchLeaseScope.getStore();
521
531
  if (!launch) return command;
522
- launch.stableCommand = command;
532
+ if (command === launch.stableCommand) return launch.resolvedCommand;
523
533
  return withAcpxLeaseEnvironment({
524
534
  command,
525
535
  leaseId: launch.leaseId,
526
536
  gatewayInstanceId: launch.gatewayInstanceId
527
537
  });
528
538
  }
529
- async canReuseStablePersistentSession(params) {
530
- if (params.mode !== "persistent" || !params.command) return false;
539
+ async readReusablePersistentSessionCommand(params) {
540
+ if (params.mode !== "persistent" || !params.command) return;
531
541
  const existing = await this.sessionStore.load(params.sessionKey);
532
- if (!existing || readRecordResetOnNextEnsure(existing)) return false;
542
+ if (!existing || readRecordResetOnNextEnsure(existing)) return;
533
543
  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;
544
+ if (!recordCwd || resolve(recordCwd) !== resolve(params.cwd?.trim() || this.cwd)) return;
545
+ const recordCommand = readRecordAgentCommand(existing);
546
+ if (!recordCommand) return;
547
+ const leaseIdentity = readAcpxProcessLeaseIdentity(recordCommand);
548
+ if (leaseIdentity && leaseIdentity.gatewayInstanceId !== this.gatewayInstanceId) return;
549
+ if (recordCommand !== (leaseIdentity ? withAcpxLeaseEnvironment({
550
+ command: params.command,
551
+ leaseId: leaseIdentity.leaseId,
552
+ gatewayInstanceId: leaseIdentity.gatewayInstanceId
553
+ }) : params.command)) return;
536
554
  const existingSessionId = typeof existing === "object" && existing !== null ? existing.acpSessionId : void 0;
537
- return !params.resumeSessionId || existingSessionId === params.resumeSessionId;
555
+ return !params.resumeSessionId || existingSessionId === params.resumeSessionId ? recordCommand : void 0;
538
556
  }
539
557
  async runWithLaunchLease(params) {
540
- if (params.enabled === false || !params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
558
+ if (!params.command || !this.wrapperRoot || !this.gatewayInstanceId || !this.processLeaseStore || !isOpenClawLeaseAwareAcpxProcessCommand({
541
559
  command: params.command,
542
560
  wrapperRoot: this.wrapperRoot
543
561
  })) return await params.run();
562
+ const processLeaseStore = this.processLeaseStore;
563
+ const reusableIdentity = readAcpxProcessLeaseIdentity(params.reusableCommand);
564
+ const canReuseLeaseIdentity = reusableIdentity?.gatewayInstanceId === this.gatewayInstanceId;
565
+ const leaseId = canReuseLeaseIdentity ? reusableIdentity.leaseId : params.finalizeCompletedProbe ? `probe-${hashAcpxProcessCommand(`${this.gatewayInstanceId}\0${extractGeneratedWrapperPath(params.command)}`)}` : createAcpxProcessLeaseId();
566
+ const leasedCommand = withAcpxLeaseEnvironment({
567
+ command: params.command,
568
+ leaseId,
569
+ gatewayInstanceId: this.gatewayInstanceId
570
+ });
544
571
  const launch = {
545
- leaseId: createAcpxProcessLeaseId(),
572
+ leaseId,
546
573
  gatewayInstanceId: this.gatewayInstanceId,
547
574
  sessionKey: params.sessionKey,
548
575
  wrapperRoot: this.wrapperRoot,
549
- stableCommand: params.command
576
+ stableCommand: params.command,
577
+ resolvedCommand: params.reusableCommand ?? leasedCommand,
578
+ leasedCommand
550
579
  };
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"
580
+ await this.retainProcessLeaseOperation(launch, async () => {
581
+ const reusableLease = canReuseLeaseIdentity ? await processLeaseStore.load(launch.leaseId) : void 0;
582
+ 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`);
583
+ if (!(!reusableLease && (!params.reusableCommand || params.reusableCommand === leasedCommand))) return;
584
+ await processLeaseStore.save({
585
+ leaseId: launch.leaseId,
586
+ gatewayInstanceId: launch.gatewayInstanceId,
587
+ sessionKey: launch.sessionKey,
588
+ wrapperRoot: launch.wrapperRoot,
589
+ wrapperPath: extractGeneratedWrapperPath(leasedCommand),
590
+ rootPid: 0,
591
+ commandHash: hashAcpxProcessCommand(leasedCommand),
592
+ startedAt: Date.now(),
593
+ state: "open"
594
+ });
595
+ });
596
+ try {
597
+ const result = await this.launchLeaseScope.run(launch, params.run);
598
+ if (params.finalizeCompletedProbe) await this.finalizeCompletedProbeLease(launch);
599
+ else await this.finalizeProcessLeaseForSession(params.sessionKey, launch);
600
+ return result;
601
+ } catch (error) {
602
+ await this.releaseProcessLeaseAfterUncertainFailure(launch);
603
+ throw error;
604
+ }
605
+ }
606
+ async prepareProcessLeaseForOperation(handle, record) {
607
+ if (!this.processLeaseStore || !this.gatewayInstanceId || !this.wrapperRoot) return;
608
+ const processLeaseStore = this.processLeaseStore;
609
+ const wrapperRoot = this.wrapperRoot;
610
+ const recordPid = readRecordAgentPid(record);
611
+ const command = readAgentCommandFromRecord(record);
612
+ const identity = readAcpxProcessLeaseIdentity(command);
613
+ if (!command || !isOpenClawLeaseAwareAcpxProcessCommand({
614
+ command,
615
+ wrapperRoot
616
+ })) return;
617
+ if (!identity) return;
618
+ if (identity.gatewayInstanceId !== this.gatewayInstanceId) throw new AcpRuntimeError("ACP_TURN_FAILED", `ACPX process lease ${identity.leaseId} belongs to another gateway`);
619
+ await this.retainProcessLeaseOperation(identity, async () => {
620
+ const existing = await processLeaseStore.load(identity.leaseId);
621
+ if (!existing) {
622
+ await processLeaseStore.save({
623
+ leaseId: identity.leaseId,
624
+ gatewayInstanceId: identity.gatewayInstanceId,
625
+ sessionKey: handle.sessionKey,
626
+ wrapperRoot,
627
+ wrapperPath: extractGeneratedWrapperPath(command),
628
+ rootPid: recordPid ?? 0,
629
+ commandHash: hashAcpxProcessCommand(command),
630
+ startedAt: Date.now(),
631
+ state: "open"
632
+ });
633
+ return;
634
+ }
635
+ 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`);
636
+ });
637
+ return identity;
638
+ }
639
+ async runSerializedProcessLeaseTransition(leaseId, run) {
640
+ const previous = this.processLeaseTransitionTails.get(leaseId) ?? Promise.resolve();
641
+ let release;
642
+ const current = new Promise((resolve) => {
643
+ release = resolve;
644
+ });
645
+ const tail = previous.then(() => current);
646
+ this.processLeaseTransitionTails.set(leaseId, tail);
647
+ await previous;
648
+ try {
649
+ return await run();
650
+ } finally {
651
+ release();
652
+ if (this.processLeaseTransitionTails.get(leaseId) === tail) this.processLeaseTransitionTails.delete(leaseId);
653
+ }
654
+ }
655
+ async retainProcessLeaseOperation(identity, prepare) {
656
+ await this.runSerializedProcessLeaseTransition(identity.leaseId, async () => {
657
+ await prepare();
658
+ this.processLeaseOperationCounts.set(identity.leaseId, (this.processLeaseOperationCounts.get(identity.leaseId) ?? 0) + 1);
659
+ });
660
+ }
661
+ async releaseProcessLeaseOperation(identity, finalize) {
662
+ if (!identity) return;
663
+ await this.runSerializedProcessLeaseTransition(identity.leaseId, async () => {
664
+ const count = this.processLeaseOperationCounts.get(identity.leaseId) ?? 0;
665
+ if (count > 1) {
666
+ this.processLeaseOperationCounts.set(identity.leaseId, count - 1);
667
+ return;
668
+ }
669
+ if (count === 0) return;
670
+ this.processLeaseOperationCounts.delete(identity.leaseId);
671
+ await finalize();
561
672
  });
562
- return await this.launchLeaseScope.run(launch, params.run);
673
+ }
674
+ async finalizeProcessLeaseForOperation(handle, identity) {
675
+ await this.finalizeProcessLeaseForSession(handle.acpxRecordId ?? handle.sessionKey, identity);
676
+ }
677
+ async finalizeProcessLeaseForSession(sessionId, identity) {
678
+ if (!identity || !this.processLeaseStore) return;
679
+ const processLeaseStore = this.processLeaseStore;
680
+ await this.releaseProcessLeaseOperation(identity, async () => {
681
+ const lease = await processLeaseStore.load(identity.leaseId);
682
+ if (!lease || lease.gatewayInstanceId !== identity.gatewayInstanceId) return;
683
+ if (lease.rootPid <= 0) {
684
+ if (this.uncertainProcessLeaseIds.has(identity.leaseId)) return;
685
+ await processLeaseStore.markState(identity.leaseId, "lost");
686
+ return;
687
+ }
688
+ this.uncertainProcessLeaseIds.delete(identity.leaseId);
689
+ try {
690
+ const recordIdentity = readAcpxProcessLeaseIdentity(readAgentCommandFromRecord(await this.sessionStore.load(sessionId)));
691
+ if (recordIdentity?.leaseId !== identity.leaseId || recordIdentity.gatewayInstanceId !== identity.gatewayInstanceId) await processLeaseStore.markState(identity.leaseId, "lost");
692
+ } catch {}
693
+ });
694
+ }
695
+ async finalizeCompletedProbeLease(identity) {
696
+ if (!this.processLeaseStore) return;
697
+ const processLeaseStore = this.processLeaseStore;
698
+ await this.releaseProcessLeaseOperation(identity, async () => {
699
+ const lease = await processLeaseStore.load(identity.leaseId);
700
+ if (!lease || lease.gatewayInstanceId !== identity.gatewayInstanceId) return;
701
+ if (lease.rootPid > 0) return;
702
+ await cleanupOpenClawOwnedAcpxPendingLease({
703
+ leaseId: lease.leaseId,
704
+ gatewayInstanceId: lease.gatewayInstanceId,
705
+ wrapperRoot: lease.wrapperRoot,
706
+ wrapperPath: lease.wrapperPath,
707
+ deps: this.processCleanupDeps
708
+ });
709
+ });
710
+ }
711
+ async releaseProcessLeaseAfterUncertainFailure(identity) {
712
+ if (!identity || !this.processLeaseStore) return;
713
+ this.uncertainProcessLeaseIds.add(identity.leaseId);
714
+ const processLeaseStore = this.processLeaseStore;
715
+ await this.releaseProcessLeaseOperation(identity, async () => {
716
+ const lease = await processLeaseStore.load(identity.leaseId);
717
+ if (!lease || lease.gatewayInstanceId !== identity.gatewayInstanceId || lease.rootPid > 0) this.uncertainProcessLeaseIds.delete(identity.leaseId);
718
+ });
719
+ }
720
+ async runWithProcessLeaseForHandle(handle, record, run) {
721
+ const identity = await this.prepareProcessLeaseForOperation(handle, record);
722
+ try {
723
+ return await run();
724
+ } finally {
725
+ await this.finalizeProcessLeaseForOperation(handle, identity);
726
+ }
727
+ }
728
+ async finalizeProcessLeaseAfter(handle, identityPromise, resultPromise) {
729
+ try {
730
+ return await resultPromise;
731
+ } finally {
732
+ await this.finalizeProcessLeaseForOperation(handle, await identityPromise);
733
+ }
734
+ }
735
+ async runSerializedSessionEnsure(sessionKey, run) {
736
+ const key = sessionKey.trim() || sessionKey;
737
+ const previous = this.ensureSessionTails.get(key) ?? Promise.resolve();
738
+ let release;
739
+ const current = new Promise((resolve) => {
740
+ release = resolve;
741
+ });
742
+ const tail = previous.then(() => current);
743
+ this.ensureSessionTails.set(key, tail);
744
+ await previous;
745
+ try {
746
+ return await run();
747
+ } finally {
748
+ release();
749
+ if (this.ensureSessionTails.get(key) === tail) this.ensureSessionTails.delete(key);
750
+ }
563
751
  }
564
752
  async withCodexWrapperDiagnostics(params) {
565
753
  try {
@@ -602,7 +790,7 @@ var AcpxRuntime = class {
602
790
  wrapperRoot: lease.wrapperRoot,
603
791
  deps: this.processCleanupDeps
604
792
  });
605
- await this.processLeaseStore?.markState(lease.leaseId, result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
793
+ await this.processLeaseStore?.markState(lease.leaseId, result.skippedReason === "process-list-unavailable" || result.skippedReason === "unsupported-platform" ? "open" : result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
606
794
  return;
607
795
  }
608
796
  const rootCommand = readAgentCommandFromRecord(record) ?? resolveAgentCommand({
@@ -623,13 +811,26 @@ var AcpxRuntime = class {
623
811
  isHealthy() {
624
812
  return this.probeDelegate.isHealthy();
625
813
  }
626
- probeAvailability() {
627
- return this.probeDelegate.probeAvailability();
814
+ async probeAvailability() {
815
+ await this.runWithLaunchLease({
816
+ sessionKey: ACPX_PROBE_LEASE_SESSION_KEY,
817
+ command: this.probeCommand,
818
+ finalizeCompletedProbe: true,
819
+ run: () => this.probeDelegate.probeAvailability()
820
+ });
628
821
  }
629
- doctor() {
630
- return this.probeDelegate.doctor();
822
+ async doctor() {
823
+ return await this.runWithLaunchLease({
824
+ sessionKey: ACPX_PROBE_LEASE_SESSION_KEY,
825
+ command: this.probeCommand,
826
+ finalizeCompletedProbe: true,
827
+ run: () => this.probeDelegate.doctor()
828
+ });
631
829
  }
632
830
  async ensureSession(input) {
831
+ return await this.runSerializedSessionEnsure(input.sessionKey, () => this.ensureSessionUnlocked(input));
832
+ }
833
+ async ensureSessionUnlocked(input) {
633
834
  assertSupportedRuntimeSessionMode(input.mode);
634
835
  const command = resolveAgentCommand({
635
836
  agentName: input.agent,
@@ -655,7 +856,7 @@ var AcpxRuntime = class {
655
856
  model: claudeModelOverride
656
857
  } : input;
657
858
  const stableLaunchCommand = codexModelOverride && command ? appendCodexAcpConfigOverrides(command, codexModelOverride) : command;
658
- const shouldStartWithLease = !await this.canReuseStablePersistentSession({
859
+ const reusableCommand = await this.readReusablePersistentSessionCommand({
659
860
  sessionKey: input.sessionKey,
660
861
  mode: input.mode,
661
862
  cwd: input.cwd,
@@ -665,7 +866,7 @@ var AcpxRuntime = class {
665
866
  const handle = !codexModelOverride ? await this.runWithLaunchLease({
666
867
  sessionKey: ensureInput.sessionKey,
667
868
  command: stableLaunchCommand,
668
- enabled: shouldStartWithLease,
869
+ reusableCommand,
669
870
  run: () => this.withCodexWrapperDiagnostics({
670
871
  command: stableLaunchCommand,
671
872
  fallbackCode: "ACP_SESSION_INIT_FAILED",
@@ -674,7 +875,7 @@ var AcpxRuntime = class {
674
875
  }) : await this.runWithLaunchLease({
675
876
  sessionKey: input.sessionKey,
676
877
  command: stableLaunchCommand,
677
- enabled: shouldStartWithLease,
878
+ reusableCommand,
678
879
  run: () => this.codexAcpModelOverrideScope.run(codexModelOverride, () => this.withCodexWrapperDiagnostics({
679
880
  command: stableLaunchCommand,
680
881
  fallbackCode: "ACP_SESSION_INIT_FAILED",
@@ -687,9 +888,12 @@ var AcpxRuntime = class {
687
888
  } : handle;
688
889
  }
689
890
  async *runTurn(input) {
690
- const command = await this.resolveCommandForHandle(input.handle);
691
- const delegate = await this.resolveDelegateForHandle(input.handle);
891
+ const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
892
+ const turnLease = await this.prepareProcessLeaseForOperation(input.handle, record);
893
+ let command;
692
894
  try {
895
+ command = (await this.loadOperationSnapshotForHandle(input.handle)).command;
896
+ const delegate = this.resolveDelegateForOperationSnapshot(input.handle, await this.loadOperationSnapshotForHandle(input.handle));
693
897
  for await (const event of delegate.runTurn(withOpenClawManagedTurnTimeout(input))) {
694
898
  if (event.type !== "error" || !isCodexAcpCommand(command) || !isGenericInternalAcpErrorMessage(event.message)) {
695
899
  yield event;
@@ -711,11 +915,17 @@ var AcpxRuntime = class {
711
915
  const stderrTail = await this.readCodexTurnFailureStderr({ handle: input.handle });
712
916
  if (!stderrTail) throw error;
713
917
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
918
+ } finally {
919
+ await this.finalizeProcessLeaseForOperation(input.handle, turnLease);
714
920
  }
715
921
  }
716
922
  startTurn(input) {
717
923
  const readCodexTurnFailureStderr = () => this.readCodexTurnFailureStderr({ handle: input.handle });
718
- const turnPromise = Promise.all([this.resolveCommandForHandle(input.handle), this.resolveDelegateForHandle(input.handle)]).then(async ([command, delegate]) => {
924
+ const snapshotPromise = this.loadOperationSnapshotForHandle(input.handle);
925
+ const turnLeasePromise = snapshotPromise.then(({ record }) => this.prepareProcessLeaseForOperation(input.handle, record));
926
+ const turnPromise = Promise.all([snapshotPromise, turnLeasePromise]).then(async ([snapshot]) => {
927
+ const { command } = snapshot;
928
+ const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
719
929
  try {
720
930
  return {
721
931
  command,
@@ -756,7 +966,7 @@ var AcpxRuntime = class {
756
966
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
757
967
  }
758
968
  } },
759
- result: turnPromise.then(async ({ command, turn }) => {
969
+ result: this.finalizeProcessLeaseAfter(input.handle, turnLeasePromise, turnPromise.then(async ({ command, turn }) => {
760
970
  try {
761
971
  const result = await turn.result;
762
972
  if (result.status !== "failed" || !isCodexAcpCommand(command) || !isGenericInternalAcpErrorMessage(result.error.message)) return result;
@@ -776,7 +986,7 @@ var AcpxRuntime = class {
776
986
  if (!stderrTail) throw error;
777
987
  throw new AcpRuntimeError("ACP_TURN_FAILED", `Internal error: ${stderrTail}`, { cause: error });
778
988
  }
779
- }),
989
+ })),
780
990
  cancel(inputArgs) {
781
991
  return turnPromise.then(({ turn }) => turn.cancel(inputArgs));
782
992
  },
@@ -789,14 +999,20 @@ var AcpxRuntime = class {
789
999
  return this.delegate.getCapabilities(input);
790
1000
  }
791
1001
  async getStatus(input) {
792
- return (await this.resolveDelegateForHandle(input.handle)).getStatus(input);
1002
+ const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1003
+ return this.resolveDelegateForOperationSnapshot(input.handle, snapshot).getStatus(input);
793
1004
  }
794
1005
  async setMode(input) {
795
- await (await this.resolveDelegateForHandle(input.handle)).setMode(input);
1006
+ const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1007
+ await this.runWithProcessLeaseForHandle(input.handle, snapshot.record, () => this.resolveDelegateForOperationSnapshot(input.handle, snapshot).setMode(input));
796
1008
  }
797
1009
  async setConfigOption(input) {
798
- const delegate = await this.resolveDelegateForHandle(input.handle);
799
- const command = await this.resolveCommandForHandle(input.handle);
1010
+ const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1011
+ await this.runWithProcessLeaseForHandle(input.handle, snapshot.record, () => this.setConfigOptionUnlocked(input, snapshot));
1012
+ }
1013
+ async setConfigOptionUnlocked(input, snapshot) {
1014
+ const { command } = snapshot;
1015
+ const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
800
1016
  const key = input.key.trim().toLowerCase();
801
1017
  const isCodexAcp = isCodexAcpCommand(command);
802
1018
  if (WIRE_TIMEOUT_CONFIG_KEYS.has(key) && (isCodexAcp || isClaudeAcpCommand(command))) return;
@@ -839,38 +1055,44 @@ var AcpxRuntime = class {
839
1055
  await delegate.setConfigOption(input);
840
1056
  }
841
1057
  async cancel(input) {
842
- const record = await this.sessionStore.load(input.handle.acpxRecordId ?? input.handle.sessionKey);
843
- await this.resolveDelegateForLoadedRecord(input.handle, record).cancel(input);
1058
+ const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1059
+ await this.resolveDelegateForOperationSnapshot(input.handle, snapshot).cancel(input);
844
1060
  }
845
1061
  async prepareFreshSession(input) {
846
1062
  this.sessionStore.markFresh(input.sessionKey);
847
1063
  }
848
1064
  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);
1065
+ const snapshot = await this.loadOperationSnapshotForHandle(input.handle);
1066
+ const closeLease = await this.prepareProcessLeaseForOperation(input.handle, snapshot.record);
1067
+ let cleanupSucceeded = false;
852
1068
  try {
853
- await delegate.close({
854
- handle: input.handle,
855
- reason: input.reason,
856
- discardPersistentState: input.discardPersistentState
857
- });
858
- closeSucceeded = true;
1069
+ const delegate = this.resolveDelegateForOperationSnapshot(input.handle, snapshot);
1070
+ let closeSucceeded;
1071
+ try {
1072
+ await delegate.close({
1073
+ handle: input.handle,
1074
+ reason: input.reason,
1075
+ discardPersistentState: input.discardPersistentState
1076
+ });
1077
+ closeSucceeded = true;
1078
+ } finally {
1079
+ await this.cleanupProcessTreeForRecord(input.handle, snapshot.record);
1080
+ cleanupSucceeded = true;
1081
+ }
1082
+ if (closeSucceeded) this.releaseManagedToolsDelegateForSession(input.handle.sessionKey);
1083
+ if (closeSucceeded && input.discardPersistentState) this.sessionStore.markFresh(input.handle.sessionKey);
859
1084
  } finally {
860
- await this.cleanupProcessTreeForRecord(input.handle, record);
1085
+ if (cleanupSucceeded) await this.finalizeProcessLeaseForOperation(input.handle, closeLease);
1086
+ else await this.releaseProcessLeaseAfterUncertainFailure(closeLease);
861
1087
  }
862
- if (closeSucceeded) this.releaseManagedToolsDelegateForSession(input.handle.sessionKey);
863
- if (closeSucceeded && input.discardPersistentState) this.sessionStore.markFresh(input.handle.sessionKey);
864
1088
  }
865
1089
  };
866
1090
  /** Test-only hooks for ACPX runtime behavior that is otherwise private. */
867
1091
  const testing = {
868
1092
  appendCodexAcpConfigOverrides,
869
- assertSupportedRuntimeSessionMode,
870
- classifyCodexAcpModelRequest,
871
1093
  isClaudeAcpCommand,
872
1094
  isCodexAcpCommand,
873
- normalizeClaudeAcpModelOverride
1095
+ normalizeAgentCommand
874
1096
  };
875
1097
  //#endregion
876
- export { ACPX_BACKEND_ID, AcpxRuntime, testing as __testing, testing, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState };
1098
+ export { ACPX_BACKEND_ID, AcpxRuntime, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, testing };