@openclaw/acpx 2026.7.1-beta.6 → 2026.7.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,12 @@
1
+ import { t as AcpxPluginConfigSchema } from "./config-schema-lrk5nlcV.js";
1
2
  import "./process-lease-DiKkFj6F.js";
2
3
  import { createRequire } from "node:module";
3
4
  import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
4
- import path from "node:path";
5
5
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
6
- import { execFile } from "node:child_process";
7
- import { promisify } from "node:util";
8
6
  import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { runExec } from "openclaw/plugin-sdk/process-runtime";
9
9
  import { fileURLToPath } from "node:url";
10
- import { z } from "zod";
11
10
  //#region extensions/acpx/src/command-line.ts
12
11
  /**
13
12
  * Small shell-command helpers for ACPX-launched processes. Splitting supports
@@ -56,42 +55,6 @@ function splitCommandParts(value) {
56
55
  return parts;
57
56
  }
58
57
  //#endregion
59
- //#region extensions/acpx/src/config-schema.ts
60
- /**
61
- * ACPX plugin configuration schema and public config types. Runtime setup uses
62
- * this file as the single source of truth for validation and defaulting.
63
- */
64
- const ACPX_PERMISSION_MODES = [
65
- "approve-all",
66
- "approve-reads",
67
- "deny-all"
68
- ];
69
- const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"];
70
- const nonEmptyTrimmedString = (message) => z.string({ error: message }).trim().min(1, { error: message });
71
- const McpServerConfigSchema = z.object({
72
- command: nonEmptyTrimmedString("command must be a non-empty string").describe("Command to run the MCP server"),
73
- args: z.array(z.string({ error: "args must be an array of strings" }), { error: "args must be an array of strings" }).optional().describe("Arguments to pass to the command"),
74
- env: z.record(z.string(), z.string({ error: "env values must be strings" }), { error: "env must be an object of strings" }).optional().describe("Environment variables for the MCP server")
75
- });
76
- /** Zod schema for validating raw ACPX plugin config from OpenClaw config. */
77
- const AcpxPluginConfigSchema = z.strictObject({
78
- cwd: nonEmptyTrimmedString("cwd must be a non-empty string").optional(),
79
- stateDir: nonEmptyTrimmedString("stateDir must be a non-empty string").optional(),
80
- probeAgent: nonEmptyTrimmedString("probeAgent must be a non-empty string").optional(),
81
- permissionMode: z.enum(ACPX_PERMISSION_MODES, { error: `permissionMode must be one of: ${ACPX_PERMISSION_MODES.join(", ")}` }).optional(),
82
- nonInteractivePermissions: z.enum(ACPX_NON_INTERACTIVE_POLICIES, { error: `nonInteractivePermissions must be one of: ${ACPX_NON_INTERACTIVE_POLICIES.join(", ")}` }).optional(),
83
- pluginToolsMcpBridge: z.boolean({ error: "pluginToolsMcpBridge must be a boolean" }).optional(),
84
- openClawToolsMcpBridge: z.boolean({ error: "openClawToolsMcpBridge must be a boolean" }).optional(),
85
- strictWindowsCmdWrapper: z.boolean({ error: "strictWindowsCmdWrapper must be a boolean" }).optional(),
86
- timeoutSeconds: z.number({ error: "timeoutSeconds must be a number >= 0.001" }).min(.001, { error: "timeoutSeconds must be a number >= 0.001" }).default(120),
87
- queueOwnerTtlSeconds: z.number({ error: "queueOwnerTtlSeconds must be a number >= 0" }).min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" }).optional(),
88
- mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
89
- agents: z.record(z.string(), z.strictObject({
90
- command: nonEmptyTrimmedString("agents.<id>.command must be a non-empty string"),
91
- args: z.array(z.string({ error: "args must be an array of strings" })).optional()
92
- })).optional()
93
- });
94
- //#endregion
95
58
  //#region extensions/acpx/src/config.ts
96
59
  /**
97
60
  * Resolves ACPX plugin config from raw user configuration. It locates the
@@ -260,7 +223,7 @@ function resolveAcpxPluginConfig(params) {
260
223
  return {
261
224
  cwd,
262
225
  stateDir,
263
- probeAgent: normalizeLowercaseStringOrEmpty(normalized.probeAgent) || void 0,
226
+ probeAgent: normalized.probeAgent,
264
227
  permissionMode: normalized.permissionMode ?? DEFAULT_PERMISSION_MODE,
265
228
  nonInteractivePermissions: normalized.nonInteractivePermissions ?? DEFAULT_NON_INTERACTIVE_POLICY,
266
229
  pluginToolsMcpBridge,
@@ -282,7 +245,6 @@ function resolveAcpxPluginConfig(params) {
282
245
  * ACPX process ownership checks and cleanup. The reaper only terminates
283
246
  * OpenClaw-owned wrapper trees after validating paths, packages, and lease ids.
284
247
  */
285
- const execFileAsync = promisify(execFile);
286
248
  const requireFromHere = createRequire(import.meta.url);
287
249
  const GENERATED_WRAPPER_BASENAMES = /* @__PURE__ */ new Set(["codex-acp-wrapper.mjs", "claude-agent-acp-wrapper.mjs"]);
288
250
  const OPENCLAW_PLUGIN_DEPS_MARKER = "/plugin-runtime-deps/";
@@ -375,11 +337,14 @@ function parseProcessList(stdout) {
375
337
  const processes = [];
376
338
  for (const line of stdout.split(/\r?\n/)) {
377
339
  const match = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<command>.+?)\s*$/.exec(line);
378
- if (!match?.groups) continue;
340
+ const pid = match?.groups?.pid;
341
+ const ppid = match?.groups?.ppid;
342
+ const command = match?.groups?.command;
343
+ if (!pid || !ppid || !command) continue;
379
344
  processes.push({
380
- pid: Number.parseInt(match.groups.pid, 10),
381
- ppid: Number.parseInt(match.groups.ppid, 10),
382
- command: match.groups.command
345
+ pid: Number.parseInt(pid, 10),
346
+ ppid: Number.parseInt(ppid, 10),
347
+ command
383
348
  });
384
349
  }
385
350
  return processes;
@@ -387,7 +352,10 @@ function parseProcessList(stdout) {
387
352
  /** List host processes in the compact shape needed by ACPX cleanup. */
388
353
  async function listPlatformProcesses() {
389
354
  if (process.platform === "win32") return [];
390
- const { stdout } = await execFileAsync("ps", ["-axo", "pid=,ppid=,command="], { maxBuffer: 8 * 1024 * 1024 });
355
+ const { stdout } = await runExec("ps", ["-axo", "pid=,ppid=,command="], {
356
+ logOutput: false,
357
+ maxBuffer: 8 * 1024 * 1024
358
+ });
391
359
  return parseProcessList(stdout);
392
360
  }
393
361
  function collectProcessTree(processes, rootPid) {
@@ -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-CzJr7Koj.js"));
203
+ const loadServiceModule = createLazyRuntimeModule(() => import("./service-Dm8hpy-C.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-Yz704dK5.js";
1
+ import { t as createAcpxRuntimeService } from "./register.runtime-DBqbPx5P.js";
2
2
  export { createAcpxRuntimeService };
@@ -1,13 +1,14 @@
1
1
  import { i as createAcpxProcessLeaseId, o as hashAcpxProcessCommand, u as withAcpxLeaseEnvironment } from "./process-lease-DiKkFj6F.js";
2
2
  import { AcpRuntimeError } from "./runtime-api.js";
3
- import { c as splitCommandParts, n as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-cncoDfwV.js";
3
+ import { c as splitCommandParts, n as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-B29BzBk4.js";
4
+ import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
5
+ import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
6
  import fs from "node:fs/promises";
5
7
  import path, { resolve } from "node:path";
6
- import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
8
  import { AsyncLocalStorage } from "node:async_hooks";
8
9
  import { ACPX_BACKEND_ID, AcpxRuntime as AcpxRuntime$1, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isRequestedModelUnsupportedError } from "acpx/runtime";
9
- import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
10
10
  import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
11
+ import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
11
12
  //#region extensions/acpx/src/runtime.ts
12
13
  /**
13
14
  * OpenClaw ACPX runtime adapter. It wraps the upstream acpx runtime with
@@ -48,7 +49,7 @@ function isGenericInternalAcpError(error) {
48
49
  async function readCodexWrapperStderrTail(params) {
49
50
  if (!params.wrapperRoot || !params.leaseId) return "";
50
51
  try {
51
- return compactDiagnosticText(redactSensitiveText((await fs.readFile(path.join(params.wrapperRoot, codexWrapperStderrLogFileName(params.leaseId)), "utf8")).slice(-6e3)));
52
+ return compactDiagnosticText(redactSensitiveText(sliceUtf16Safe(await fs.readFile(path.join(params.wrapperRoot, codexWrapperStderrLogFileName(params.leaseId)), "utf8"), -6e3)));
52
53
  } catch {
53
54
  return "";
54
55
  }
@@ -213,9 +214,14 @@ function isEnvAssignment(value) {
213
214
  return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value);
214
215
  }
215
216
  function unwrapEnvCommand(parts) {
216
- if (!parts.length || basename(parts[0]) !== "env") return parts;
217
+ const command = parts.at(0);
218
+ if (!command || basename(command) !== "env") return parts;
217
219
  let index = 1;
218
- while (index < parts.length && isEnvAssignment(parts[index])) index += 1;
220
+ while (true) {
221
+ const part = parts.at(index);
222
+ if (!part || !isEnvAssignment(part)) break;
223
+ index += 1;
224
+ }
219
225
  return parts.slice(index);
220
226
  }
221
227
  function matchesExecutableName(value, executableName) {
@@ -362,13 +368,6 @@ function resolveAgentCommand(params) {
362
368
  const resolvedCommand = params.agentRegistry.resolve(normalizedAgentName);
363
369
  return typeof resolvedCommand === "string" ? resolvedCommand.trim() || void 0 : void 0;
364
370
  }
365
- function resolveProbeAgentName(options) {
366
- const { probeAgent } = options;
367
- return normalizeAgentName(typeof probeAgent === "string" ? probeAgent : void 0) ?? "codex";
368
- }
369
- function resolveAgentCommandForName(params) {
370
- return resolveAgentCommand(params);
371
- }
372
371
  function shouldUseBridgeSafeDelegateForCommand(command) {
373
372
  return isOpenClawBridgeCommand(command);
374
373
  }
@@ -430,17 +429,12 @@ var AcpxRuntime = class {
430
429
  ...sharedOptions,
431
430
  mcpServers: []
432
431
  }, this.delegateTestOptions) : this.delegate;
433
- this.probeDelegate = this.openclawToolsMcpBridgeEnabled ? this.bridgeSafeDelegate : this.resolveDelegateForAgent(resolveProbeAgentName(options));
434
- }
435
- resolveDelegateForAgent(agentName) {
436
- const command = resolveAgentCommandForName({
437
- agentName,
432
+ const probeCommand = resolveAgentCommand({
433
+ agentName: normalizeAgentName(options.probeAgent) ?? "codex",
438
434
  agentRegistry: this.agentRegistry
439
435
  });
440
- return this.resolveDelegateForCommand(command);
441
- }
442
- resolveDelegateForCommand(command) {
443
- return shouldUseBridgeSafeDelegateForCommand(command) ? this.bridgeSafeDelegate : this.delegate;
436
+ const useBridgeSafeProbe = this.openclawToolsMcpBridgeEnabled || shouldUseBridgeSafeDelegateForCommand(probeCommand);
437
+ this.probeDelegate = useBridgeSafeProbe ? this.bridgeSafeDelegate : this.delegate;
444
438
  }
445
439
  resolveDelegateForSession(params) {
446
440
  if (shouldUseBridgeSafeDelegateForCommand(params.command)) return this.bridgeSafeDelegate;
@@ -479,7 +473,7 @@ var AcpxRuntime = class {
479
473
  command: recordCommand,
480
474
  sessionKey: handle.sessionKey
481
475
  });
482
- const command = resolveAgentCommandForName({
476
+ const command = resolveAgentCommand({
483
477
  agentName: readAgentFromHandle(handle),
484
478
  agentRegistry: this.agentRegistry
485
479
  });
@@ -491,7 +485,7 @@ var AcpxRuntime = class {
491
485
  async resolveCommandForHandle(handle) {
492
486
  const recordCommand = readAgentCommandFromRecord(await this.sessionStore.load(handle.acpxRecordId ?? handle.sessionKey));
493
487
  if (recordCommand) return recordCommand;
494
- return resolveAgentCommandForName({
488
+ return resolveAgentCommand({
495
489
  agentName: readAgentFromHandle(handle),
496
490
  agentRegistry: this.agentRegistry
497
491
  });
@@ -585,7 +579,7 @@ var AcpxRuntime = class {
585
579
  await this.processLeaseStore?.markState(lease.leaseId, result.terminatedPids.length > 0 || result.skippedReason === "missing-root" ? "closed" : "lost");
586
580
  return;
587
581
  }
588
- const rootCommand = readAgentCommandFromRecord(record) ?? resolveAgentCommandForName({
582
+ const rootCommand = readAgentCommandFromRecord(record) ?? resolveAgentCommand({
589
583
  agentName: readAgentFromHandle(handle),
590
584
  agentRegistry: this.agentRegistry
591
585
  });
@@ -611,7 +605,7 @@ var AcpxRuntime = class {
611
605
  }
612
606
  async ensureSession(input) {
613
607
  assertSupportedRuntimeSessionMode(input.mode);
614
- const command = resolveAgentCommandForName({
608
+ const command = resolveAgentCommand({
615
609
  agentName: input.agent,
616
610
  agentRegistry: this.agentRegistry
617
611
  });
@@ -1,17 +1,19 @@
1
- import { n as createLazyAcpRuntimeProxy } from "./register.runtime-Yz704dK5.js";
1
+ import { n as createLazyAcpRuntimeProxy } from "./register.runtime-DBqbPx5P.js";
2
+ import "./config-schema-lrk5nlcV.js";
2
3
  import { a as createAcpxProcessLeaseStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, h as normalizeAcpxGatewayInstanceRecord, l as openAcpxProcessLeaseStateStore, n as OPENCLAW_ACPX_LEASE_ID_ENV, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, t as OPENCLAW_ACPX_LEASE_ID_ARG } from "./process-lease-DiKkFj6F.js";
3
4
  import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "./runtime-api.js";
4
- import { a as resolveAcpxPluginRoot, c as splitCommandParts, i as resolveAcpxPluginConfig, o as toAcpMcpServers, r as reapStaleOpenClawOwnedAcpxOrphans, s as quoteCommandPart, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-cncoDfwV.js";
5
+ import { a as resolveAcpxPluginRoot, c as splitCommandParts, i as resolveAcpxPluginConfig, o as toAcpMcpServers, r as reapStaleOpenClawOwnedAcpxOrphans, s as quoteCommandPart, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-B29BzBk4.js";
5
6
  import { createRequire } from "node:module";
7
+ import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
6
8
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
7
- import fs from "node:fs/promises";
9
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
10
+ import fs from "node:fs";
11
+ import fs$1 from "node:fs/promises";
8
12
  import path from "node:path";
13
+ import os from "node:os";
9
14
  import { randomUUID } from "node:crypto";
10
- import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
11
15
  import { inspect } from "node:util";
12
- import fs$1 from "node:fs";
13
16
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
14
- import os from "node:os";
15
17
  import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
16
18
  //#region extensions/acpx/src/codex-trust-config.ts
17
19
  /**
@@ -123,9 +125,11 @@ function extractTrustedCodexProjectPaths(configToml) {
123
125
  continue;
124
126
  }
125
127
  const assignment = /^(?<key>"(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_\-/.~:]+)\s*=\s*(?<value>.+)$/.exec(line);
126
- if (!assignment?.groups) continue;
127
- const key = parseTomlString(assignment.groups.key) ?? assignment.groups.key;
128
- const value = assignment.groups.value.trim();
128
+ const rawKey = assignment?.groups?.key;
129
+ const rawValue = assignment?.groups?.value;
130
+ if (!rawKey || rawValue === void 0) continue;
131
+ const key = parseTomlString(rawKey) ?? rawKey;
132
+ const value = rawValue.trim();
129
133
  if (inProjectsTable && /^\{.*\}$/.test(value)) {
130
134
  if (/\btrust_level\s*=\s*["']trusted["']/.test(value) && key) trusted.add(key);
131
135
  continue;
@@ -229,7 +233,7 @@ const RUN_CONFIGURED_COMMAND_SENTINEL = "--openclaw-run-configured";
229
233
  const requireFromHere = createRequire(import.meta.url);
230
234
  function readSelfManifest() {
231
235
  const manifestPath = path.join(resolveAcpxPluginRoot(import.meta.url), "package.json");
232
- return JSON.parse(fs$1.readFileSync(manifestPath, "utf8"));
236
+ return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
233
237
  }
234
238
  function readManifestDependencyVersion(packageName) {
235
239
  const version = readSelfManifest().dependencies?.[packageName];
@@ -254,7 +258,7 @@ async function resolveInstalledAcpPackageBinPath(packageName, binName) {
254
258
  if (manifest.name !== packageName) return;
255
259
  const binPath = resolvePackageBinPath(packageJsonPath, manifest, binName);
256
260
  if (!binPath) return;
257
- await fs.access(binPath);
261
+ await fs$1.access(binPath);
258
262
  return binPath;
259
263
  } catch {
260
264
  return;
@@ -381,6 +385,7 @@ function buildAdapterWrapperScript(params) {
381
385
  import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
382
386
  import path from "node:path";
383
387
  import { spawn } from "node:child_process";
388
+ import { StringDecoder } from "node:string_decoder";
384
389
  import { fileURLToPath } from "node:url";
385
390
 
386
391
  ${params.envSetup}
@@ -431,7 +436,25 @@ function redactDiagnosticText(text) {
431
436
  return redacted;
432
437
  }
433
438
 
439
+ function tailUtf16Safe(text, maxChars) {
440
+ let start = Math.max(0, text.length - maxChars);
441
+ const startsInsideSurrogatePair =
442
+ start > 0 &&
443
+ start < text.length &&
444
+ text.charCodeAt(start) >= 0xdc00 &&
445
+ text.charCodeAt(start) <= 0xdfff &&
446
+ text.charCodeAt(start - 1) >= 0xd800 &&
447
+ text.charCodeAt(start - 1) <= 0xdbff;
448
+ if (startsInsideSurrogatePair) {
449
+ start += 1;
450
+ }
451
+ return text.slice(start);
452
+ }
453
+
434
454
  let pendingStderrLogText = "";
455
+ // Pipe chunks can split a UTF-8 sequence. Preserve decoder state so diagnostic
456
+ // capture does not manufacture replacement characters between chunks.
457
+ const stderrDecoder = new StringDecoder("utf8");
435
458
  const stderrPrivateKeyEndPattern = /-----END [A-Z ]*PRIVATE KEY-----/;
436
459
 
437
460
  function hasUnclosedPrivateKeyBlock(text) {
@@ -456,7 +479,7 @@ function writeRedactedStderrLog(text) {
456
479
  appendFileSync(stderrLogPath, redactDiagnosticText(text), "utf8");
457
480
  const current = readFileSync(stderrLogPath, "utf8");
458
481
  if (current.length > stderrLogMaxChars) {
459
- writeFileSync(stderrLogPath, current.slice(-stderrLogMaxChars), "utf8");
482
+ writeFileSync(stderrLogPath, tailUtf16Safe(current, stderrLogMaxChars), "utf8");
460
483
  }
461
484
  } catch {
462
485
  // Stderr capture is diagnostic-only; never break the ACP adapter.
@@ -475,7 +498,7 @@ function flushFinalizedStderrLogText() {
475
498
  const lastLineBreak = pendingStderrLogText.lastIndexOf("\\n");
476
499
  if (lastLineBreak === -1) {
477
500
  if (pendingStderrLogText.length > stderrLogMaxChars) {
478
- pendingStderrLogText = pendingStderrLogText.slice(-stderrLogMaxChars);
501
+ pendingStderrLogText = tailUtf16Safe(pendingStderrLogText, stderrLogMaxChars);
479
502
  }
480
503
  return;
481
504
  }
@@ -488,7 +511,7 @@ function flushFinalizedStderrLogText() {
488
511
  }
489
512
  if (flushEnd <= 0) {
490
513
  if (pendingStderrLogText.length > stderrLogMaxChars) {
491
- pendingStderrLogText = pendingStderrLogText.slice(-stderrLogMaxChars);
514
+ pendingStderrLogText = tailUtf16Safe(pendingStderrLogText, stderrLogMaxChars);
492
515
  }
493
516
  return;
494
517
  }
@@ -498,7 +521,7 @@ function flushFinalizedStderrLogText() {
498
521
  }
499
522
 
500
523
  function appendStderrLog(chunk) {
501
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
524
+ const text = stderrDecoder.write(chunk);
502
525
  if (!text) {
503
526
  return;
504
527
  }
@@ -507,6 +530,7 @@ function appendStderrLog(chunk) {
507
530
  }
508
531
 
509
532
  function finishStderrLog() {
533
+ pendingStderrLogText += stderrDecoder.end();
510
534
  const text = redactIncompletePrivateKeyTail(pendingStderrLogText);
511
535
  pendingStderrLogText = "";
512
536
  writeRedactedStderrLog(text);
@@ -731,7 +755,7 @@ function buildClaudeAcpWrapperScript(installedBinPath) {
731
755
  }
732
756
  async function readSourceCodexConfig(codexHome) {
733
757
  try {
734
- return await fs.readFile(path.join(codexHome, "config.toml"), "utf8");
758
+ return await fs$1.readFile(path.join(codexHome, "config.toml"), "utf8");
735
759
  } catch (error) {
736
760
  if (error.code === "ENOENT") return;
737
761
  throw error;
@@ -741,8 +765,8 @@ async function prepareIsolatedCodexHome(params) {
741
765
  const sourceConfig = await readSourceCodexConfig(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"));
742
766
  const trustedProjectPaths = [...sourceConfig ? extractTrustedCodexProjectPaths(sourceConfig) : [], params.workspaceDir];
743
767
  const codexHome = path.join(params.baseDir, "codex-home");
744
- await fs.mkdir(codexHome, { recursive: true });
745
- await fs.writeFile(path.join(codexHome, "config.toml"), renderIsolatedCodexConfig({
768
+ await fs$1.mkdir(codexHome, { recursive: true });
769
+ await fs$1.writeFile(path.join(codexHome, "config.toml"), renderIsolatedCodexConfig({
746
770
  sourceConfigToml: sourceConfig,
747
771
  projectPaths: trustedProjectPaths
748
772
  }), "utf8");
@@ -750,20 +774,20 @@ async function prepareIsolatedCodexHome(params) {
750
774
  }
751
775
  async function makeGeneratedWrapperExecutableIfPossible(wrapperPath) {
752
776
  try {
753
- await fs.chmod(wrapperPath, 493);
777
+ await fs$1.chmod(wrapperPath, 493);
754
778
  } catch {}
755
779
  }
756
780
  async function writeCodexAcpWrapper(baseDir, installedBinPath) {
757
- await fs.mkdir(baseDir, { recursive: true });
781
+ await fs$1.mkdir(baseDir, { recursive: true });
758
782
  const wrapperPath = path.join(baseDir, "codex-acp-wrapper.mjs");
759
- await fs.writeFile(wrapperPath, buildCodexAcpWrapperScript(installedBinPath), { encoding: "utf8" });
783
+ await fs$1.writeFile(wrapperPath, buildCodexAcpWrapperScript(installedBinPath), { encoding: "utf8" });
760
784
  await makeGeneratedWrapperExecutableIfPossible(wrapperPath);
761
785
  return wrapperPath;
762
786
  }
763
787
  async function writeClaudeAcpWrapper(baseDir, installedBinPath) {
764
- await fs.mkdir(baseDir, { recursive: true });
788
+ await fs$1.mkdir(baseDir, { recursive: true });
765
789
  const wrapperPath = path.join(baseDir, "claude-agent-acp-wrapper.mjs");
766
- await fs.writeFile(wrapperPath, buildClaudeAcpWrapperScript(installedBinPath), { encoding: "utf8" });
790
+ await fs$1.writeFile(wrapperPath, buildClaudeAcpWrapperScript(installedBinPath), { encoding: "utf8" });
767
791
  await makeGeneratedWrapperExecutableIfPossible(wrapperPath);
768
792
  return wrapperPath;
769
793
  }
@@ -852,7 +876,7 @@ async function prepareAcpxCodexAuthConfig(params) {
852
876
  const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
853
877
  const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
854
878
  const ACPX_BACKEND_ID = "acpx";
855
- const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-WjNm2DKO.js"));
879
+ const loadRuntimeModule = createLazyRuntimeModule(() => import("./runtime-D_fW1HnP.js"));
856
880
  /** Convert ACPX timeout seconds into timer-safe milliseconds. */
857
881
  function resolveAcpxTimerTimeoutMs(timeoutSeconds) {
858
882
  if (timeoutSeconds === void 0) return;
@@ -924,13 +948,9 @@ function formatDoctorFailureMessage(report) {
924
948
  const detailText = report.details?.map(formatDoctorDetail).filter(Boolean).join("; ").trim();
925
949
  return detailText ? `${report.message} (${detailText})` : report.message;
926
950
  }
927
- function normalizeProbeAgent(value) {
928
- const normalized = value?.trim().toLowerCase();
929
- return normalized ? normalized : void 0;
930
- }
931
951
  function resolveAllowedAgentsProbeAgent(ctx) {
932
952
  for (const agent of ctx.config.acp?.allowedAgents ?? []) {
933
- const normalized = normalizeProbeAgent(agent);
953
+ const normalized = normalizeLowercaseStringOrEmpty(agent);
934
954
  if (normalized) return normalized;
935
955
  }
936
956
  }
@@ -1043,8 +1063,8 @@ function createAcpxRuntimeService(params = {}) {
1043
1063
  }));
1044
1064
  const wrapperRoot = path.join(ctx.stateDir, "acpx");
1045
1065
  await measureAcpxStartup(ctx, "filesystem.prepare", async () => {
1046
- await fs.mkdir(pluginConfig.stateDir, { recursive: true });
1047
- await fs.mkdir(wrapperRoot, { recursive: true });
1066
+ await fs$1.mkdir(pluginConfig.stateDir, { recursive: true });
1067
+ await fs$1.mkdir(wrapperRoot, { recursive: true });
1048
1068
  });
1049
1069
  const gatewayInstanceId = await measureAcpxStartup(ctx, "gateway-instance-id", () => resolveGatewayInstanceId(openKeyedStore));
1050
1070
  const processLeaseStore = createAcpxProcessLeaseStore({ store: openAcpxProcessLeaseStateStore(openKeyedStore) });
package/dist/setup-api.js CHANGED
@@ -1,5 +1,5 @@
1
- import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
1
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
3
  //#region extensions/acpx/setup-api.ts
4
4
  /**
5
5
  * ACPX setup plugin entry. It auto-enables setup when ACP config already points
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/acpx",
3
- "version": "2026.7.1-beta.6",
3
+ "version": "2026.7.2-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/acpx",
9
- "version": "2026.7.1-beta.6",
9
+ "version": "2026.7.2-beta.1",
10
10
  "dependencies": {
11
11
  "@agentclientprotocol/claude-agent-acp": "0.55.0",
12
12
  "@zed-industries/codex-acp": "0.16.0",
@@ -49,9 +49,15 @@
49
49
  "type": "number",
50
50
  "minimum": 0
51
51
  },
52
- "probeAgent": {
53
- "type": "string",
54
- "minLength": 1
52
+ "piSessionCatalog": {
53
+ "type": "object",
54
+ "additionalProperties": false,
55
+ "properties": {
56
+ "enabled": {
57
+ "type": "boolean",
58
+ "default": true
59
+ }
60
+ }
55
61
  },
56
62
  "mcpServers": {
57
63
  "type": "object",
@@ -138,6 +144,10 @@
138
144
  "help": "Reserved compatibility field for the older embedded ACPX queue-owner path. Accepted for compatibility and logged as ignored.",
139
145
  "advanced": true
140
146
  },
147
+ "piSessionCatalog.enabled": {
148
+ "label": "Pi Session Catalog",
149
+ "help": "Auto-detect Pi sessions on the Gateway and paired nodes, then show them in the sessions sidebar."
150
+ },
141
151
  "probeAgent": {
142
152
  "label": "Health Probe Agent",
143
153
  "help": "Agent id used for the embedded ACP runtime health probe. Defaults to the first `acp.allowedAgents` entry when that allowlist is set, otherwise to the runtime built-in probe agent (codex). Set this explicitly (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/acpx",
3
- "version": "2026.7.1-beta.6",
3
+ "version": "2026.7.2-beta.1",
4
4
  "description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,10 +26,10 @@
26
26
  "minHostVersion": ">=2026.4.25"
27
27
  },
28
28
  "compat": {
29
- "pluginApi": ">=2026.7.1-beta.6"
29
+ "pluginApi": ">=2026.7.2-beta.1"
30
30
  },
31
31
  "build": {
32
- "openclawVersion": "2026.7.1-beta.6",
32
+ "openclawVersion": "2026.7.2-beta.1",
33
33
  "staticAssets": [
34
34
  {
35
35
  "source": "./src/runtime-internals/mcp-proxy.mjs",
@@ -58,7 +58,7 @@
58
58
  "skills/**"
59
59
  ],
60
60
  "peerDependencies": {
61
- "openclaw": ">=2026.7.1-beta.6"
61
+ "openclaw": ">=2026.7.2-beta.1"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "openclaw": {