@openclaw/acpx 2026.9.7 → 2026.9.9

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,28 +1,120 @@
1
- import { t as resolveAcpxPluginConfig } from "./config-CdPsIued.mjs";
2
- import { a as hashAcpxProcessCommand, c as openAcpxProcessLeaseStateStore, l as readAcpxProcessLeaseIdentity, o as normalizeAcpxProcessLease } from "./process-lease-C3dNPtu8.mjs";
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";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
- import { archiveLegacyStateSource, asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
6
5
  import { isDeepStrictEqual } from "node:util";
6
+ import { movePathWithCopyFallback } from "openclaw/plugin-sdk/security-runtime";
7
7
  //#region extensions/acpx/src/session-owner-migration.ts
8
+ function openStateDirectoryMarker(input) {
9
+ return input.openKeyedStore({
10
+ namespace: "state-directory-migration",
11
+ maxEntries: 1
12
+ });
13
+ }
14
+ async function directoryEntries(directory) {
15
+ return fs.readdir(directory).catch((error) => {
16
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ENOENT") return [];
17
+ throw error;
18
+ });
19
+ }
20
+ function legacyStateDirectory(input) {
21
+ const explicit = (0, runtime_doctor_migrations_exports.asObjectRecord)(input.rawConfig)?.stateDir;
22
+ return typeof explicit === "string" && explicit.trim() ? void 0 : path.resolve(input.workspaceDir?.trim() || process.cwd(), "state");
23
+ }
24
+ async function hasLegacyState(input) {
25
+ const source = legacyStateDirectory(input);
26
+ return Boolean(source && source !== input.stateDir && (await directoryEntries(input.stateDir)).length === 0 && await hasSessionData(source));
27
+ }
28
+ async function hasSessionData(directory) {
29
+ return (await directoryEntries(path.join(directory, "sessions"))).some((name) => name.endsWith(".json"));
30
+ }
31
+ /** Startup and offline Doctor share this one-time relocation of the former default. */
32
+ async function adoptAcpxStateDirectory(input) {
33
+ const result = {
34
+ stateDir: input.stateDir,
35
+ changes: [],
36
+ warnings: []
37
+ };
38
+ const source = legacyStateDirectory(input);
39
+ if (!source || source === input.stateDir) return result;
40
+ let published = false;
41
+ let legacyFallback = false;
42
+ try {
43
+ const marker = openStateDirectoryMarker(input);
44
+ const previous = await marker.lookup("workspace-state-v1");
45
+ if (previous?.destination === input.stateDir && previous.completed) return result;
46
+ const destinationEmpty = (await directoryEntries(input.stateDir)).length === 0;
47
+ if (destinationEmpty) {
48
+ legacyFallback = true;
49
+ legacyFallback = await hasSessionData(source);
50
+ }
51
+ if (previous?.destination === input.stateDir) {
52
+ if (await hasSessionData(input.stateDir)) {
53
+ published = true;
54
+ await marker.register("workspace-state-v1", {
55
+ destination: input.stateDir,
56
+ completed: true
57
+ }, { assertCurrent: input.assertCurrent });
58
+ result.changes.push(`Completed ACPX session state adoption at ${input.stateDir}.`);
59
+ if ((await directoryEntries(source)).length > 0) result.warnings.push(`ACPX uses ${input.stateDir}; a legacy copy remains at ${source}. Verify the migrated sessions before removing that copy.`);
60
+ return result;
61
+ }
62
+ if (!destinationEmpty) {
63
+ legacyFallback = true;
64
+ legacyFallback = await hasSessionData(source);
65
+ throw new Error("Destination became occupied before ACPX state adoption completed");
66
+ }
67
+ }
68
+ if (!destinationEmpty || !legacyFallback) return result;
69
+ if (!(await fs.lstat(source)).isDirectory() || input.stateDir.startsWith(`${source}${path.sep}`)) throw new Error("Legacy state must be a directory outside the new state directory");
70
+ input.assertCurrent?.();
71
+ await fs.mkdir(path.dirname(input.stateDir), { recursive: true });
72
+ await marker.register("workspace-state-v1", {
73
+ destination: input.stateDir,
74
+ completed: false
75
+ }, { assertCurrent: input.assertCurrent });
76
+ await movePathWithCopyFallback({
77
+ from: source,
78
+ to: input.stateDir,
79
+ assertBeforeMutation: input.assertCurrent,
80
+ onDestinationPublished() {
81
+ published = true;
82
+ }
83
+ });
84
+ result.changes.push(`Migrated ACPX session state from ${source} to ${input.stateDir}.`);
85
+ await marker.register("workspace-state-v1", {
86
+ destination: input.stateDir,
87
+ completed: true
88
+ }, { assertCurrent: input.assertCurrent });
89
+ } catch (error) {
90
+ result.stateDir = !published && legacyFallback ? source : input.stateDir;
91
+ result.warnings.push(published ? `ACPX state adopted at ${input.stateDir}; migration cleanup needs attention: ${String(error)}. Run openclaw doctor --fix.` : legacyFallback ? `ACPX state migration failed: ${String(error)}. Using ${source} for this process; set plugins.entries.acpx.config.stateDir to ${JSON.stringify(source)} to keep the old location.` : `ACPX state migration could not be inspected: ${String(error)}. Keeping ${input.stateDir}; run openclaw doctor --fix.`);
92
+ }
93
+ return result;
94
+ }
95
+ function stateDirectoryInput(input) {
96
+ return {
97
+ rawConfig: input.config.plugins?.entries?.acpx?.config,
98
+ workspaceDir: input.serviceWorkspaceDir,
99
+ stateDir: path.dirname(sessionDirectory(input)),
100
+ openKeyedStore: input.context.openPluginStateKeyedStore
101
+ };
102
+ }
8
103
  function sessionDirectory(input) {
9
104
  if (!input.serviceWorkspaceDir) throw new Error("ACP ownership repair requires the Gateway service workspace; upgrade OpenClaw Doctor.");
10
105
  return path.join(resolveAcpxPluginConfig({
11
106
  rawConfig: input.config.plugins?.entries?.acpx?.config,
12
- workspaceDir: input.serviceWorkspaceDir
107
+ workspaceDir: input.serviceWorkspaceDir,
108
+ stateDir: input.stateDir
13
109
  }).stateDir, "sessions");
14
110
  }
15
- async function legacyRecords(input) {
16
- const directory = sessionDirectory(input);
17
- const ids = (await fs.readdir(directory).catch((error) => {
18
- if (asObjectRecord(error)?.code === "ENOENT") return [];
19
- throw error;
20
- })).filter((name) => name.endsWith(".json")).map((name) => decodeURIComponent(name.slice(0, -5))).filter((id) => !id.startsWith("agent:") && !id.startsWith(".openclaw-owner-"));
111
+ async function legacyRecords(input, directory = sessionDirectory(input)) {
112
+ const ids = (await directoryEntries(directory)).filter((name) => name.endsWith(".json")).map((name) => decodeURIComponent(name.slice(0, -5))).filter((id) => !id.startsWith("agent:") && !id.startsWith(".openclaw-owner-"));
21
113
  if (ids.length === 0) return {
22
114
  directory,
23
115
  ids
24
116
  };
25
- const { resolveAcpxSessionResource } = await import("./session-resource-BaJ6bs5d.mjs").then((n) => n.n);
117
+ const { resolveAcpxSessionResource } = await import("./session-resource-B-4yPr-j.mjs").then((n) => n.n);
26
118
  const evidence = await input.context.inspectAcpSessionClaims?.();
27
119
  const { decodeAcpxRuntimeHandleState } = await import("acpx/runtime");
28
120
  return {
@@ -42,7 +134,7 @@ function requireStoppedPid(pid) {
42
134
  try {
43
135
  process.kill(pid, 0);
44
136
  } catch (error) {
45
- if (asObjectRecord(error)?.code === "ESRCH") return;
137
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ESRCH") return;
46
138
  throw new Error("record process liveness cannot be verified", { cause: error });
47
139
  }
48
140
  throw new Error("record still has a live process; stop the harness before Doctor repair");
@@ -62,10 +154,10 @@ function matchesClaimRecord(claim, raw, oldId, resource, decode) {
62
154
  async function migrateRecord(input, directory, oldId, claims, changes, warnings) {
63
155
  const sourcePath = recordPath(directory, oldId);
64
156
  const sourceBytes = await fs.readFile(sourcePath, "utf8");
65
- const raw = asObjectRecord(JSON.parse(sourceBytes));
157
+ const raw = (0, runtime_doctor_migrations_exports.asObjectRecord)(JSON.parse(sourceBytes));
66
158
  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");
67
159
  requireStoppedPid(raw.pid);
68
- const { resolveAcpxSessionResource } = await import("./session-resource-BaJ6bs5d.mjs").then((n) => n.n);
160
+ const { resolveAcpxSessionResource } = await import("./session-resource-B-4yPr-j.mjs").then((n) => n.n);
69
161
  const { createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState } = await import("acpx/runtime");
70
162
  const candidates = claims.filter((claim) => matchesClaimRecord(claim, raw, oldId, resolveAcpxSessionResource(claim), decodeAcpxRuntimeHandleState));
71
163
  if (candidates.length !== 1) throw new Error("exactly one current canonical owner claim is required");
@@ -116,7 +208,7 @@ async function migrateRecord(input, directory, oldId, claims, changes, warnings)
116
208
  name: resource
117
209
  })) throw new Error("rekey would alter interpreted history/event references; source retained");
118
210
  const existing = await fs.readFile(destinationPath, "utf8").catch((error) => {
119
- if (asObjectRecord(error)?.code === "ENOENT") return;
211
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ENOENT") return;
120
212
  throw error;
121
213
  });
122
214
  const originalLocator = decodeAcpxRuntimeHandleState(claim.meta.runtimeSessionName);
@@ -151,7 +243,7 @@ async function migrateRecord(input, directory, oldId, claims, changes, warnings)
151
243
  const verified = await input.context.inspectAcpSessionClaims();
152
244
  if (verified.incomplete.length || !verified.claims.some((item) => item.agentId === claim.agentId && item.sessionKey === claim.sessionKey && item.meta.identity?.acpxRecordId === recordId && isDeepStrictEqual(item.binding, claim.binding))) throw new Error("canonical metadata verification failed; source retained for rerun");
153
245
  changes.push(`Migrated ACP backend history for ${claim.agentId}/${claim.sessionKey} to its owner-qualified resource.`);
154
- if (!oneshot) await archiveLegacyStateSource({
246
+ if (!oneshot) await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
155
247
  filePath: sourcePath,
156
248
  label: "ACP owner record",
157
249
  changes,
@@ -169,18 +261,38 @@ const acpxSessionOwnerMigration = {
169
261
  label: "ACP session owners",
170
262
  doctorOnly: true,
171
263
  phase: "after-session-repair",
264
+ collectBackupResources(input) {
265
+ const destination = sessionDirectory(input);
266
+ const explicit = (0, runtime_doctor_migrations_exports.asObjectRecord)(input.config.plugins?.entries?.acpx?.config)?.stateDir;
267
+ return [path.dirname(destination), ...!explicit && input.serviceWorkspaceDir ? [path.join(input.serviceWorkspaceDir, "state")] : []].map((directory) => ({
268
+ path: directory,
269
+ kind: "directory"
270
+ }));
271
+ },
172
272
  async detectLegacyState(input) {
273
+ try {
274
+ const directoryInput = stateDirectoryInput(input);
275
+ const marker = legacyStateDirectory(directoryInput) ? await openStateDirectoryMarker(directoryInput).lookup("workspace-state-v1") : void 0;
276
+ const matchingMarker = marker?.destination === directoryInput.stateDir;
277
+ if (!(matchingMarker && marker.completed) && (matchingMarker && !marker.completed || await hasLegacyState(directoryInput))) return { preview: ["ACPX session state in <workspace>/state will be migrated automatically to the OpenClaw state directory."] };
278
+ } catch (error) {
279
+ return { preview: [`ACPX legacy state could not be inspected: ${String(error)}. Run openclaw doctor --fix.`] };
280
+ }
173
281
  const { ids } = await legacyRecords(input);
174
282
  return ids.length ? { preview: [`ACP backend has ${ids.length} unqualified record(s). Stop the Gateway and run openclaw doctor --fix; ambiguous histories remain intact.`] } : null;
175
283
  },
176
284
  async migrateLegacyState(input) {
177
- const changes = [];
178
- const warnings = [];
285
+ const { stateDir, changes, warnings } = await adoptAcpxStateDirectory(stateDirectoryInput(input));
286
+ if (warnings.length) return {
287
+ changes,
288
+ warnings,
289
+ warningDisposition: "recoverable"
290
+ };
179
291
  if (!input.context.inspectAcpSessionClaims || !input.context.updateAcpSessionIdentity) return {
180
292
  changes,
181
293
  warnings: ["ACP owner repair requires current offline Doctor maintenance authority."]
182
294
  };
183
- const { directory, ids } = await legacyRecords(input);
295
+ const { directory, ids } = await legacyRecords(input, path.join(stateDir, "sessions"));
184
296
  const evidence = await input.context.inspectAcpSessionClaims();
185
297
  if (evidence.incomplete.length) return {
186
298
  changes,
@@ -200,4 +312,4 @@ const acpxSessionOwnerMigration = {
200
312
  }
201
313
  };
202
314
  //#endregion
203
- export { acpxSessionOwnerMigration };
315
+ export { acpxSessionOwnerMigration, adoptAcpxStateDirectory };
@@ -1,19 +1,7 @@
1
+ import { _ as __exportAll } from "./process-lease-n38HFGdp.mjs";
1
2
  import { AcpRuntimeError } from "../runtime-api.js";
2
- import "node:module";
3
3
  import { createHash } from "node:crypto";
4
4
  import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
5
- //#region \0rolldown/runtime.js
6
- var __defProp = Object.defineProperty;
7
- var __exportAll = (all, no_symbols) => {
8
- let target = {};
9
- for (var name in all) __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true
12
- });
13
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
- return target;
15
- };
16
- //#endregion
17
5
  //#region extensions/acpx/src/session-resource.ts
18
6
  var session_resource_exports = /* @__PURE__ */ __exportAll({ resolveAcpxSessionResource: () => resolveAcpxSessionResource });
19
7
  /** Logical OpenClaw keys stay intact; only bare backend resource names need a namespace. */
@@ -1,7 +1,6 @@
1
- import { c as openAcpxProcessLeaseStateStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, 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-C3dNPtu8.mjs";
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
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
- import { archiveLegacyStateSource, asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
5
4
  //#region extensions/acpx/doctor-contract-api.ts
6
5
  const ACPX_CONFIG_PATH = [
7
6
  "plugins",
@@ -17,16 +16,16 @@ const legacyConfigRules = RETIRED_ACPX_CONFIG_KEYS.map((key) => ({
17
16
  }));
18
17
  /** Removes retired plugin-owned config without keeping runtime compatibility keys. */
19
18
  function normalizeCompatibilityConfig({ cfg }) {
20
- const entry = asObjectRecord(cfg.plugins?.entries?.acpx);
21
- const pluginConfig = asObjectRecord(entry?.config);
19
+ const entry = (0, runtime_doctor_migrations_exports.asObjectRecord)(cfg.plugins?.entries?.acpx);
20
+ const pluginConfig = (0, runtime_doctor_migrations_exports.asObjectRecord)(entry?.config);
22
21
  const retiredKeys = RETIRED_ACPX_CONFIG_KEYS.filter((key) => Object.hasOwn(pluginConfig ?? {}, key));
23
22
  if (!pluginConfig || retiredKeys.length === 0) return {
24
23
  config: cfg,
25
24
  changes: []
26
25
  };
27
26
  const nextConfig = structuredClone(cfg);
28
- const nextEntry = asObjectRecord(nextConfig.plugins?.entries?.acpx);
29
- const nextPluginConfig = asObjectRecord(nextEntry?.config);
27
+ const nextEntry = (0, runtime_doctor_migrations_exports.asObjectRecord)(nextConfig.plugins?.entries?.acpx);
28
+ const nextPluginConfig = (0, runtime_doctor_migrations_exports.asObjectRecord)(nextEntry?.config);
30
29
  if (!nextPluginConfig) return {
31
30
  config: cfg,
32
31
  changes: []
@@ -94,20 +93,14 @@ const stateMigrations = [{
94
93
  warnings
95
94
  };
96
95
  }
97
- if (canAdoptLegacyGateway && canonicalGatewayInstanceId) {
98
- await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
99
- instanceId: canonicalGatewayInstanceId,
100
- createdAt: Date.now()
101
- });
102
- changes.push("Migrated ACPX gateway instance id -> plugin state");
103
- } else if (canonicalGatewayInstanceId && !existingGateway) {
96
+ if (canonicalGatewayInstanceId && (canAdoptLegacyGateway || !existingGateway)) {
104
97
  await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
105
98
  instanceId: canonicalGatewayInstanceId,
106
99
  createdAt: Date.now()
107
100
  });
108
101
  changes.push("Migrated ACPX gateway instance id -> plugin state");
109
102
  } else if (gatewayInstanceId && existingGateway?.instanceId !== gatewayInstanceId) warnings.push("Skipped ACPX gateway instance id import because plugin state already differs");
110
- if (gatewayInstanceId) await archiveLegacyStateSource({
103
+ if (gatewayInstanceId) await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
111
104
  filePath: gatewayInstancePath,
112
105
  label: "ACPX gateway-instance-id",
113
106
  changes,
@@ -119,7 +112,7 @@ const stateMigrations = [{
119
112
  for (const lease of openLeases) if (await processLeaseStore.registerIfAbsent(lease.leaseId, lease)) imported++;
120
113
  else alreadyPresent++;
121
114
  changes.push(`Migrated ACPX process leases -> plugin state (${imported} imported, ${alreadyPresent} already present)`);
122
- await archiveLegacyStateSource({
115
+ await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
123
116
  filePath: processLeasePath,
124
117
  label: "ACPX process-leases",
125
118
  changes,
@@ -136,11 +129,14 @@ const stateMigrations = [{
136
129
  label: "ACP session owners",
137
130
  doctorOnly: true,
138
131
  phase: "after-session-repair",
132
+ async collectBackupResources(input) {
133
+ return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.collectBackupResources(input);
134
+ },
139
135
  async detectLegacyState(input) {
140
- return (await import("./.setup/session-owner-migration-CdJSYRk1.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
136
+ return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
141
137
  },
142
138
  async migrateLegacyState(input) {
143
- return (await import("./.setup/session-owner-migration-CdJSYRk1.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
139
+ return (await import("./.setup/session-owner-migration-DsGrYq7Y.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
144
140
  }
145
141
  }];
146
142
  //#endregion
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as createAcpxRuntimeService } from "./.setup/register.runtime-Bm4HrMCp.mjs";
2
- import { a as ACPX_NATIVE_AGENT_IDS, o as AcpxNativeAgentsSchema, t as resolveAcpxPluginConfig } from "./.setup/config-CdPsIued.mjs";
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";
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";
@@ -157,26 +157,47 @@ function createAcpAgentHarness(params) {
157
157
  },
158
158
  async loadModelCatalog(input) {
159
159
  generation.signal.throwIfAborted();
160
- if (!params.isEnabled()) return [];
160
+ if (!params.isEnabled()) return { entries: [] };
161
161
  const inspection = inspectAgent();
162
- if (inspection?.launch.kind !== "installed") return [];
162
+ if (inspection?.launch.kind !== "installed") return { entries: [] };
163
163
  const config = resolveAcpxPluginConfig({
164
164
  rawConfig: params.api.pluginConfig,
165
165
  workspaceDir: input.workspaceDir
166
166
  });
167
- const models = await inspectAgentModels({
168
- agentCommand: inspection.launch.argv,
169
- cwd: input.workspaceDir ?? config.cwd,
170
- signal: generation.signal,
171
- timeoutMs: config.timeoutSeconds === void 0 ? void 0 : finiteSecondsToTimerSafeMilliseconds(config.timeoutSeconds) ?? 1
172
- });
173
- generation.signal.throwIfAborted();
174
- return (params.isEnabled() ? models?.availableModels ?? [] : []).map((model) => ({
175
- provider: id,
176
- id: model.modelId,
177
- name: model.name,
178
- nativeRuntime: id
179
- }));
167
+ try {
168
+ const models = await inspectAgentModels({
169
+ agentCommand: inspection.launch.argv,
170
+ cwd: input.workspaceDir ?? config.cwd,
171
+ signal: generation.signal,
172
+ timeoutMs: config.timeoutSeconds === void 0 ? void 0 : finiteSecondsToTimerSafeMilliseconds(config.timeoutSeconds) ?? 1
173
+ });
174
+ generation.signal.throwIfAborted();
175
+ if (!params.isEnabled()) return { entries: [] };
176
+ return {
177
+ entries: (models?.availableModels ?? []).map((model) => ({
178
+ provider: id,
179
+ id: model.modelId,
180
+ name: model.name,
181
+ nativeRuntime: id
182
+ })),
183
+ outcomes: [{
184
+ provider: id,
185
+ status: "ready"
186
+ }]
187
+ };
188
+ } catch (error) {
189
+ generation.signal.throwIfAborted();
190
+ if (!params.isEnabled()) return { entries: [] };
191
+ const authRequired = error instanceof Error && error.name === "RequestError" && "code" in error && error.code === -32e3 && (error.message === "Authentication required" || error.message.startsWith("Authentication required: "));
192
+ return {
193
+ entries: [],
194
+ outcomes: [{
195
+ provider: id,
196
+ status: authRequired ? "auth-rejected" : "unavailable",
197
+ rejectionScope: "catalog"
198
+ }]
199
+ };
200
+ }
180
201
  },
181
202
  async runAttempt(input) {
182
203
  generation.signal.throwIfAborted();
@@ -226,12 +247,11 @@ function createAcpAgentHarness(params) {
226
247
  }
227
248
  //#endregion
228
249
  //#region extensions/acpx/src/pi-session-catalog-plugin.ts
229
- const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./.setup/pi-session-catalog-runtime-UhiQSaDw.mjs"));
250
+ const loadPiSessionCatalogModule = createLazyRuntimeModule(() => import("./.setup/pi-session-catalog-runtime-IKEanK3s.mjs"));
230
251
  function fullConfigCatalogEnabled(config) {
231
252
  if (!isRecord(config) || !isRecord(config.plugins) || !isRecord(config.plugins.entries)) return true;
232
253
  const entry = config.plugins.entries.acpx;
233
- if (!isRecord(entry) || !isRecord(entry.config) || !isRecord(entry.config.piSessionCatalog)) return true;
234
- return entry.config.piSessionCatalog.enabled !== false;
254
+ return isPiSessionCatalogEnabled(isRecord(entry) ? entry.config : void 0);
235
255
  }
236
256
  function isPiSessionCatalogEnabled(pluginConfig) {
237
257
  return !isRecord(pluginConfig) || !isRecord(pluginConfig.piSessionCatalog) || pluginConfig.piSessionCatalog.enabled !== false;
@@ -1,2 +1,2 @@
1
- import { t as createAcpxRuntimeService } from "./.setup/register.runtime-Bm4HrMCp.mjs";
1
+ import { t as createAcpxRuntimeService } from "./.setup/register.runtime-DByczkH_.mjs";
2
2
  export { createAcpxRuntimeService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/acpx",
3
- "version": "2026.9.7",
3
+ "version": "2026.9.9",
4
4
  "description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,8 +8,9 @@
8
8
  },
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "@agentclientprotocol/claude-agent-acp": "0.76.0",
12
- "@agentclientprotocol/codex-acp": "1.11.0",
11
+ "@agentclientprotocol/claude-agent-acp": "0.79.0",
12
+ "@agentclientprotocol/codex-acp": "1.12.0",
13
+ "@openclaw/fs-safe": "0.21.1",
13
14
  "acpx": "0.19.1",
14
15
  "smol-toml": "1.8.0",
15
16
  "zod": "4.6.5"
@@ -44,10 +45,10 @@
44
45
  ]
45
46
  },
46
47
  "compat": {
47
- "pluginApi": ">=2026.9.5"
48
+ "pluginApi": ">=2026.9.9"
48
49
  },
49
50
  "build": {
50
- "openclawVersion": "2026.9.5",
51
+ "openclawVersion": "2026.9.9",
51
52
  "staticAssets": [
52
53
  {
53
54
  "source": "./src/runtime-internals/mcp-proxy.mjs",
@@ -77,7 +78,7 @@
77
78
  "skills/**"
78
79
  ],
79
80
  "peerDependencies": {
80
- "openclaw": ">=2026.9.5"
81
+ "openclaw": ">=2026.9.9"
81
82
  },
82
83
  "peerDependenciesMeta": {
83
84
  "openclaw": {
@@ -209,8 +209,8 @@ ${ACPX_CMD} codex sessions close oc-codex-<conversationId>
209
209
  Defaults are:
210
210
 
211
211
  - `openclaw -> openclaw acp`
212
- - `claude -> bundled @agentclientprotocol/claude-agent-acp@0.76.0`
213
- - `codex -> bundled @agentclientprotocol/codex-acp@1.11.0 through OpenClaw's isolated CODEX_HOME wrapper`
212
+ - `claude -> bundled @agentclientprotocol/claude-agent-acp@0.79.0`
213
+ - `codex -> bundled @agentclientprotocol/codex-acp@1.12.0 through OpenClaw's isolated CODEX_HOME wrapper`
214
214
  - `copilot -> copilot --acp --stdio`
215
215
  - `cursor -> cursor-agent acp`
216
216
  - `droid -> droid exec --output-format acp`
@@ -1,138 +0,0 @@
1
- import { a as renderAgentCommand, s as splitCommandParts } from "./command-line-CPBLOiZM.mjs";
2
- import { createHash } from "node:crypto";
3
- //#region extensions/acpx/src/state.ts
4
- const ACPX_PROCESS_LEASE_NAMESPACE = "process-leases";
5
- const ACPX_PROCESS_LEASE_MAX_ENTRIES = 4096;
6
- const ACPX_LEGACY_PROCESS_LEASE_FILE = "process-leases.json";
7
- const ACPX_GATEWAY_INSTANCE_NAMESPACE = "gateway-instance";
8
- const ACPX_GATEWAY_INSTANCE_KEY = "current";
9
- const ACPX_LEGACY_GATEWAY_INSTANCE_FILE = "gateway-instance-id";
10
- function normalizeAcpxGatewayInstanceRecord(value) {
11
- if (typeof value !== "object" || value === null) return;
12
- const record = value;
13
- if (typeof record.instanceId !== "string" || !record.instanceId.trim()) return;
14
- const createdAt = typeof record.createdAt === "number" && Number.isFinite(record.createdAt) ? Math.trunc(record.createdAt) : 0;
15
- return {
16
- instanceId: record.instanceId.trim(),
17
- createdAt
18
- };
19
- }
20
- //#endregion
21
- //#region extensions/acpx/src/process-lease.ts
22
- /**
23
- * Persistent lease store for ACPX wrapper processes. Leases let OpenClaw attach
24
- * gateway/session identity to spawned ACP processes and clean them up later.
25
- */
26
- /** CLI argument carrying the ACPX process lease id. */
27
- const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
28
- /** CLI argument carrying the owning gateway instance id. */
29
- const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
30
- /** Synthetic session identity for generated-wrapper health probes. */
31
- const ACPX_PROBE_LEASE_SESSION_KEY = "openclaw:acpx:probe";
32
- /** Read OpenClaw lease identity from a generated wrapper command. */
33
- function readAcpxProcessLeaseIdentity(command) {
34
- 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 ?? [];
35
- const leaseIndex = parts.lastIndexOf(OPENCLAW_ACPX_LEASE_ID_ARG);
36
- const gatewayIndex = parts.lastIndexOf(OPENCLAW_GATEWAY_INSTANCE_ID_ARG);
37
- const leaseId = leaseIndex >= 0 ? parts[leaseIndex + 1]?.trim() : "";
38
- const gatewayInstanceId = gatewayIndex >= 0 ? parts[gatewayIndex + 1]?.trim() : "";
39
- if (!leaseId || !gatewayInstanceId) return;
40
- return {
41
- leaseId,
42
- gatewayInstanceId
43
- };
44
- }
45
- function normalizeAcpxProcessLease(value) {
46
- if (typeof value !== "object" || value === null) return;
47
- const record = value;
48
- if (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" || ![
49
- "open",
50
- "closing",
51
- "closed",
52
- "lost"
53
- ].includes(String(record.state))) return;
54
- return {
55
- leaseId: record.leaseId,
56
- gatewayInstanceId: record.gatewayInstanceId,
57
- sessionKey: record.sessionKey,
58
- wrapperRoot: record.wrapperRoot,
59
- wrapperPath: record.wrapperPath,
60
- rootPid: record.rootPid,
61
- ...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
62
- commandHash: record.commandHash,
63
- startedAt: record.startedAt,
64
- state: record.state
65
- };
66
- }
67
- function normalizeAcpxProcessLeaseFile(value) {
68
- const root = typeof value === "object" && value !== null ? value : {};
69
- return {
70
- version: 1,
71
- leases: Array.isArray(root.leases) ? root.leases.map(normalizeAcpxProcessLease).filter((lease) => Boolean(lease)) : []
72
- };
73
- }
74
- function openAcpxProcessLeaseStateStore(openKeyedStore) {
75
- return openKeyedStore({
76
- namespace: ACPX_PROCESS_LEASE_NAMESPACE,
77
- maxEntries: ACPX_PROCESS_LEASE_MAX_ENTRIES,
78
- overflowPolicy: "reject-new"
79
- });
80
- }
81
- /** Create a serialized SQLite-backed ACPX process lease store. */
82
- function createAcpxProcessLeaseStore(params) {
83
- let updateQueue = Promise.resolve();
84
- async function update(mutator) {
85
- const run = updateQueue.then(async () => {
86
- await mutator();
87
- });
88
- updateQueue = run.catch(() => {});
89
- await run;
90
- }
91
- async function readCurrent() {
92
- await updateQueue;
93
- return (await params.store.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => Boolean(lease));
94
- }
95
- return {
96
- async load(leaseId) {
97
- await updateQueue;
98
- return normalizeAcpxProcessLease(await params.store.lookup(leaseId));
99
- },
100
- async listOpen(gatewayInstanceId) {
101
- return (await readCurrent()).filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
102
- },
103
- async save(lease) {
104
- await update(async () => {
105
- await params.store.register(lease.leaseId, lease);
106
- });
107
- },
108
- async markState(leaseId, state) {
109
- await update(async () => {
110
- if (state === "closed" || state === "lost") {
111
- await params.store.delete(leaseId);
112
- return;
113
- }
114
- const lease = normalizeAcpxProcessLease(await params.store.lookup(leaseId));
115
- if (lease) await params.store.register(leaseId, {
116
- ...lease,
117
- state
118
- });
119
- });
120
- }
121
- };
122
- }
123
- /** Hash a wrapper command so process leases can detect command drift. */
124
- function hashAcpxProcessCommand(command) {
125
- return createHash("sha256").update(renderAgentCommand(command)).digest("hex");
126
- }
127
- /** Append portable wrapper arguments without changing the executable or argument bytes. */
128
- function withAcpxLeaseArgs(params) {
129
- return [
130
- ...splitCommandParts(params.command),
131
- OPENCLAW_ACPX_LEASE_ID_ARG,
132
- params.leaseId,
133
- OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
134
- params.gatewayInstanceId
135
- ];
136
- }
137
- //#endregion
138
- export { hashAcpxProcessCommand as a, openAcpxProcessLeaseStateStore as c, ACPX_GATEWAY_INSTANCE_KEY as d, ACPX_GATEWAY_INSTANCE_NAMESPACE as f, normalizeAcpxGatewayInstanceRecord as h, createAcpxProcessLeaseStore as i, readAcpxProcessLeaseIdentity as l, ACPX_LEGACY_PROCESS_LEASE_FILE as m, OPENCLAW_ACPX_LEASE_ID_ARG as n, normalizeAcpxProcessLease as o, ACPX_LEGACY_GATEWAY_INSTANCE_FILE as p, OPENCLAW_GATEWAY_INSTANCE_ID_ARG as r, normalizeAcpxProcessLeaseFile as s, ACPX_PROBE_LEASE_SESSION_KEY as t, withAcpxLeaseArgs as u };