@openclaw/acpx 2026.9.8 → 2026.10.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,9 +1,125 @@
1
- import { a as hashAcpxProcessCommand, c as openAcpxProcessLeaseStateStore, g as runtime_doctor_migrations_exports, l as readAcpxProcessLeaseIdentity, o as normalizeAcpxProcessLease } from "./process-lease-n38HFGdp.mjs";
2
- import { t as resolveAcpxPluginConfig } from "./config-Blz15uwJ.mjs";
1
+ import { t as runtime_doctor_migrations_exports } from "./runtime-doctor-migrations-hxP-CXX7.mjs";
2
+ import { c as renderAgentCommand, t as resolveAcpxPluginConfig, u as splitCommandParts } from "./config-76krYh6C.mjs";
3
3
  import path from "node:path";
4
+ import { asFiniteNumber, asOptionalObjectRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { createHash } from "node:crypto";
4
6
  import fs from "node:fs/promises";
5
7
  import { isDeepStrictEqual } from "node:util";
6
8
  import { movePathWithCopyFallback } from "openclaw/plugin-sdk/security-runtime";
9
+ //#region extensions/acpx/src/state.ts
10
+ const ACPX_PROCESS_LEASE_NAMESPACE = "process-leases";
11
+ const ACPX_PROCESS_LEASE_MAX_ENTRIES = 4096;
12
+ const ACPX_GATEWAY_INSTANCE_NAMESPACE = "gateway-instance";
13
+ const ACPX_GATEWAY_INSTANCE_KEY = "current";
14
+ function normalizeAcpxGatewayInstanceRecord(value) {
15
+ const record = asOptionalObjectRecord(value);
16
+ const instanceId = normalizeOptionalString(record?.instanceId);
17
+ if (!instanceId) return;
18
+ return {
19
+ instanceId,
20
+ createdAt: Math.trunc(asFiniteNumber(record?.createdAt) ?? 0)
21
+ };
22
+ }
23
+ //#endregion
24
+ //#region extensions/acpx/src/process-lease.ts
25
+ const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
26
+ const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
27
+ /** Synthetic session identity for generated-wrapper health probes. */
28
+ const ACPX_PROBE_LEASE_SESSION_KEY = "openclaw:acpx:probe";
29
+ function readAcpxProcessLeaseIdentity(command) {
30
+ const parts = typeof command === "string" ? Array.from(command.matchAll(/(?:^|\s)(--openclaw-(?:acpx-lease-id|gateway-instance-id))\s+(?:"([^"]*)"|'([^']*)'|(\S+))(?=\s|$)/g)).flatMap((match) => [match[1], match[2] ?? match[3] ?? match[4]]) : command ?? [];
31
+ const leaseIndex = parts.lastIndexOf(OPENCLAW_ACPX_LEASE_ID_ARG);
32
+ const gatewayIndex = parts.lastIndexOf(OPENCLAW_GATEWAY_INSTANCE_ID_ARG);
33
+ const leaseId = leaseIndex >= 0 ? parts[leaseIndex + 1]?.trim() : "";
34
+ const gatewayInstanceId = gatewayIndex >= 0 ? parts[gatewayIndex + 1]?.trim() : "";
35
+ if (!leaseId || !gatewayInstanceId) return;
36
+ return {
37
+ leaseId,
38
+ gatewayInstanceId
39
+ };
40
+ }
41
+ function normalizeAcpxProcessLease(value) {
42
+ const record = asOptionalObjectRecord(value);
43
+ if (!record || typeof record.leaseId !== "string" || typeof record.gatewayInstanceId !== "string" || typeof record.sessionKey !== "string" || typeof record.wrapperRoot !== "string" || typeof record.wrapperPath !== "string" || typeof record.rootPid !== "number" || typeof record.commandHash !== "string" || typeof record.startedAt !== "number" || ![
44
+ "open",
45
+ "closing",
46
+ "closed",
47
+ "lost"
48
+ ].includes(String(record.state))) return;
49
+ return {
50
+ leaseId: record.leaseId,
51
+ gatewayInstanceId: record.gatewayInstanceId,
52
+ sessionKey: record.sessionKey,
53
+ wrapperRoot: record.wrapperRoot,
54
+ wrapperPath: record.wrapperPath,
55
+ rootPid: record.rootPid,
56
+ ...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
57
+ commandHash: record.commandHash,
58
+ startedAt: record.startedAt,
59
+ state: record.state
60
+ };
61
+ }
62
+ function openAcpxProcessLeaseStateStore(openKeyedStore) {
63
+ return openKeyedStore({
64
+ namespace: ACPX_PROCESS_LEASE_NAMESPACE,
65
+ maxEntries: ACPX_PROCESS_LEASE_MAX_ENTRIES,
66
+ overflowPolicy: "reject-new"
67
+ });
68
+ }
69
+ function createAcpxProcessLeaseStore(params) {
70
+ let updateQueue = Promise.resolve();
71
+ async function update(mutator) {
72
+ const run = updateQueue.then(mutator);
73
+ updateQueue = run.catch(() => {});
74
+ await run;
75
+ }
76
+ async function readCurrent() {
77
+ await updateQueue;
78
+ return (await params.store.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => Boolean(lease));
79
+ }
80
+ return {
81
+ async load(leaseId) {
82
+ await updateQueue;
83
+ return normalizeAcpxProcessLease(await params.store.lookup(leaseId));
84
+ },
85
+ async listOpen(gatewayInstanceId) {
86
+ return (await readCurrent()).filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
87
+ },
88
+ async save(lease) {
89
+ await update(async () => {
90
+ await params.store.register(lease.leaseId, lease);
91
+ });
92
+ },
93
+ async markState(leaseId, state) {
94
+ await update(async () => {
95
+ if (state === "closed" || state === "lost") {
96
+ await params.store.delete(leaseId);
97
+ return;
98
+ }
99
+ const lease = normalizeAcpxProcessLease(await params.store.lookup(leaseId));
100
+ if (lease) await params.store.register(leaseId, {
101
+ ...lease,
102
+ state
103
+ });
104
+ });
105
+ }
106
+ };
107
+ }
108
+ /** Hash a wrapper command so process leases can detect command drift. */
109
+ function hashAcpxProcessCommand(command) {
110
+ return createHash("sha256").update(renderAgentCommand(command)).digest("hex");
111
+ }
112
+ /** Append portable wrapper arguments without changing the executable or argument bytes. */
113
+ function withAcpxLeaseArgs(params) {
114
+ return [
115
+ ...splitCommandParts(params.command),
116
+ OPENCLAW_ACPX_LEASE_ID_ARG,
117
+ params.leaseId,
118
+ OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
119
+ params.gatewayInstanceId
120
+ ];
121
+ }
122
+ //#endregion
7
123
  //#region extensions/acpx/src/session-owner-migration.ts
8
124
  function openStateDirectoryMarker(input) {
9
125
  return input.openKeyedStore({
@@ -114,7 +230,7 @@ async function legacyRecords(input, directory = sessionDirectory(input)) {
114
230
  directory,
115
231
  ids
116
232
  };
117
- const { resolveAcpxSessionResource } = await import("./session-resource-B-4yPr-j.mjs").then((n) => n.n);
233
+ const { resolveAcpxSessionResource } = await import("./session-resource-DKJsUJPu.mjs").then((n) => n.n);
118
234
  const evidence = await input.context.inspectAcpSessionClaims?.();
119
235
  const { decodeAcpxRuntimeHandleState } = await import("acpx/runtime");
120
236
  return {
@@ -157,7 +273,7 @@ async function migrateRecord(input, directory, oldId, claims, changes, warnings)
157
273
  const raw = (0, runtime_doctor_migrations_exports.asObjectRecord)(JSON.parse(sourceBytes));
158
274
  if (!raw || raw.acpx_record_id !== oldId || typeof raw.name !== "string" || !raw.name.trim() || raw.name !== oldId && !oldId.startsWith(`${raw.name}:oneshot:`)) throw new Error("record ID/name is not a recognized ACPX locator");
159
275
  requireStoppedPid(raw.pid);
160
- const { resolveAcpxSessionResource } = await import("./session-resource-B-4yPr-j.mjs").then((n) => n.n);
276
+ const { resolveAcpxSessionResource } = await import("./session-resource-DKJsUJPu.mjs").then((n) => n.n);
161
277
  const { createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState } = await import("acpx/runtime");
162
278
  const candidates = claims.filter((claim) => matchesClaimRecord(claim, raw, oldId, resolveAcpxSessionResource(claim), decodeAcpxRuntimeHandleState));
163
279
  if (candidates.length !== 1) throw new Error("exactly one current canonical owner claim is required");
@@ -312,4 +428,4 @@ const acpxSessionOwnerMigration = {
312
428
  }
313
429
  };
314
430
  //#endregion
315
- export { acpxSessionOwnerMigration, adoptAcpxStateDirectory };
431
+ export { hashAcpxProcessCommand as a, acpxSessionOwnerMigration, adoptAcpxStateDirectory, withAcpxLeaseArgs as c, normalizeAcpxGatewayInstanceRecord as d, createAcpxProcessLeaseStore as i, ACPX_GATEWAY_INSTANCE_KEY as l, OPENCLAW_ACPX_LEASE_ID_ARG as n, openAcpxProcessLeaseStateStore as o, OPENCLAW_GATEWAY_INSTANCE_ID_ARG as r, readAcpxProcessLeaseIdentity as s, ACPX_PROBE_LEASE_SESSION_KEY as t, ACPX_GATEWAY_INSTANCE_NAMESPACE as u };
@@ -1,4 +1,4 @@
1
- import { _ as __exportAll } from "./process-lease-n38HFGdp.mjs";
1
+ import { n as __exportAll } from "./runtime-doctor-migrations-hxP-CXX7.mjs";
2
2
  import { AcpRuntimeError } from "../runtime-api.js";
3
3
  import { createHash } from "node:crypto";
4
4
  import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
@@ -1,6 +1,4 @@
1
- import { c as openAcpxProcessLeaseStateStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, g as runtime_doctor_migrations_exports, h as normalizeAcpxGatewayInstanceRecord, m as ACPX_LEGACY_PROCESS_LEASE_FILE, o as normalizeAcpxProcessLease, p as ACPX_LEGACY_GATEWAY_INSTANCE_FILE, s as normalizeAcpxProcessLeaseFile } from "./.setup/process-lease-n38HFGdp.mjs";
2
- import path from "node:path";
3
- import fs from "node:fs/promises";
1
+ import { t as runtime_doctor_migrations_exports } from "./.setup/runtime-doctor-migrations-hxP-CXX7.mjs";
4
2
  //#region extensions/acpx/doctor-contract-api.ts
5
3
  const ACPX_CONFIG_PATH = [
6
4
  "plugins",
@@ -36,107 +34,19 @@ function normalizeCompatibilityConfig({ cfg }) {
36
34
  changes: [`Removed retired ACPX plugin config: ${retiredKeys.map((key) => [...ACPX_CONFIG_PATH, key].join(".")).join(", ")}.`]
37
35
  };
38
36
  }
39
- function resolveLegacyGatewayInstancePath(stateDir) {
40
- return path.join(stateDir, ACPX_LEGACY_GATEWAY_INSTANCE_FILE);
41
- }
42
- function resolveLegacyProcessLeasePath(stateDir) {
43
- return path.join(stateDir, "acpx", ACPX_LEGACY_PROCESS_LEASE_FILE);
44
- }
45
- async function readLegacyGatewayInstanceId(filePath) {
46
- try {
47
- return (await fs.readFile(filePath, "utf8")).trim() || null;
48
- } catch {
49
- return null;
50
- }
51
- }
52
- async function readLegacyOpenProcessLeases(filePath) {
53
- try {
54
- return normalizeAcpxProcessLeaseFile(JSON.parse(await fs.readFile(filePath, "utf8"))).leases.filter((lease) => lease.state === "open" || lease.state === "closing");
55
- } catch {
56
- return [];
57
- }
58
- }
59
37
  const stateMigrations = [{
60
- id: "acpx-runtime-state-to-plugin-state",
61
- label: "ACPX runtime state",
62
- async detectLegacyState(params) {
63
- const gatewayInstanceId = await readLegacyGatewayInstanceId(resolveLegacyGatewayInstancePath(params.stateDir));
64
- const openLeases = await readLegacyOpenProcessLeases(resolveLegacyProcessLeasePath(params.stateDir));
65
- if (!gatewayInstanceId && openLeases.length === 0) return null;
66
- const preview = [];
67
- if (gatewayInstanceId) preview.push(`- ACPX gateway instance id: ${resolveLegacyGatewayInstancePath(params.stateDir)} -> plugin state (${ACPX_GATEWAY_INSTANCE_NAMESPACE})`);
68
- if (openLeases.length > 0) preview.push(`- ACPX process leases: ${resolveLegacyProcessLeasePath(params.stateDir)} -> plugin state (${openLeases.length} open lease(s))`);
69
- return { preview };
70
- },
71
- async migrateLegacyState(params) {
72
- const changes = [];
73
- const warnings = [];
74
- const gatewayInstancePath = resolveLegacyGatewayInstancePath(params.stateDir);
75
- const gatewayInstanceId = await readLegacyGatewayInstanceId(gatewayInstancePath);
76
- const processLeasePath = resolveLegacyProcessLeasePath(params.stateDir);
77
- const openLeases = await readLegacyOpenProcessLeases(processLeasePath);
78
- const processLeaseStore = openAcpxProcessLeaseStateStore(params.context.openPluginStateKeyedStore);
79
- const gatewayStore = params.context.openPluginStateKeyedStore({
80
- namespace: ACPX_GATEWAY_INSTANCE_NAMESPACE,
81
- maxEntries: 1
82
- });
83
- const existingGateway = normalizeAcpxGatewayInstanceRecord(await gatewayStore.lookup(ACPX_GATEWAY_INSTANCE_KEY));
84
- const existingLiveLeases = (await processLeaseStore.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => lease != null && (lease.state === "open" || lease.state === "closing"));
85
- const leaseGatewayIds = new Set(openLeases.map((lease) => lease.gatewayInstanceId));
86
- const onlyLeaseGatewayId = leaseGatewayIds.size === 1 ? [...leaseGatewayIds][0] : null;
87
- const canAdoptLegacyGateway = existingGateway && gatewayInstanceId && existingGateway.instanceId !== gatewayInstanceId && onlyLeaseGatewayId === gatewayInstanceId && existingLiveLeases.length === 0;
88
- const canonicalGatewayInstanceId = canAdoptLegacyGateway || !existingGateway ? gatewayInstanceId ?? onlyLeaseGatewayId : existingGateway.instanceId;
89
- if (openLeases.length > 0 && (!canonicalGatewayInstanceId || [...leaseGatewayIds].some((leaseGatewayId) => leaseGatewayId !== canonicalGatewayInstanceId))) {
90
- warnings.push("Skipped ACPX process lease migration because legacy leases do not match the canonical gateway instance id; left legacy sources in place for manual cleanup");
91
- return {
92
- changes,
93
- warnings
94
- };
95
- }
96
- if (canonicalGatewayInstanceId && (canAdoptLegacyGateway || !existingGateway)) {
97
- await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
98
- instanceId: canonicalGatewayInstanceId,
99
- createdAt: Date.now()
100
- });
101
- changes.push("Migrated ACPX gateway instance id -> plugin state");
102
- } else if (gatewayInstanceId && existingGateway?.instanceId !== gatewayInstanceId) warnings.push("Skipped ACPX gateway instance id import because plugin state already differs");
103
- if (gatewayInstanceId) await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
104
- filePath: gatewayInstancePath,
105
- label: "ACPX gateway-instance-id",
106
- changes,
107
- warnings
108
- });
109
- if (openLeases.length > 0) {
110
- let imported = 0;
111
- let alreadyPresent = 0;
112
- for (const lease of openLeases) if (await processLeaseStore.registerIfAbsent(lease.leaseId, lease)) imported++;
113
- else alreadyPresent++;
114
- changes.push(`Migrated ACPX process leases -> plugin state (${imported} imported, ${alreadyPresent} already present)`);
115
- await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
116
- filePath: processLeasePath,
117
- label: "ACPX process-leases",
118
- changes,
119
- warnings
120
- });
121
- }
122
- return {
123
- changes,
124
- warnings
125
- };
126
- }
127
- }, {
128
38
  id: "acpx-session-owner-resources",
129
39
  label: "ACP session owners",
130
40
  doctorOnly: true,
131
41
  phase: "after-session-repair",
132
42
  async collectBackupResources(input) {
133
- return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.collectBackupResources(input);
43
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.collectBackupResources(input);
134
44
  },
135
45
  async detectLegacyState(input) {
136
- return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
46
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
137
47
  },
138
48
  async migrateLegacyState(input) {
139
- return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
49
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
140
50
  }
141
51
  }];
142
52
  //#endregion
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
- import { t as createAcpxRuntimeService } from "./.setup/register.runtime-DByczkH_.mjs";
2
- import { a as ACPX_NATIVE_AGENT_IDS, o as AcpxNativeAgentsSchema, t as resolveAcpxPluginConfig } from "./.setup/config-Blz15uwJ.mjs";
1
+ import { t as createAcpxRuntimeService } from "./.setup/register.runtime-C0nVOTzT.mjs";
2
+ import { g as AcpxNativeAgentsSchema, h as ACPX_NATIVE_AGENT_IDS, t as resolveAcpxPluginConfig } from "./.setup/config-76krYh6C.mjs";
3
3
  import { a as PI_SESSIONS_CAPABILITY, c as PI_SESSION_READ_COMMAND, l as PI_TERMINAL_RESUME_COMMAND, o as PI_SESSIONS_LIST_COMMAND, r as piSessionStoreAvailable, s as PI_SESSION_ID_PATTERN } from "./.setup/pi-session-paths-CIvyk6KB.mjs";
4
4
  import { createAgentRegistry } from "acpx/agent-registry";
5
5
  import { tryDispatchAcpReplyHook } from "openclaw/plugin-sdk/acp-runtime-backend";
6
6
  import { createLazyRuntimeModule, createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime";
7
7
  import { inspectAgentModels } from "acpx/runtime";
8
+ import { createNativeSessionCommitFinalizer } from "openclaw/plugin-sdk/agent-harness-session-runtime";
8
9
  import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
9
10
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
10
11
  import { resolveNodeHostExecutable } from "openclaw/plugin-sdk/node-host";
@@ -227,14 +228,14 @@ function createAcpAgentHarness(params) {
227
228
  async withSessionDeletion(input, run) {
228
229
  let committed = false;
229
230
  try {
230
- return await run({
231
+ return await run(createNativeSessionCommitFinalizer({
231
232
  commit: () => {
232
233
  committed = true;
233
234
  },
234
235
  rollback: () => {
235
236
  committed = false;
236
237
  }
237
- });
238
+ }));
238
239
  } finally {
239
240
  if (committed) await retire(input, input.assertCurrent);
240
241
  }
@@ -247,7 +248,7 @@ function createAcpAgentHarness(params) {
247
248
  }
248
249
  //#endregion
249
250
  //#region extensions/acpx/src/pi-session-catalog-plugin.ts
250
- const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./.setup/pi-session-catalog-runtime-IKEanK3s.mjs"));
251
+ const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./.setup/pi-session-catalog-runtime-7jc50ZJ4.mjs"));
251
252
  function fullConfigCatalogEnabled(config) {
252
253
  if (!isRecord(config) || !isRecord(config.plugins) || !isRecord(config.plugins.entries)) return true;
253
254
  const entry = config.plugins.entries.acpx;
@@ -1,2 +1,2 @@
1
- import { t as createAcpxRuntimeService } from "./.setup/register.runtime-DByczkH_.mjs";
1
+ import { t as createAcpxRuntimeService } from "./.setup/register.runtime-C0nVOTzT.mjs";
2
2
  export { createAcpxRuntimeService };
@@ -16,7 +16,6 @@
16
16
  "doctorContract": {
17
17
  "configRepair": true,
18
18
  "stateMigrations": [
19
- { "id": "acpx-runtime-state-to-plugin-state" },
20
19
  {
21
20
  "id": "acpx-session-owner-resources",
22
21
  "doctorOnly": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/acpx",
3
- "version": "2026.9.8",
3
+ "version": "2026.10.1-beta.2",
4
4
  "description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,7 @@
10
10
  "dependencies": {
11
11
  "@agentclientprotocol/claude-agent-acp": "0.79.0",
12
12
  "@agentclientprotocol/codex-acp": "1.12.0",
13
- "@openclaw/fs-safe": "0.21.1",
13
+ "@openclaw/fs-safe": "0.23.0",
14
14
  "acpx": "0.19.1",
15
15
  "smol-toml": "1.8.0",
16
16
  "zod": "4.6.5"
@@ -45,20 +45,10 @@
45
45
  ]
46
46
  },
47
47
  "compat": {
48
- "pluginApi": ">=2026.9.8"
48
+ "pluginApi": ">=2026.10.1-beta.2"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.9.8",
52
- "staticAssets": [
53
- {
54
- "source": "./src/runtime-internals/mcp-proxy.mjs",
55
- "output": "mcp-proxy.mjs"
56
- },
57
- {
58
- "source": "./src/runtime-internals/mcp-command-line.mjs",
59
- "output": "mcp-command-line.mjs"
60
- }
61
- ]
51
+ "openclawVersion": "2026.10.1-beta.2"
62
52
  },
63
53
  "release": {
64
54
  "publishToClawHub": true,
@@ -78,7 +68,7 @@
78
68
  "skills/**"
79
69
  ],
80
70
  "peerDependencies": {
81
- "openclaw": ">=2026.9.8"
71
+ "openclaw": ">=2026.10.1-beta.2"
82
72
  },
83
73
  "peerDependenciesMeta": {
84
74
  "openclaw": {
@@ -1,135 +0,0 @@
1
- import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
- //#region extensions/acpx/src/codex-adapter.ts
3
- const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
4
- const CODEX_ACP_BIN = "codex-acp";
5
- const LEGACY_CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
6
- const OPENCLAW_CODEX_CONFIG_ARG = "--openclaw-codex-config";
7
- //#endregion
8
- //#region extensions/acpx/src/command-line.ts
9
- /** Match ACPX's persisted argv identity; scalar records keep their original bytes. */
10
- function renderAgentCommand(command) {
11
- return typeof command === "string" ? command : command.map((part) => /^[A-Za-z0-9_@%+=:,./^~-]+$/.test(part) ? part : JSON.stringify(part)).join(" ");
12
- }
13
- /** Split a command string into argv-like parts using simple quote/backslash rules. */
14
- function splitCommandParts(value) {
15
- if (Array.isArray(value)) return value;
16
- const windows = process.platform === "win32";
17
- const parts = [];
18
- let current = "";
19
- let quote = null;
20
- let escaping = false;
21
- let hasPart = false;
22
- for (const ch of value) {
23
- if (escaping) {
24
- current += ch;
25
- escaping = false;
26
- hasPart = true;
27
- continue;
28
- }
29
- if (ch === "\\" && quote !== "'" && !windows) {
30
- escaping = true;
31
- hasPart = true;
32
- continue;
33
- }
34
- if (windows && ch === "\"" && quote !== "'") {
35
- const backslashes = current.match(/\\+$/)?.[0].length ?? 0;
36
- current = current.slice(0, current.length - backslashes) + "\\".repeat(Math.floor(backslashes / 2));
37
- if (backslashes % 2 === 1) {
38
- current += "\"";
39
- continue;
40
- }
41
- }
42
- if (quote) {
43
- if (ch === quote) quote = null;
44
- else current += ch;
45
- continue;
46
- }
47
- if (ch === "'" || ch === "\"") {
48
- quote = ch;
49
- hasPart = true;
50
- continue;
51
- }
52
- if (/\s/.test(ch)) {
53
- if (hasPart) {
54
- parts.push(current);
55
- current = "";
56
- hasPart = false;
57
- }
58
- continue;
59
- }
60
- current += ch;
61
- hasPart = true;
62
- }
63
- if (escaping) current += "\\";
64
- if (quote) throw new Error("Invalid agent command: unterminated quote");
65
- if (hasPart) parts.push(current);
66
- return parts;
67
- }
68
- const OPENCLAW_BRIDGE_EXECUTABLE = "openclaw";
69
- const OPENCLAW_BRIDGE_SUBCOMMAND = "acp";
70
- function basename(value) {
71
- return value.split(/[\\/]/).pop() ?? value;
72
- }
73
- function isEnvAssignment(value) {
74
- return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value);
75
- }
76
- function unwrapEnvCommand(parts) {
77
- const command = parts.at(0);
78
- if (!command || basename(command) !== "env") return parts;
79
- let index = 1;
80
- while (true) {
81
- const part = parts.at(index);
82
- if (!part || !isEnvAssignment(part)) break;
83
- index += 1;
84
- }
85
- return parts.slice(index);
86
- }
87
- function matchesExecutableName(value, executableName) {
88
- const normalized = basename(value).toLowerCase();
89
- return normalized === executableName || normalized === `${executableName}.exe`;
90
- }
91
- function matchesPackageSpec(value, packageName) {
92
- const normalized = value.trim().toLowerCase();
93
- return normalized === packageName || normalized.startsWith(`${packageName}@`);
94
- }
95
- function stripModuleExtension(value) {
96
- return value.replace(/\.[cm]?js$/i, "").toLowerCase();
97
- }
98
- function isAcpCommand(command, params) {
99
- if (!command) return false;
100
- const parts = unwrapEnvCommand(splitCommandParts(command));
101
- if (!parts.length) return false;
102
- if (parts.some((part) => matchesPackageSpec(part, params.packageName))) return true;
103
- const commandName = basename(parts[0] ?? "");
104
- if (matchesExecutableName(commandName, params.executableName)) return true;
105
- if (!matchesExecutableName(commandName, "node")) return false;
106
- const scriptName = stripModuleExtension(basename(parts[1] ?? ""));
107
- return scriptName === params.executableName || scriptName === `${params.executableName}-wrapper`;
108
- }
109
- function isOpenClawBridgeCommand(command) {
110
- if (!command) return false;
111
- const parts = unwrapEnvCommand(splitCommandParts(command));
112
- if (basename(parts[0] ?? "") === OPENCLAW_BRIDGE_EXECUTABLE) return parts[1] === OPENCLAW_BRIDGE_SUBCOMMAND;
113
- if (basename(parts[0] ?? "") !== "node") return false;
114
- const scriptName = basename(parts[1] ?? "");
115
- return /^openclaw(?:\.[cm]?js)?$/i.test(scriptName) && parts[2] === OPENCLAW_BRIDGE_SUBCOMMAND;
116
- }
117
- function isCodexAcpCommand(command) {
118
- return isAcpCommand(command, {
119
- packageName: CODEX_ACP_PACKAGE,
120
- executableName: "codex-acp"
121
- });
122
- }
123
- function isClaudeAcpCommand(command) {
124
- return isAcpCommand(command, {
125
- packageName: "@agentclientprotocol/claude-agent-acp",
126
- executableName: "claude-agent-acp"
127
- });
128
- }
129
- function resolveAgentCommand(params) {
130
- const normalizedAgentName = normalizeOptionalLowercaseString(params.agentName);
131
- if (!normalizedAgentName) return;
132
- return splitCommandParts(params.agentRegistry.resolve(normalizedAgentName));
133
- }
134
- //#endregion
135
- export { resolveAgentCommand as a, CODEX_ACP_PACKAGE as c, renderAgentCommand as i, LEGACY_CODEX_ACP_PACKAGE as l, isCodexAcpCommand as n, splitCommandParts as o, isOpenClawBridgeCommand as r, CODEX_ACP_BIN as s, isClaudeAcpCommand as t, OPENCLAW_CODEX_CONFIG_ARG as u };
@@ -1,128 +0,0 @@
1
- /**
2
- * Command-line parser for ACPX MCP proxy targets. It handles simple quoting and
3
- * Windows executable paths before spawning the configured MCP target.
4
- */
5
- const WINDOWS_DIRECT_EXECUTABLE_PATH_RE =
6
- /^(?<command>(?:[A-Za-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+[\\/]).*?\.(?:exe|com))(?=\s|$)(?:\s+(?<rest>.*))?$/i;
7
-
8
- // Windows wrapper scripts need their host shell or interpreter (`cmd.exe`,
9
- // `powershell.exe`, or `node`) instead of direct spawning.
10
- const WINDOWS_WRAPPER_PATH_RE =
11
- /^(?:[A-Za-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+[\\/]).*?\.(?:bat|cmd|cjs|js|mjs|ps1)$/i;
12
-
13
- function splitCommandParts(value, platform = process.platform) {
14
- const parts = [];
15
- let current = "";
16
- let quote = null;
17
- let escaping = false;
18
-
19
- for (let index = 0; index < value.length; index += 1) {
20
- const ch = value[index];
21
- const next = value[index + 1];
22
- if (escaping) {
23
- current += ch;
24
- escaping = false;
25
- continue;
26
- }
27
- if (ch === "\\") {
28
- if (quote === "'") {
29
- current += ch;
30
- continue;
31
- }
32
- if (platform === "win32") {
33
- if (quote === '"') {
34
- if (next === '"' || next === "\\") {
35
- escaping = true;
36
- continue;
37
- }
38
- current += ch;
39
- continue;
40
- }
41
- if (!quote) {
42
- current += ch;
43
- continue;
44
- }
45
- }
46
- escaping = true;
47
- continue;
48
- }
49
- if (quote) {
50
- if (ch === quote) {
51
- quote = null;
52
- } else {
53
- current += ch;
54
- }
55
- continue;
56
- }
57
- if (ch === "'" || ch === '"') {
58
- quote = ch;
59
- continue;
60
- }
61
- if (/\s/.test(ch)) {
62
- if (current.length > 0) {
63
- parts.push(current);
64
- current = "";
65
- }
66
- continue;
67
- }
68
- current += ch;
69
- }
70
-
71
- if (escaping) {
72
- current += "\\";
73
- }
74
- if (quote) {
75
- throw new Error("Invalid agent command: unterminated quote");
76
- }
77
- if (current.length > 0) {
78
- parts.push(current);
79
- }
80
- return parts;
81
- }
82
-
83
- function splitWindowsExecutableCommand(value, platform = process.platform) {
84
- if (platform !== "win32") {
85
- return null;
86
- }
87
- const trimmed = value.trim();
88
- if (!trimmed || trimmed.startsWith('"') || trimmed.startsWith("'")) {
89
- return null;
90
- }
91
- const match = trimmed.match(WINDOWS_DIRECT_EXECUTABLE_PATH_RE);
92
- if (!match?.groups?.command) {
93
- return null;
94
- }
95
- const rest = match.groups.rest?.trim() ?? "";
96
- return {
97
- command: match.groups.command,
98
- args: rest ? splitCommandParts(rest, platform) : [],
99
- };
100
- }
101
-
102
- function assertSupportedWindowsCommand(command, platform = process.platform) {
103
- if (platform !== "win32" || !WINDOWS_WRAPPER_PATH_RE.test(command)) {
104
- return;
105
- }
106
- throw new Error(
107
- `Unsupported Windows agent command wrapper: ${command}. ` +
108
- "Invoke wrapper scripts through their shell or interpreter instead " +
109
- "(for example `cmd.exe /c`, `powershell.exe -File`, or `node <script>`).",
110
- );
111
- }
112
-
113
- /** Split a configured command string into `{ command, args }` for child_process.spawn. */
114
- export function splitCommandLine(value, platform = process.platform) {
115
- const windowsCommand = splitWindowsExecutableCommand(value, platform);
116
- const parts = windowsCommand ?? splitCommandParts(value, platform);
117
- if (parts.length === 0) {
118
- throw new Error("Invalid agent command: empty command");
119
- }
120
- const parsed = Array.isArray(parts)
121
- ? {
122
- command: parts[0],
123
- args: parts.slice(1),
124
- }
125
- : parts;
126
- assertSupportedWindowsCommand(parsed.command, platform);
127
- return parsed;
128
- }