@openclaw/acpx 2026.6.5-beta.2 → 2026.6.5-beta.5
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/dist/doctor-contract-api.js +121 -0
- package/dist/index.js +5 -2
- package/dist/process-lease-DiKkFj6F.js +141 -0
- package/dist/{process-reaper-CEUqe0IY.js → process-reaper-Dv_iMcOo.js} +3 -122
- package/dist/{register.runtime-DVYJo9yv.js → register.runtime-FxCa4GGt.js} +1 -1
- package/dist/register.runtime.js +1 -1
- package/dist/{runtime-COFrLvHN.js → runtime-C3wjx6fQ.js} +12 -3
- package/dist/{service-8gUnMlij.js → service-DepTzi7g.js} +24 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -4
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { c as normalizeAcpxProcessLeaseFile, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, h as normalizeAcpxGatewayInstanceRecord, l as openAcpxProcessLeaseStateStore, m as ACPX_LEGACY_PROCESS_LEASE_FILE, p as ACPX_LEGACY_GATEWAY_INSTANCE_FILE, s as normalizeAcpxProcessLease } from "./process-lease-DiKkFj6F.js";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region extensions/acpx/doctor-contract-api.ts
|
|
5
|
+
function resolveLegacyGatewayInstancePath(stateDir) {
|
|
6
|
+
return path.join(stateDir, ACPX_LEGACY_GATEWAY_INSTANCE_FILE);
|
|
7
|
+
}
|
|
8
|
+
function resolveLegacyProcessLeasePath(stateDir) {
|
|
9
|
+
return path.join(stateDir, "acpx", ACPX_LEGACY_PROCESS_LEASE_FILE);
|
|
10
|
+
}
|
|
11
|
+
async function fileExists(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
return (await fs.stat(filePath)).isFile();
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function readLegacyGatewayInstanceId(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
return (await fs.readFile(filePath, "utf8")).trim() || null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function readLegacyOpenProcessLeases(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return normalizeAcpxProcessLeaseFile(JSON.parse(await fs.readFile(filePath, "utf8"))).leases.filter((lease) => lease.state === "open" || lease.state === "closing");
|
|
28
|
+
} catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function archiveLegacySource(params) {
|
|
33
|
+
const archivedPath = `${params.filePath}.migrated`;
|
|
34
|
+
if (await fileExists(archivedPath)) {
|
|
35
|
+
params.warnings.push(`Left migrated ACPX ${params.label} source in place because ${archivedPath} already exists`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await fs.rename(params.filePath, archivedPath);
|
|
40
|
+
params.changes.push(`Archived ACPX ${params.label} legacy source -> ${archivedPath}`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
params.warnings.push(`Failed archiving ACPX ${params.label} legacy source: ${String(err)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const stateMigrations = [{
|
|
46
|
+
id: "acpx-runtime-state-to-plugin-state",
|
|
47
|
+
label: "ACPX runtime state",
|
|
48
|
+
async detectLegacyState(params) {
|
|
49
|
+
const gatewayInstanceId = await readLegacyGatewayInstanceId(resolveLegacyGatewayInstancePath(params.stateDir));
|
|
50
|
+
const openLeases = await readLegacyOpenProcessLeases(resolveLegacyProcessLeasePath(params.stateDir));
|
|
51
|
+
if (!gatewayInstanceId && openLeases.length === 0) return null;
|
|
52
|
+
const preview = [];
|
|
53
|
+
if (gatewayInstanceId) preview.push(`- ACPX gateway instance id: ${resolveLegacyGatewayInstancePath(params.stateDir)} -> plugin state (${ACPX_GATEWAY_INSTANCE_NAMESPACE})`);
|
|
54
|
+
if (openLeases.length > 0) preview.push(`- ACPX process leases: ${resolveLegacyProcessLeasePath(params.stateDir)} -> plugin state (${openLeases.length} open lease(s))`);
|
|
55
|
+
return { preview };
|
|
56
|
+
},
|
|
57
|
+
async migrateLegacyState(params) {
|
|
58
|
+
const changes = [];
|
|
59
|
+
const warnings = [];
|
|
60
|
+
const gatewayInstancePath = resolveLegacyGatewayInstancePath(params.stateDir);
|
|
61
|
+
const gatewayInstanceId = await readLegacyGatewayInstanceId(gatewayInstancePath);
|
|
62
|
+
const processLeasePath = resolveLegacyProcessLeasePath(params.stateDir);
|
|
63
|
+
const openLeases = await readLegacyOpenProcessLeases(processLeasePath);
|
|
64
|
+
const processLeaseStore = openAcpxProcessLeaseStateStore(params.context.openPluginStateKeyedStore);
|
|
65
|
+
const gatewayStore = params.context.openPluginStateKeyedStore({
|
|
66
|
+
namespace: ACPX_GATEWAY_INSTANCE_NAMESPACE,
|
|
67
|
+
maxEntries: 1
|
|
68
|
+
});
|
|
69
|
+
const existingGateway = normalizeAcpxGatewayInstanceRecord(await gatewayStore.lookup(ACPX_GATEWAY_INSTANCE_KEY));
|
|
70
|
+
const existingLiveLeases = (await processLeaseStore.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => lease != null && (lease.state === "open" || lease.state === "closing"));
|
|
71
|
+
const leaseGatewayIds = new Set(openLeases.map((lease) => lease.gatewayInstanceId));
|
|
72
|
+
const onlyLeaseGatewayId = leaseGatewayIds.size === 1 ? [...leaseGatewayIds][0] : null;
|
|
73
|
+
const canAdoptLegacyGateway = existingGateway && gatewayInstanceId && existingGateway.instanceId !== gatewayInstanceId && onlyLeaseGatewayId === gatewayInstanceId && existingLiveLeases.length === 0;
|
|
74
|
+
const canonicalGatewayInstanceId = canAdoptLegacyGateway || !existingGateway ? gatewayInstanceId ?? onlyLeaseGatewayId : existingGateway.instanceId;
|
|
75
|
+
if (openLeases.length > 0 && (!canonicalGatewayInstanceId || [...leaseGatewayIds].some((leaseGatewayId) => leaseGatewayId !== canonicalGatewayInstanceId))) {
|
|
76
|
+
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");
|
|
77
|
+
return {
|
|
78
|
+
changes,
|
|
79
|
+
warnings
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (canAdoptLegacyGateway && canonicalGatewayInstanceId) {
|
|
83
|
+
await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
|
|
84
|
+
instanceId: canonicalGatewayInstanceId,
|
|
85
|
+
createdAt: Date.now()
|
|
86
|
+
});
|
|
87
|
+
changes.push("Migrated ACPX gateway instance id -> plugin state");
|
|
88
|
+
} else if (canonicalGatewayInstanceId && !existingGateway) {
|
|
89
|
+
await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
|
|
90
|
+
instanceId: canonicalGatewayInstanceId,
|
|
91
|
+
createdAt: Date.now()
|
|
92
|
+
});
|
|
93
|
+
changes.push("Migrated ACPX gateway instance id -> plugin state");
|
|
94
|
+
} else if (gatewayInstanceId && existingGateway?.instanceId !== gatewayInstanceId) warnings.push("Skipped ACPX gateway instance id import because plugin state already differs");
|
|
95
|
+
if (gatewayInstanceId) await archiveLegacySource({
|
|
96
|
+
filePath: gatewayInstancePath,
|
|
97
|
+
label: "gateway-instance-id",
|
|
98
|
+
changes,
|
|
99
|
+
warnings
|
|
100
|
+
});
|
|
101
|
+
if (openLeases.length > 0) {
|
|
102
|
+
let imported = 0;
|
|
103
|
+
let alreadyPresent = 0;
|
|
104
|
+
for (const lease of openLeases) if (await processLeaseStore.registerIfAbsent(lease.leaseId, lease)) imported++;
|
|
105
|
+
else alreadyPresent++;
|
|
106
|
+
changes.push(`Migrated ACPX process leases -> plugin state (${imported} imported, ${alreadyPresent} already present)`);
|
|
107
|
+
await archiveLegacySource({
|
|
108
|
+
filePath: processLeasePath,
|
|
109
|
+
label: "process-leases",
|
|
110
|
+
changes,
|
|
111
|
+
warnings
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
changes,
|
|
116
|
+
warnings
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}];
|
|
120
|
+
//#endregion
|
|
121
|
+
export { stateMigrations };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "./register.runtime-
|
|
1
|
+
import { t as createAcpxRuntimeService } from "./register.runtime-FxCa4GGt.js";
|
|
2
2
|
import { tryDispatchAcpReplyHook } from "openclaw/plugin-sdk/acp-runtime-backend";
|
|
3
3
|
//#region extensions/acpx/index.ts
|
|
4
4
|
/**
|
|
@@ -10,7 +10,10 @@ const plugin = {
|
|
|
10
10
|
name: "ACPX Runtime",
|
|
11
11
|
description: "Embedded ACP runtime backend with plugin-owned session and transport management.",
|
|
12
12
|
register(api) {
|
|
13
|
-
api.registerService(createAcpxRuntimeService({
|
|
13
|
+
api.registerService(createAcpxRuntimeService({
|
|
14
|
+
pluginConfig: api.pluginConfig,
|
|
15
|
+
openKeyedStore: (options) => api.runtime.state.openKeyedStore(options)
|
|
16
|
+
}));
|
|
14
17
|
api.on("reply_dispatch", tryDispatchAcpReplyHook);
|
|
15
18
|
}
|
|
16
19
|
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
//#region extensions/acpx/src/state.ts
|
|
3
|
+
const ACPX_PROCESS_LEASE_NAMESPACE = "process-leases";
|
|
4
|
+
const ACPX_PROCESS_LEASE_MAX_ENTRIES = 4096;
|
|
5
|
+
const ACPX_LEGACY_PROCESS_LEASE_FILE = "process-leases.json";
|
|
6
|
+
const ACPX_GATEWAY_INSTANCE_NAMESPACE = "gateway-instance";
|
|
7
|
+
const ACPX_GATEWAY_INSTANCE_KEY = "current";
|
|
8
|
+
const ACPX_LEGACY_GATEWAY_INSTANCE_FILE = "gateway-instance-id";
|
|
9
|
+
function normalizeAcpxGatewayInstanceRecord(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return;
|
|
11
|
+
const record = value;
|
|
12
|
+
if (typeof record.instanceId !== "string" || !record.instanceId.trim()) return;
|
|
13
|
+
const createdAt = typeof record.createdAt === "number" && Number.isFinite(record.createdAt) ? Math.trunc(record.createdAt) : 0;
|
|
14
|
+
return {
|
|
15
|
+
instanceId: record.instanceId.trim(),
|
|
16
|
+
createdAt
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region extensions/acpx/src/process-lease.ts
|
|
21
|
+
/**
|
|
22
|
+
* Persistent lease store for ACPX wrapper processes. Leases let OpenClaw attach
|
|
23
|
+
* gateway/session identity to spawned ACP processes and clean them up later.
|
|
24
|
+
*/
|
|
25
|
+
/** Environment variable carrying the ACPX process lease id. */
|
|
26
|
+
const OPENCLAW_ACPX_LEASE_ID_ENV = "OPENCLAW_ACPX_LEASE_ID";
|
|
27
|
+
/** Environment variable carrying the owning gateway instance id. */
|
|
28
|
+
const OPENCLAW_GATEWAY_INSTANCE_ID_ENV = "OPENCLAW_GATEWAY_INSTANCE_ID";
|
|
29
|
+
/** CLI argument carrying the ACPX process lease id for platforms without env wrapping. */
|
|
30
|
+
const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
|
|
31
|
+
/** CLI argument carrying the owning gateway instance id. */
|
|
32
|
+
const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
|
|
33
|
+
function normalizeAcpxProcessLease(value) {
|
|
34
|
+
if (typeof value !== "object" || value === null) return;
|
|
35
|
+
const record = value;
|
|
36
|
+
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" || ![
|
|
37
|
+
"open",
|
|
38
|
+
"closing",
|
|
39
|
+
"closed",
|
|
40
|
+
"lost"
|
|
41
|
+
].includes(String(record.state))) return;
|
|
42
|
+
return {
|
|
43
|
+
leaseId: record.leaseId,
|
|
44
|
+
gatewayInstanceId: record.gatewayInstanceId,
|
|
45
|
+
sessionKey: record.sessionKey,
|
|
46
|
+
wrapperRoot: record.wrapperRoot,
|
|
47
|
+
wrapperPath: record.wrapperPath,
|
|
48
|
+
rootPid: record.rootPid,
|
|
49
|
+
...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
|
|
50
|
+
commandHash: record.commandHash,
|
|
51
|
+
startedAt: record.startedAt,
|
|
52
|
+
state: record.state
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function normalizeAcpxProcessLeaseFile(value) {
|
|
56
|
+
const root = typeof value === "object" && value !== null ? value : {};
|
|
57
|
+
return {
|
|
58
|
+
version: 1,
|
|
59
|
+
leases: Array.isArray(root.leases) ? root.leases.map(normalizeAcpxProcessLease).filter((lease) => Boolean(lease)) : []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function openAcpxProcessLeaseStateStore(openKeyedStore) {
|
|
63
|
+
return openKeyedStore({
|
|
64
|
+
namespace: ACPX_PROCESS_LEASE_NAMESPACE,
|
|
65
|
+
maxEntries: ACPX_PROCESS_LEASE_MAX_ENTRIES
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Create a serialized SQLite-backed ACPX process lease store. */
|
|
69
|
+
function createAcpxProcessLeaseStore(params) {
|
|
70
|
+
let updateQueue = Promise.resolve();
|
|
71
|
+
async function update(mutator) {
|
|
72
|
+
const run = updateQueue.then(async () => {
|
|
73
|
+
await mutator();
|
|
74
|
+
});
|
|
75
|
+
updateQueue = run.catch(() => {});
|
|
76
|
+
await run;
|
|
77
|
+
}
|
|
78
|
+
async function readCurrent() {
|
|
79
|
+
await updateQueue;
|
|
80
|
+
return (await params.store.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => Boolean(lease));
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
async load(leaseId) {
|
|
84
|
+
await updateQueue;
|
|
85
|
+
return normalizeAcpxProcessLease(await params.store.lookup(leaseId));
|
|
86
|
+
},
|
|
87
|
+
async listOpen(gatewayInstanceId) {
|
|
88
|
+
return (await readCurrent()).filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
|
|
89
|
+
},
|
|
90
|
+
async save(lease) {
|
|
91
|
+
await update(async () => {
|
|
92
|
+
await params.store.register(lease.leaseId, lease);
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
async markState(leaseId, state) {
|
|
96
|
+
await update(async () => {
|
|
97
|
+
if (state === "closed" || state === "lost") {
|
|
98
|
+
await params.store.delete(leaseId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const lease = normalizeAcpxProcessLease(await params.store.lookup(leaseId));
|
|
102
|
+
if (lease) await params.store.register(leaseId, {
|
|
103
|
+
...lease,
|
|
104
|
+
state
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Create a unique lease id for one ACPX wrapper process. */
|
|
111
|
+
function createAcpxProcessLeaseId() {
|
|
112
|
+
return randomUUID();
|
|
113
|
+
}
|
|
114
|
+
/** Hash a wrapper command so process leases can detect command drift. */
|
|
115
|
+
function hashAcpxProcessCommand(command) {
|
|
116
|
+
return createHash("sha256").update(command).digest("hex");
|
|
117
|
+
}
|
|
118
|
+
function quoteEnvValue(value) {
|
|
119
|
+
return /^[A-Za-z0-9_./:=@+-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
120
|
+
}
|
|
121
|
+
function appendAcpxLeaseArgs(params) {
|
|
122
|
+
return [
|
|
123
|
+
params.command,
|
|
124
|
+
OPENCLAW_ACPX_LEASE_ID_ARG,
|
|
125
|
+
quoteEnvValue(params.leaseId),
|
|
126
|
+
OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
|
|
127
|
+
quoteEnvValue(params.gatewayInstanceId)
|
|
128
|
+
].join(" ");
|
|
129
|
+
}
|
|
130
|
+
/** Add ACPX lease identity to a command through env vars and portable args. */
|
|
131
|
+
function withAcpxLeaseEnvironment(params) {
|
|
132
|
+
if ((params.platform ?? process.platform) === "win32") return appendAcpxLeaseArgs(params);
|
|
133
|
+
return [
|
|
134
|
+
"env",
|
|
135
|
+
`${OPENCLAW_ACPX_LEASE_ID_ENV}=${quoteEnvValue(params.leaseId)}`,
|
|
136
|
+
`${OPENCLAW_GATEWAY_INSTANCE_ID_ENV}=${quoteEnvValue(params.gatewayInstanceId)}`,
|
|
137
|
+
appendAcpxLeaseArgs(params)
|
|
138
|
+
].join(" ");
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { createAcpxProcessLeaseStore as a, normalizeAcpxProcessLeaseFile as c, ACPX_GATEWAY_INSTANCE_KEY as d, ACPX_GATEWAY_INSTANCE_NAMESPACE as f, normalizeAcpxGatewayInstanceRecord as h, createAcpxProcessLeaseId as i, openAcpxProcessLeaseStateStore as l, ACPX_LEGACY_PROCESS_LEASE_FILE as m, OPENCLAW_ACPX_LEASE_ID_ENV as n, hashAcpxProcessCommand as o, ACPX_LEGACY_GATEWAY_INSTANCE_FILE as p, OPENCLAW_GATEWAY_INSTANCE_ID_ARG as r, normalizeAcpxProcessLease as s, OPENCLAW_ACPX_LEASE_ID_ARG as t, withAcpxLeaseEnvironment as u };
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
import "./process-lease-DiKkFj6F.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
|
-
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
4
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
5
|
import { execFile } from "node:child_process";
|
|
8
6
|
import { promisify } from "node:util";
|
|
9
7
|
import fsSync from "node:fs";
|
|
@@ -58,123 +56,6 @@ function splitCommandParts(value) {
|
|
|
58
56
|
return parts;
|
|
59
57
|
}
|
|
60
58
|
//#endregion
|
|
61
|
-
//#region extensions/acpx/src/process-lease.ts
|
|
62
|
-
/**
|
|
63
|
-
* Persistent lease store for ACPX wrapper processes. Leases let OpenClaw attach
|
|
64
|
-
* gateway/session identity to spawned ACP processes and clean them up later.
|
|
65
|
-
*/
|
|
66
|
-
/** Environment variable carrying the ACPX process lease id. */
|
|
67
|
-
const OPENCLAW_ACPX_LEASE_ID_ENV = "OPENCLAW_ACPX_LEASE_ID";
|
|
68
|
-
/** Environment variable carrying the owning gateway instance id. */
|
|
69
|
-
const OPENCLAW_GATEWAY_INSTANCE_ID_ENV = "OPENCLAW_GATEWAY_INSTANCE_ID";
|
|
70
|
-
/** CLI argument carrying the ACPX process lease id for platforms without env wrapping. */
|
|
71
|
-
const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
|
|
72
|
-
/** CLI argument carrying the owning gateway instance id. */
|
|
73
|
-
const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
|
|
74
|
-
const LEASE_FILE = "process-leases.json";
|
|
75
|
-
function normalizeLease(value) {
|
|
76
|
-
if (typeof value !== "object" || value === null) return;
|
|
77
|
-
const record = value;
|
|
78
|
-
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" || ![
|
|
79
|
-
"open",
|
|
80
|
-
"closing",
|
|
81
|
-
"closed",
|
|
82
|
-
"lost"
|
|
83
|
-
].includes(String(record.state))) return;
|
|
84
|
-
return {
|
|
85
|
-
leaseId: record.leaseId,
|
|
86
|
-
gatewayInstanceId: record.gatewayInstanceId,
|
|
87
|
-
sessionKey: record.sessionKey,
|
|
88
|
-
wrapperRoot: record.wrapperRoot,
|
|
89
|
-
wrapperPath: record.wrapperPath,
|
|
90
|
-
rootPid: record.rootPid,
|
|
91
|
-
...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
|
|
92
|
-
commandHash: record.commandHash,
|
|
93
|
-
startedAt: record.startedAt,
|
|
94
|
-
state: record.state
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async function readLeaseFile(filePath) {
|
|
98
|
-
const { value } = await readJsonFileWithFallback(filePath, {
|
|
99
|
-
version: 1,
|
|
100
|
-
leases: []
|
|
101
|
-
});
|
|
102
|
-
return {
|
|
103
|
-
version: 1,
|
|
104
|
-
leases: Array.isArray(value.leases) ? value.leases.map(normalizeLease).filter((lease) => Boolean(lease)) : []
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function writeLeaseFile(filePath, value) {
|
|
108
|
-
return writeJsonFileAtomically(filePath, value);
|
|
109
|
-
}
|
|
110
|
-
/** Create a serialized JSON-backed ACPX process lease store. */
|
|
111
|
-
function createAcpxProcessLeaseStore(params) {
|
|
112
|
-
const filePath = path.join(params.stateDir, LEASE_FILE);
|
|
113
|
-
let updateQueue = Promise.resolve();
|
|
114
|
-
async function update(mutator) {
|
|
115
|
-
const run = updateQueue.then(async () => {
|
|
116
|
-
await fs.mkdir(params.stateDir, { recursive: true });
|
|
117
|
-
await writeLeaseFile(filePath, {
|
|
118
|
-
version: 1,
|
|
119
|
-
leases: mutator((await readLeaseFile(filePath)).leases)
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
updateQueue = run.catch(() => {});
|
|
123
|
-
await run;
|
|
124
|
-
}
|
|
125
|
-
async function readCurrent() {
|
|
126
|
-
await updateQueue;
|
|
127
|
-
return await readLeaseFile(filePath);
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
async load(leaseId) {
|
|
131
|
-
return (await readCurrent()).leases.find((lease) => lease.leaseId === leaseId);
|
|
132
|
-
},
|
|
133
|
-
async listOpen(gatewayInstanceId) {
|
|
134
|
-
return (await readCurrent()).leases.filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
|
|
135
|
-
},
|
|
136
|
-
async save(lease) {
|
|
137
|
-
await update((leases) => [...leases.filter((entry) => entry.leaseId !== lease.leaseId), lease]);
|
|
138
|
-
},
|
|
139
|
-
async markState(leaseId, state) {
|
|
140
|
-
await update((leases) => leases.map((lease) => lease.leaseId === leaseId ? {
|
|
141
|
-
...lease,
|
|
142
|
-
state
|
|
143
|
-
} : lease));
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/** Create a unique lease id for one ACPX wrapper process. */
|
|
148
|
-
function createAcpxProcessLeaseId() {
|
|
149
|
-
return randomUUID();
|
|
150
|
-
}
|
|
151
|
-
/** Hash a wrapper command so process leases can detect command drift. */
|
|
152
|
-
function hashAcpxProcessCommand(command) {
|
|
153
|
-
return createHash("sha256").update(command).digest("hex");
|
|
154
|
-
}
|
|
155
|
-
function quoteEnvValue(value) {
|
|
156
|
-
return /^[A-Za-z0-9_./:=@+-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
157
|
-
}
|
|
158
|
-
function appendAcpxLeaseArgs(params) {
|
|
159
|
-
return [
|
|
160
|
-
params.command,
|
|
161
|
-
OPENCLAW_ACPX_LEASE_ID_ARG,
|
|
162
|
-
quoteEnvValue(params.leaseId),
|
|
163
|
-
OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
|
|
164
|
-
quoteEnvValue(params.gatewayInstanceId)
|
|
165
|
-
].join(" ");
|
|
166
|
-
}
|
|
167
|
-
/** Add ACPX lease identity to a command through env vars and portable args. */
|
|
168
|
-
function withAcpxLeaseEnvironment(params) {
|
|
169
|
-
if ((params.platform ?? process.platform) === "win32") return appendAcpxLeaseArgs(params);
|
|
170
|
-
return [
|
|
171
|
-
"env",
|
|
172
|
-
`${OPENCLAW_ACPX_LEASE_ID_ENV}=${quoteEnvValue(params.leaseId)}`,
|
|
173
|
-
`${OPENCLAW_GATEWAY_INSTANCE_ID_ENV}=${quoteEnvValue(params.gatewayInstanceId)}`,
|
|
174
|
-
appendAcpxLeaseArgs(params)
|
|
175
|
-
].join(" ");
|
|
176
|
-
}
|
|
177
|
-
//#endregion
|
|
178
59
|
//#region extensions/acpx/src/config-schema.ts
|
|
179
60
|
/**
|
|
180
61
|
* ACPX plugin configuration schema and public config types. Runtime setup uses
|
|
@@ -639,4 +520,4 @@ async function reapStaleOpenClawOwnedAcpxOrphans(params) {
|
|
|
639
520
|
};
|
|
640
521
|
}
|
|
641
522
|
//#endregion
|
|
642
|
-
export { resolveAcpxPluginRoot as a,
|
|
523
|
+
export { resolveAcpxPluginRoot as a, splitCommandParts as c, resolveAcpxPluginConfig as i, isOpenClawLeaseAwareAcpxProcessCommand as n, toAcpMcpServers as o, reapStaleOpenClawOwnedAcpxOrphans as r, quoteCommandPart as s, cleanupOpenClawOwnedAcpxProcessTree as t };
|
|
@@ -205,7 +205,7 @@ function createLazyAcpRuntimeProxy(resolveRuntime) {
|
|
|
205
205
|
const ACPX_BACKEND_ID = "acpx";
|
|
206
206
|
let serviceModulePromise = null;
|
|
207
207
|
function loadServiceModule() {
|
|
208
|
-
serviceModulePromise ??= import("./service-
|
|
208
|
+
serviceModulePromise ??= import("./service-DepTzi7g.js");
|
|
209
209
|
return serviceModulePromise;
|
|
210
210
|
}
|
|
211
211
|
async function startRealService(state) {
|
package/dist/register.runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createAcpxRuntimeService } from "./register.runtime-
|
|
1
|
+
import { t as createAcpxRuntimeService } from "./register.runtime-FxCa4GGt.js";
|
|
2
2
|
export { createAcpxRuntimeService };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { i as createAcpxProcessLeaseId, o as hashAcpxProcessCommand, u as withAcpxLeaseEnvironment } from "./process-lease-DiKkFj6F.js";
|
|
1
2
|
import { AcpRuntimeError } from "./runtime-api.js";
|
|
2
|
-
import {
|
|
3
|
-
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { c as splitCommandParts, n as isOpenClawLeaseAwareAcpxProcessCommand, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-Dv_iMcOo.js";
|
|
5
4
|
import fs from "node:fs/promises";
|
|
6
5
|
import path, { resolve } from "node:path";
|
|
6
|
+
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
8
|
import { ACPX_BACKEND_ID, AcpxRuntime as AcpxRuntime$1, createAcpRuntime, createAgentRegistry, createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState } from "acpx/runtime";
|
|
8
9
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
9
10
|
import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
@@ -83,6 +84,11 @@ function readOpenClawLeaseIdFromRecord(record) {
|
|
|
83
84
|
const { openclawLeaseId } = record;
|
|
84
85
|
return typeof openclawLeaseId === "string" ? openclawLeaseId.trim() || void 0 : void 0;
|
|
85
86
|
}
|
|
87
|
+
function readOpenClawGatewayInstanceIdFromRecord(record) {
|
|
88
|
+
if (typeof record !== "object" || record === null) return;
|
|
89
|
+
const { openclawGatewayInstanceId } = record;
|
|
90
|
+
return typeof openclawGatewayInstanceId === "string" ? openclawGatewayInstanceId.trim() || void 0 : void 0;
|
|
91
|
+
}
|
|
86
92
|
function extractGeneratedWrapperPath(command) {
|
|
87
93
|
return splitCommandParts(command ?? "").find((part) => basename(part) === "codex-acp-wrapper.mjs" || basename(part) === "claude-agent-acp-wrapper.mjs") ?? "";
|
|
88
94
|
}
|
|
@@ -502,9 +508,12 @@ var AcpxRuntime = class {
|
|
|
502
508
|
agentRegistry: this.agentRegistry
|
|
503
509
|
});
|
|
504
510
|
if (!rootPid || !rootCommand) return;
|
|
511
|
+
const expectedGatewayInstanceId = readOpenClawGatewayInstanceIdFromRecord(record);
|
|
505
512
|
await cleanupOpenClawOwnedAcpxProcessTree({
|
|
506
513
|
rootPid,
|
|
507
514
|
rootCommand,
|
|
515
|
+
...leaseId ? { expectedLeaseId: leaseId } : {},
|
|
516
|
+
...expectedGatewayInstanceId ? { expectedGatewayInstanceId } : {},
|
|
508
517
|
wrapperRoot: this.wrapperRoot,
|
|
509
518
|
deps: this.processCleanupDeps
|
|
510
519
|
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { n as createLazyAcpRuntimeProxy } from "./register.runtime-
|
|
1
|
+
import { n as createLazyAcpRuntimeProxy } from "./register.runtime-FxCa4GGt.js";
|
|
2
|
+
import { a as createAcpxProcessLeaseStore, d as ACPX_GATEWAY_INSTANCE_KEY, f as ACPX_GATEWAY_INSTANCE_NAMESPACE, h as normalizeAcpxGatewayInstanceRecord, l as openAcpxProcessLeaseStateStore, n as OPENCLAW_ACPX_LEASE_ID_ENV, r as OPENCLAW_GATEWAY_INSTANCE_ID_ARG, t as OPENCLAW_ACPX_LEASE_ID_ARG } from "./process-lease-DiKkFj6F.js";
|
|
2
3
|
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "./runtime-api.js";
|
|
3
|
-
import { a as resolveAcpxPluginRoot, c as
|
|
4
|
+
import { a as resolveAcpxPluginRoot, c as splitCommandParts, i as resolveAcpxPluginConfig, o as toAcpMcpServers, r as reapStaleOpenClawOwnedAcpxOrphans, s as quoteCommandPart, t as cleanupOpenClawOwnedAcpxProcessTree } from "./process-reaper-Dv_iMcOo.js";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import fs from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
7
|
-
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
-
import {
|
|
9
|
+
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
10
10
|
import { inspect } from "node:util";
|
|
11
11
|
import fsSync from "node:fs";
|
|
12
12
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
13
13
|
import os from "node:os";
|
|
14
|
+
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
14
15
|
//#region extensions/acpx/src/codex-trust-config.ts
|
|
15
16
|
/**
|
|
16
17
|
* Builds isolated Codex config for ACPX sessions. It preserves safe inherited
|
|
@@ -846,7 +847,7 @@ const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
|
|
|
846
847
|
const ACPX_BACKEND_ID = "acpx";
|
|
847
848
|
let runtimeModulePromise = null;
|
|
848
849
|
function loadRuntimeModule() {
|
|
849
|
-
runtimeModulePromise ??= import("./runtime-
|
|
850
|
+
runtimeModulePromise ??= import("./runtime-C3wjx6fQ.js");
|
|
850
851
|
return runtimeModulePromise;
|
|
851
852
|
}
|
|
852
853
|
/** Convert ACPX timeout seconds into timer-safe milliseconds. */
|
|
@@ -955,17 +956,21 @@ async function withStartupProbeTimeout(params) {
|
|
|
955
956
|
if (timeout) clearTimeout(timeout);
|
|
956
957
|
}
|
|
957
958
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
959
|
+
function openGatewayInstanceStateStore(openKeyedStore) {
|
|
960
|
+
return openKeyedStore({
|
|
961
|
+
namespace: ACPX_GATEWAY_INSTANCE_NAMESPACE,
|
|
962
|
+
maxEntries: 1
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
async function resolveGatewayInstanceId(openKeyedStore) {
|
|
966
|
+
const store = openGatewayInstanceStateStore(openKeyedStore);
|
|
967
|
+
const existing = normalizeAcpxGatewayInstanceRecord(await store.lookup(ACPX_GATEWAY_INSTANCE_KEY));
|
|
968
|
+
if (existing) return existing.instanceId;
|
|
966
969
|
const next = randomUUID();
|
|
967
|
-
await
|
|
968
|
-
|
|
970
|
+
await store.register(ACPX_GATEWAY_INSTANCE_KEY, {
|
|
971
|
+
instanceId: next,
|
|
972
|
+
createdAt: Date.now()
|
|
973
|
+
});
|
|
969
974
|
return next;
|
|
970
975
|
}
|
|
971
976
|
async function reapOpenAcpxProcessLeases(params) {
|
|
@@ -1017,6 +1022,8 @@ function createAcpxRuntimeService(params = {}) {
|
|
|
1017
1022
|
ctx.logger.info("skipping embedded acpx runtime backend (OPENCLAW_SKIP_ACPX_RUNTIME=1)");
|
|
1018
1023
|
return;
|
|
1019
1024
|
}
|
|
1025
|
+
const openKeyedStore = params.openKeyedStore;
|
|
1026
|
+
if (!openKeyedStore) throw new Error("ACPX runtime service requires plugin keyed state");
|
|
1020
1027
|
const basePluginConfig = await measureAcpxStartup(ctx, "config.resolve", () => resolveAcpxPluginConfig({
|
|
1021
1028
|
rawConfig: params.pluginConfig,
|
|
1022
1029
|
workspaceDir: ctx.workspaceDir
|
|
@@ -1035,8 +1042,8 @@ function createAcpxRuntimeService(params = {}) {
|
|
|
1035
1042
|
await fs.mkdir(pluginConfig.stateDir, { recursive: true });
|
|
1036
1043
|
await fs.mkdir(wrapperRoot, { recursive: true });
|
|
1037
1044
|
});
|
|
1038
|
-
const gatewayInstanceId = await measureAcpxStartup(ctx, "gateway-instance-id", () => resolveGatewayInstanceId(
|
|
1039
|
-
const processLeaseStore = createAcpxProcessLeaseStore({
|
|
1045
|
+
const gatewayInstanceId = await measureAcpxStartup(ctx, "gateway-instance-id", () => resolveGatewayInstanceId(openKeyedStore));
|
|
1046
|
+
const processLeaseStore = createAcpxProcessLeaseStore({ store: openAcpxProcessLeaseStateStore(openKeyedStore) });
|
|
1040
1047
|
const startupReap = await measureAcpxStartup(ctx, "process-leases.reap", () => reapOpenAcpxProcessLeases({
|
|
1041
1048
|
gatewayInstanceId,
|
|
1042
1049
|
leaseStore: processLeaseStore,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/acpx",
|
|
3
|
-
"version": "2026.6.5-beta.
|
|
3
|
+
"version": "2026.6.5-beta.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/acpx",
|
|
9
|
-
"version": "2026.6.5-beta.
|
|
9
|
+
"version": "2026.6.5-beta.5",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@agentclientprotocol/claude-agent-acp": "0.39.0",
|
|
12
12
|
"@zed-industries/codex-acp": "0.15.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/acpx",
|
|
3
|
-
"version": "2026.6.5-beta.
|
|
3
|
+
"version": "2026.6.5-beta.5",
|
|
4
4
|
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"minHostVersion": ">=2026.4.25"
|
|
27
27
|
},
|
|
28
28
|
"compat": {
|
|
29
|
-
"pluginApi": ">=2026.6.5-beta.
|
|
29
|
+
"pluginApi": ">=2026.6.5-beta.5"
|
|
30
30
|
},
|
|
31
31
|
"build": {
|
|
32
|
-
"openclawVersion": "2026.6.5-beta.
|
|
32
|
+
"openclawVersion": "2026.6.5-beta.5",
|
|
33
33
|
"staticAssets": [
|
|
34
34
|
{
|
|
35
35
|
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"skills/**"
|
|
59
59
|
],
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"openclaw": ">=2026.6.5-beta.
|
|
61
|
+
"openclaw": ">=2026.6.5-beta.5"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"openclaw": {
|