@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.
- package/README.md +6 -0
- package/assets/icon.png +0 -0
- package/dist/.setup/{command-line-CPBLOiZM.mjs → command-line-B72femaL.mjs} +3 -6
- package/dist/.setup/{config-CdPsIued.mjs → config-Blz15uwJ.mjs} +15 -15
- package/dist/.setup/{pi-session-catalog-runtime-UhiQSaDw.mjs → pi-session-catalog-runtime-IKEanK3s.mjs} +20 -55
- package/dist/.setup/process-lease-n38HFGdp.mjs +311 -0
- package/dist/.setup/{register.runtime-Bm4HrMCp.mjs → register.runtime-DByczkH_.mjs} +15 -38
- package/dist/.setup/{service-DMae5pPH.mjs → service-D5hGWl5U.mjs} +78 -117
- package/dist/.setup/{session-owner-migration-CdJSYRk1.mjs → session-owner-migration-DsGrYq7Y.mjs} +132 -20
- package/dist/.setup/{session-resource-BaJ6bs5d.mjs → session-resource-B-4yPr-j.mjs} +1 -13
- package/dist/doctor-contract-api.js +13 -17
- package/dist/index.js +40 -20
- package/dist/register.runtime.js +1 -1
- package/package.json +7 -6
- package/skills/acp-router/SKILL.md +2 -2
- package/dist/.setup/process-lease-C3dNPtu8.mjs +0 -138
package/dist/.setup/{session-owner-migration-CdJSYRk1.mjs → session-owner-migration-DsGrYq7Y.mjs}
RENAMED
|
@@ -1,28 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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 (
|
|
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-
|
|
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-
|
|
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-
|
|
2
|
-
import { a as ACPX_NATIVE_AGENT_IDS, o as AcpxNativeAgentsSchema, t as resolveAcpxPluginConfig } from "./.setup/config-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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-
|
|
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
|
-
|
|
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;
|
package/dist/register.runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "./.setup/register.runtime-
|
|
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.
|
|
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.
|
|
12
|
-
"@agentclientprotocol/codex-acp": "1.
|
|
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.
|
|
48
|
+
"pluginApi": ">=2026.9.9"
|
|
48
49
|
},
|
|
49
50
|
"build": {
|
|
50
|
-
"openclawVersion": "2026.9.
|
|
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.
|
|
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.
|
|
213
|
-
- `codex -> bundled @agentclientprotocol/codex-acp@1.
|
|
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 };
|