@openclaw/acpx 2026.9.7 → 2026.10.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,431 @@
1
+ import { t as runtime_doctor_migrations_exports } from "./runtime-doctor-migrations-hxP-CXX7.mjs";
2
+ import { c as renderAgentCommand, t as resolveAcpxPluginConfig, u as splitCommandParts } from "./config-76krYh6C.mjs";
3
+ import path from "node:path";
4
+ import { asFiniteNumber, asOptionalObjectRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { createHash } from "node:crypto";
6
+ import fs from "node:fs/promises";
7
+ import { isDeepStrictEqual } from "node:util";
8
+ import { movePathWithCopyFallback } from "openclaw/plugin-sdk/security-runtime";
9
+ //#region extensions/acpx/src/state.ts
10
+ const ACPX_PROCESS_LEASE_NAMESPACE = "process-leases";
11
+ const ACPX_PROCESS_LEASE_MAX_ENTRIES = 4096;
12
+ const ACPX_GATEWAY_INSTANCE_NAMESPACE = "gateway-instance";
13
+ const ACPX_GATEWAY_INSTANCE_KEY = "current";
14
+ function normalizeAcpxGatewayInstanceRecord(value) {
15
+ const record = asOptionalObjectRecord(value);
16
+ const instanceId = normalizeOptionalString(record?.instanceId);
17
+ if (!instanceId) return;
18
+ return {
19
+ instanceId,
20
+ createdAt: Math.trunc(asFiniteNumber(record?.createdAt) ?? 0)
21
+ };
22
+ }
23
+ //#endregion
24
+ //#region extensions/acpx/src/process-lease.ts
25
+ const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
26
+ const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
27
+ /** Synthetic session identity for generated-wrapper health probes. */
28
+ const ACPX_PROBE_LEASE_SESSION_KEY = "openclaw:acpx:probe";
29
+ function readAcpxProcessLeaseIdentity(command) {
30
+ const parts = typeof command === "string" ? Array.from(command.matchAll(/(?:^|\s)(--openclaw-(?:acpx-lease-id|gateway-instance-id))\s+(?:"([^"]*)"|'([^']*)'|(\S+))(?=\s|$)/g)).flatMap((match) => [match[1], match[2] ?? match[3] ?? match[4]]) : command ?? [];
31
+ const leaseIndex = parts.lastIndexOf(OPENCLAW_ACPX_LEASE_ID_ARG);
32
+ const gatewayIndex = parts.lastIndexOf(OPENCLAW_GATEWAY_INSTANCE_ID_ARG);
33
+ const leaseId = leaseIndex >= 0 ? parts[leaseIndex + 1]?.trim() : "";
34
+ const gatewayInstanceId = gatewayIndex >= 0 ? parts[gatewayIndex + 1]?.trim() : "";
35
+ if (!leaseId || !gatewayInstanceId) return;
36
+ return {
37
+ leaseId,
38
+ gatewayInstanceId
39
+ };
40
+ }
41
+ function normalizeAcpxProcessLease(value) {
42
+ const record = asOptionalObjectRecord(value);
43
+ if (!record || typeof record.leaseId !== "string" || typeof record.gatewayInstanceId !== "string" || typeof record.sessionKey !== "string" || typeof record.wrapperRoot !== "string" || typeof record.wrapperPath !== "string" || typeof record.rootPid !== "number" || typeof record.commandHash !== "string" || typeof record.startedAt !== "number" || ![
44
+ "open",
45
+ "closing",
46
+ "closed",
47
+ "lost"
48
+ ].includes(String(record.state))) return;
49
+ return {
50
+ leaseId: record.leaseId,
51
+ gatewayInstanceId: record.gatewayInstanceId,
52
+ sessionKey: record.sessionKey,
53
+ wrapperRoot: record.wrapperRoot,
54
+ wrapperPath: record.wrapperPath,
55
+ rootPid: record.rootPid,
56
+ ...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
57
+ commandHash: record.commandHash,
58
+ startedAt: record.startedAt,
59
+ state: record.state
60
+ };
61
+ }
62
+ function openAcpxProcessLeaseStateStore(openKeyedStore) {
63
+ return openKeyedStore({
64
+ namespace: ACPX_PROCESS_LEASE_NAMESPACE,
65
+ maxEntries: ACPX_PROCESS_LEASE_MAX_ENTRIES,
66
+ overflowPolicy: "reject-new"
67
+ });
68
+ }
69
+ function createAcpxProcessLeaseStore(params) {
70
+ let updateQueue = Promise.resolve();
71
+ async function update(mutator) {
72
+ const run = updateQueue.then(mutator);
73
+ updateQueue = run.catch(() => {});
74
+ await run;
75
+ }
76
+ async function readCurrent() {
77
+ await updateQueue;
78
+ return (await params.store.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => Boolean(lease));
79
+ }
80
+ return {
81
+ async load(leaseId) {
82
+ await updateQueue;
83
+ return normalizeAcpxProcessLease(await params.store.lookup(leaseId));
84
+ },
85
+ async listOpen(gatewayInstanceId) {
86
+ return (await readCurrent()).filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
87
+ },
88
+ async save(lease) {
89
+ await update(async () => {
90
+ await params.store.register(lease.leaseId, lease);
91
+ });
92
+ },
93
+ async markState(leaseId, state) {
94
+ await update(async () => {
95
+ if (state === "closed" || state === "lost") {
96
+ await params.store.delete(leaseId);
97
+ return;
98
+ }
99
+ const lease = normalizeAcpxProcessLease(await params.store.lookup(leaseId));
100
+ if (lease) await params.store.register(leaseId, {
101
+ ...lease,
102
+ state
103
+ });
104
+ });
105
+ }
106
+ };
107
+ }
108
+ /** Hash a wrapper command so process leases can detect command drift. */
109
+ function hashAcpxProcessCommand(command) {
110
+ return createHash("sha256").update(renderAgentCommand(command)).digest("hex");
111
+ }
112
+ /** Append portable wrapper arguments without changing the executable or argument bytes. */
113
+ function withAcpxLeaseArgs(params) {
114
+ return [
115
+ ...splitCommandParts(params.command),
116
+ OPENCLAW_ACPX_LEASE_ID_ARG,
117
+ params.leaseId,
118
+ OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
119
+ params.gatewayInstanceId
120
+ ];
121
+ }
122
+ //#endregion
123
+ //#region extensions/acpx/src/session-owner-migration.ts
124
+ function openStateDirectoryMarker(input) {
125
+ return input.openKeyedStore({
126
+ namespace: "state-directory-migration",
127
+ maxEntries: 1
128
+ });
129
+ }
130
+ async function directoryEntries(directory) {
131
+ return fs.readdir(directory).catch((error) => {
132
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ENOENT") return [];
133
+ throw error;
134
+ });
135
+ }
136
+ function legacyStateDirectory(input) {
137
+ const explicit = (0, runtime_doctor_migrations_exports.asObjectRecord)(input.rawConfig)?.stateDir;
138
+ return typeof explicit === "string" && explicit.trim() ? void 0 : path.resolve(input.workspaceDir?.trim() || process.cwd(), "state");
139
+ }
140
+ async function hasLegacyState(input) {
141
+ const source = legacyStateDirectory(input);
142
+ return Boolean(source && source !== input.stateDir && (await directoryEntries(input.stateDir)).length === 0 && await hasSessionData(source));
143
+ }
144
+ async function hasSessionData(directory) {
145
+ return (await directoryEntries(path.join(directory, "sessions"))).some((name) => name.endsWith(".json"));
146
+ }
147
+ /** Startup and offline Doctor share this one-time relocation of the former default. */
148
+ async function adoptAcpxStateDirectory(input) {
149
+ const result = {
150
+ stateDir: input.stateDir,
151
+ changes: [],
152
+ warnings: []
153
+ };
154
+ const source = legacyStateDirectory(input);
155
+ if (!source || source === input.stateDir) return result;
156
+ let published = false;
157
+ let legacyFallback = false;
158
+ try {
159
+ const marker = openStateDirectoryMarker(input);
160
+ const previous = await marker.lookup("workspace-state-v1");
161
+ if (previous?.destination === input.stateDir && previous.completed) return result;
162
+ const destinationEmpty = (await directoryEntries(input.stateDir)).length === 0;
163
+ if (destinationEmpty) {
164
+ legacyFallback = true;
165
+ legacyFallback = await hasSessionData(source);
166
+ }
167
+ if (previous?.destination === input.stateDir) {
168
+ if (await hasSessionData(input.stateDir)) {
169
+ published = true;
170
+ await marker.register("workspace-state-v1", {
171
+ destination: input.stateDir,
172
+ completed: true
173
+ }, { assertCurrent: input.assertCurrent });
174
+ result.changes.push(`Completed ACPX session state adoption at ${input.stateDir}.`);
175
+ 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.`);
176
+ return result;
177
+ }
178
+ if (!destinationEmpty) {
179
+ legacyFallback = true;
180
+ legacyFallback = await hasSessionData(source);
181
+ throw new Error("Destination became occupied before ACPX state adoption completed");
182
+ }
183
+ }
184
+ if (!destinationEmpty || !legacyFallback) return result;
185
+ 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");
186
+ input.assertCurrent?.();
187
+ await fs.mkdir(path.dirname(input.stateDir), { recursive: true });
188
+ await marker.register("workspace-state-v1", {
189
+ destination: input.stateDir,
190
+ completed: false
191
+ }, { assertCurrent: input.assertCurrent });
192
+ await movePathWithCopyFallback({
193
+ from: source,
194
+ to: input.stateDir,
195
+ assertBeforeMutation: input.assertCurrent,
196
+ onDestinationPublished() {
197
+ published = true;
198
+ }
199
+ });
200
+ result.changes.push(`Migrated ACPX session state from ${source} to ${input.stateDir}.`);
201
+ await marker.register("workspace-state-v1", {
202
+ destination: input.stateDir,
203
+ completed: true
204
+ }, { assertCurrent: input.assertCurrent });
205
+ } catch (error) {
206
+ result.stateDir = !published && legacyFallback ? source : input.stateDir;
207
+ 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.`);
208
+ }
209
+ return result;
210
+ }
211
+ function stateDirectoryInput(input) {
212
+ return {
213
+ rawConfig: input.config.plugins?.entries?.acpx?.config,
214
+ workspaceDir: input.serviceWorkspaceDir,
215
+ stateDir: path.dirname(sessionDirectory(input)),
216
+ openKeyedStore: input.context.openPluginStateKeyedStore
217
+ };
218
+ }
219
+ function sessionDirectory(input) {
220
+ if (!input.serviceWorkspaceDir) throw new Error("ACP ownership repair requires the Gateway service workspace; upgrade OpenClaw Doctor.");
221
+ return path.join(resolveAcpxPluginConfig({
222
+ rawConfig: input.config.plugins?.entries?.acpx?.config,
223
+ workspaceDir: input.serviceWorkspaceDir,
224
+ stateDir: input.stateDir
225
+ }).stateDir, "sessions");
226
+ }
227
+ async function legacyRecords(input, directory = sessionDirectory(input)) {
228
+ 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-"));
229
+ if (ids.length === 0) return {
230
+ directory,
231
+ ids
232
+ };
233
+ const { resolveAcpxSessionResource } = await import("./session-resource-DKJsUJPu.mjs").then((n) => n.n);
234
+ const evidence = await input.context.inspectAcpSessionClaims?.();
235
+ const { decodeAcpxRuntimeHandleState } = await import("acpx/runtime");
236
+ return {
237
+ directory,
238
+ ids: ids.filter((id) => !evidence?.claims.some((claim) => {
239
+ const locator = decodeAcpxRuntimeHandleState(claim.meta.runtimeSessionName);
240
+ return evidence.incomplete.length === 0 && claim.meta.identity?.state === "resolved" && claim.meta.identity.acpxRecordId === id && locator?.acpxRecordId === id && locator.name === resolveAcpxSessionResource(claim);
241
+ })).filter((id) => !id.includes(":oneshot:") || evidence?.claims.some((claim) => {
242
+ const locator = decodeAcpxRuntimeHandleState(claim.meta.runtimeSessionName);
243
+ return claim.meta.identity?.acpxRecordId === id && locator?.name !== resolveAcpxSessionResource(claim);
244
+ })).toSorted()
245
+ };
246
+ }
247
+ function requireStoppedPid(pid) {
248
+ if (pid === void 0 || pid === null) return;
249
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) throw new Error("record process identity is uncertain");
250
+ try {
251
+ process.kill(pid, 0);
252
+ } catch (error) {
253
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ESRCH") return;
254
+ throw new Error("record process liveness cannot be verified", { cause: error });
255
+ }
256
+ throw new Error("record still has a live process; stop the harness before Doctor repair");
257
+ }
258
+ function recordPath(directory, recordId) {
259
+ return path.join(directory, `${encodeURIComponent(recordId)}.json`);
260
+ }
261
+ function matchesClaimRecord(claim, raw, oldId, resource, decode) {
262
+ const state = decode(claim.meta.runtimeSessionName);
263
+ const identity = claim.meta.identity;
264
+ if (!state || !identity || identity.state !== "resolved" || !identity.acpxSessionId && !identity.agentSessionId || state.mode !== claim.meta.mode) return false;
265
+ const recordId = claim.meta.mode === "oneshot" ? oldId : resource;
266
+ const oldLocator = state.name === raw.name && identity.acpxRecordId === oldId && state.acpxRecordId === oldId;
267
+ const newLocator = state.name === resource && identity.acpxRecordId === recordId && state.acpxRecordId === recordId;
268
+ return (oldLocator || newLocator) && state.agent === claim.meta.agent && (!identity.acpxSessionId || identity.acpxSessionId === raw.acp_session_id) && (!identity.agentSessionId || identity.agentSessionId === raw.agent_session_id) && (!state.backendSessionId || state.backendSessionId === raw.acp_session_id) && (!state.agentSessionId || state.agentSessionId === raw.agent_session_id);
269
+ }
270
+ async function migrateRecord(input, directory, oldId, claims, changes, warnings) {
271
+ const sourcePath = recordPath(directory, oldId);
272
+ const sourceBytes = await fs.readFile(sourcePath, "utf8");
273
+ const raw = (0, runtime_doctor_migrations_exports.asObjectRecord)(JSON.parse(sourceBytes));
274
+ if (!raw || raw.acpx_record_id !== oldId || typeof raw.name !== "string" || !raw.name.trim() || raw.name !== oldId && !oldId.startsWith(`${raw.name}:oneshot:`)) throw new Error("record ID/name is not a recognized ACPX locator");
275
+ requireStoppedPid(raw.pid);
276
+ const { resolveAcpxSessionResource } = await import("./session-resource-DKJsUJPu.mjs").then((n) => n.n);
277
+ const { createFileSessionStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState } = await import("acpx/runtime");
278
+ const candidates = claims.filter((claim) => matchesClaimRecord(claim, raw, oldId, resolveAcpxSessionResource(claim), decodeAcpxRuntimeHandleState));
279
+ if (candidates.length !== 1) throw new Error("exactly one current canonical owner claim is required");
280
+ const claim = candidates[0];
281
+ const resource = resolveAcpxSessionResource(claim);
282
+ const oneshot = claim.meta.mode === "oneshot";
283
+ if (oneshot !== (raw.name !== oldId)) throw new Error("canonical claim and backend record mode disagree");
284
+ if (!oneshot && oldId === resource) return;
285
+ const recordId = oneshot ? oldId : resource;
286
+ const leaseStore = openAcpxProcessLeaseStateStore(input.context.openPluginStateKeyedStore);
287
+ const leases = (await leaseStore.entries()).map((row) => ({
288
+ row,
289
+ lease: normalizeAcpxProcessLease(row.value)
290
+ }));
291
+ if (leases.some(({ lease }) => !lease)) throw new Error("process lease evidence is incomplete");
292
+ const commandIdentity = typeof raw.agent_command === "string" ? readAcpxProcessLeaseIdentity(raw.agent_command) : void 0;
293
+ const matchingLeases = leases.filter(({ lease }) => lease.sessionKey === raw.name || lease.sessionKey === resource);
294
+ for (const { lease } of matchingLeases) {
295
+ if (lease.state === "open" || lease.state === "closing") throw new Error("record has a live or uncertain process lease");
296
+ requireStoppedPid(lease.rootPid || void 0);
297
+ if (!commandIdentity || lease.leaseId !== commandIdentity.leaseId || lease.gatewayInstanceId !== commandIdentity.gatewayInstanceId || lease.commandHash !== hashAcpxProcessCommand(String(raw.agent_command))) throw new Error("record lease association does not match its persisted command");
298
+ }
299
+ const destinationPath = recordPath(directory, recordId);
300
+ const candidate = oneshot ? raw : {
301
+ ...raw,
302
+ acpx_record_id: resource,
303
+ name: resource
304
+ };
305
+ const candidateBytes = `${JSON.stringify(candidate, null, 2)}\n`;
306
+ const source = await createFileSessionStore({ stateDir: path.dirname(directory) }).load(oldId);
307
+ if (!source) throw new Error("pinned ACPX reader rejected the source record");
308
+ const temporaryDirectory = await fs.mkdtemp(path.join(directory, ".openclaw-owner-"));
309
+ const temporarySessions = path.join(temporaryDirectory, "sessions");
310
+ const temporaryPath = recordPath(temporarySessions, recordId);
311
+ try {
312
+ await fs.mkdir(temporarySessions, { mode: 448 });
313
+ const file = await fs.open(temporaryPath, "wx", 384);
314
+ try {
315
+ await file.writeFile(candidateBytes);
316
+ await file.sync();
317
+ } finally {
318
+ await file.close();
319
+ }
320
+ const interpreted = await createFileSessionStore({ stateDir: temporaryDirectory }).load(recordId);
321
+ if (!interpreted || !isDeepStrictEqual(interpreted, oneshot ? source : {
322
+ ...source,
323
+ acpxRecordId: resource,
324
+ name: resource
325
+ })) throw new Error("rekey would alter interpreted history/event references; source retained");
326
+ const existing = await fs.readFile(destinationPath, "utf8").catch((error) => {
327
+ if ((0, runtime_doctor_migrations_exports.asObjectRecord)(error)?.code === "ENOENT") return;
328
+ throw error;
329
+ });
330
+ const originalLocator = decodeAcpxRuntimeHandleState(claim.meta.runtimeSessionName);
331
+ if (!oneshot && originalLocator.name === resource && existing === void 0) throw new Error("migrated metadata has no published destination; source retained");
332
+ if (existing !== void 0 && !isDeepStrictEqual(JSON.parse(existing), candidate)) throw new Error("destination conflicts; no files were overwritten");
333
+ if (existing === void 0) await fs.link(temporaryPath, destinationPath);
334
+ const publicationDirectory = await fs.open(directory, "r");
335
+ try {
336
+ await publicationDirectory.sync();
337
+ } finally {
338
+ await publicationDirectory.close();
339
+ }
340
+ if (await fs.readFile(sourcePath, "utf8") !== sourceBytes) throw new Error("source changed during repair");
341
+ if (!isDeepStrictEqual(JSON.parse(await fs.readFile(destinationPath, "utf8")), candidate)) throw new Error("destination changed during repair");
342
+ for (const { row } of matchingLeases) {
343
+ if (!isDeepStrictEqual(await leaseStore.lookup(row.key), row.value)) throw new Error("lease changed during repair");
344
+ await leaseStore.register(row.key, {
345
+ ...row.value,
346
+ sessionKey: resource
347
+ });
348
+ }
349
+ const state = decodeAcpxRuntimeHandleState(claim.meta.runtimeSessionName);
350
+ input.context.updateAcpSessionIdentity({
351
+ claim,
352
+ runtimeSessionName: encodeAcpxRuntimeHandleState({
353
+ ...state,
354
+ name: resource,
355
+ acpxRecordId: recordId
356
+ }),
357
+ acpxRecordId: recordId
358
+ });
359
+ const verified = await input.context.inspectAcpSessionClaims();
360
+ 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");
361
+ changes.push(`Migrated ACP backend history for ${claim.agentId}/${claim.sessionKey} to its owner-qualified resource.`);
362
+ if (!oneshot) await (0, runtime_doctor_migrations_exports.archiveLegacyStateSource)({
363
+ filePath: sourcePath,
364
+ label: "ACP owner record",
365
+ changes,
366
+ warnings
367
+ });
368
+ } finally {
369
+ await fs.rm(temporaryDirectory, {
370
+ recursive: true,
371
+ force: true
372
+ });
373
+ }
374
+ }
375
+ const acpxSessionOwnerMigration = {
376
+ id: "acpx-session-owner-resources",
377
+ label: "ACP session owners",
378
+ doctorOnly: true,
379
+ phase: "after-session-repair",
380
+ collectBackupResources(input) {
381
+ const destination = sessionDirectory(input);
382
+ const explicit = (0, runtime_doctor_migrations_exports.asObjectRecord)(input.config.plugins?.entries?.acpx?.config)?.stateDir;
383
+ return [path.dirname(destination), ...!explicit && input.serviceWorkspaceDir ? [path.join(input.serviceWorkspaceDir, "state")] : []].map((directory) => ({
384
+ path: directory,
385
+ kind: "directory"
386
+ }));
387
+ },
388
+ async detectLegacyState(input) {
389
+ try {
390
+ const directoryInput = stateDirectoryInput(input);
391
+ const marker = legacyStateDirectory(directoryInput) ? await openStateDirectoryMarker(directoryInput).lookup("workspace-state-v1") : void 0;
392
+ const matchingMarker = marker?.destination === directoryInput.stateDir;
393
+ 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."] };
394
+ } catch (error) {
395
+ return { preview: [`ACPX legacy state could not be inspected: ${String(error)}. Run openclaw doctor --fix.`] };
396
+ }
397
+ const { ids } = await legacyRecords(input);
398
+ 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;
399
+ },
400
+ async migrateLegacyState(input) {
401
+ const { stateDir, changes, warnings } = await adoptAcpxStateDirectory(stateDirectoryInput(input));
402
+ if (warnings.length) return {
403
+ changes,
404
+ warnings,
405
+ warningDisposition: "recoverable"
406
+ };
407
+ if (!input.context.inspectAcpSessionClaims || !input.context.updateAcpSessionIdentity) return {
408
+ changes,
409
+ warnings: ["ACP owner repair requires current offline Doctor maintenance authority."]
410
+ };
411
+ const { directory, ids } = await legacyRecords(input, path.join(stateDir, "sessions"));
412
+ const evidence = await input.context.inspectAcpSessionClaims();
413
+ if (evidence.incomplete.length) return {
414
+ changes,
415
+ warnings: [`ACP owner evidence is incomplete; all records retained: ${evidence.incomplete.join("; ")}`]
416
+ };
417
+ for (const oldId of ids) try {
418
+ const current = await input.context.inspectAcpSessionClaims();
419
+ if (current.incomplete.length) throw new Error("canonical ownership evidence became incomplete");
420
+ await migrateRecord(input, directory, oldId, current.claims, changes, warnings);
421
+ } catch (error) {
422
+ warnings.push(`ACP record ${oldId} retained: ${String(error)}`);
423
+ }
424
+ return {
425
+ changes,
426
+ warnings
427
+ };
428
+ }
429
+ };
430
+ //#endregion
431
+ export { hashAcpxProcessCommand as a, acpxSessionOwnerMigration, adoptAcpxStateDirectory, withAcpxLeaseArgs as c, normalizeAcpxGatewayInstanceRecord as d, createAcpxProcessLeaseStore as i, ACPX_GATEWAY_INSTANCE_KEY as l, OPENCLAW_ACPX_LEASE_ID_ARG as n, openAcpxProcessLeaseStateStore as o, OPENCLAW_GATEWAY_INSTANCE_ID_ARG as r, readAcpxProcessLeaseIdentity as s, ACPX_PROBE_LEASE_SESSION_KEY as t, ACPX_GATEWAY_INSTANCE_NAMESPACE as u };
@@ -1,19 +1,7 @@
1
+ import { n as __exportAll } from "./runtime-doctor-migrations-hxP-CXX7.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,4 @@
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";
2
- import path from "node:path";
3
- import fs from "node:fs/promises";
4
- import { archiveLegacyStateSource, asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
1
+ import { t as runtime_doctor_migrations_exports } from "./.setup/runtime-doctor-migrations-hxP-CXX7.mjs";
5
2
  //#region extensions/acpx/doctor-contract-api.ts
6
3
  const ACPX_CONFIG_PATH = [
7
4
  "plugins",
@@ -17,16 +14,16 @@ const legacyConfigRules = RETIRED_ACPX_CONFIG_KEYS.map((key) => ({
17
14
  }));
18
15
  /** Removes retired plugin-owned config without keeping runtime compatibility keys. */
19
16
  function normalizeCompatibilityConfig({ cfg }) {
20
- const entry = asObjectRecord(cfg.plugins?.entries?.acpx);
21
- const pluginConfig = asObjectRecord(entry?.config);
17
+ const entry = (0, runtime_doctor_migrations_exports.asObjectRecord)(cfg.plugins?.entries?.acpx);
18
+ const pluginConfig = (0, runtime_doctor_migrations_exports.asObjectRecord)(entry?.config);
22
19
  const retiredKeys = RETIRED_ACPX_CONFIG_KEYS.filter((key) => Object.hasOwn(pluginConfig ?? {}, key));
23
20
  if (!pluginConfig || retiredKeys.length === 0) return {
24
21
  config: cfg,
25
22
  changes: []
26
23
  };
27
24
  const nextConfig = structuredClone(cfg);
28
- const nextEntry = asObjectRecord(nextConfig.plugins?.entries?.acpx);
29
- const nextPluginConfig = asObjectRecord(nextEntry?.config);
25
+ const nextEntry = (0, runtime_doctor_migrations_exports.asObjectRecord)(nextConfig.plugins?.entries?.acpx);
26
+ const nextPluginConfig = (0, runtime_doctor_migrations_exports.asObjectRecord)(nextEntry?.config);
30
27
  if (!nextPluginConfig) return {
31
28
  config: cfg,
32
29
  changes: []
@@ -37,110 +34,19 @@ function normalizeCompatibilityConfig({ cfg }) {
37
34
  changes: [`Removed retired ACPX plugin config: ${retiredKeys.map((key) => [...ACPX_CONFIG_PATH, key].join(".")).join(", ")}.`]
38
35
  };
39
36
  }
40
- function resolveLegacyGatewayInstancePath(stateDir) {
41
- return path.join(stateDir, ACPX_LEGACY_GATEWAY_INSTANCE_FILE);
42
- }
43
- function resolveLegacyProcessLeasePath(stateDir) {
44
- return path.join(stateDir, "acpx", ACPX_LEGACY_PROCESS_LEASE_FILE);
45
- }
46
- async function readLegacyGatewayInstanceId(filePath) {
47
- try {
48
- return (await fs.readFile(filePath, "utf8")).trim() || null;
49
- } catch {
50
- return null;
51
- }
52
- }
53
- async function readLegacyOpenProcessLeases(filePath) {
54
- try {
55
- return normalizeAcpxProcessLeaseFile(JSON.parse(await fs.readFile(filePath, "utf8"))).leases.filter((lease) => lease.state === "open" || lease.state === "closing");
56
- } catch {
57
- return [];
58
- }
59
- }
60
37
  const stateMigrations = [{
61
- id: "acpx-runtime-state-to-plugin-state",
62
- label: "ACPX runtime state",
63
- async detectLegacyState(params) {
64
- const gatewayInstanceId = await readLegacyGatewayInstanceId(resolveLegacyGatewayInstancePath(params.stateDir));
65
- const openLeases = await readLegacyOpenProcessLeases(resolveLegacyProcessLeasePath(params.stateDir));
66
- if (!gatewayInstanceId && openLeases.length === 0) return null;
67
- const preview = [];
68
- if (gatewayInstanceId) preview.push(`- ACPX gateway instance id: ${resolveLegacyGatewayInstancePath(params.stateDir)} -> plugin state (${ACPX_GATEWAY_INSTANCE_NAMESPACE})`);
69
- if (openLeases.length > 0) preview.push(`- ACPX process leases: ${resolveLegacyProcessLeasePath(params.stateDir)} -> plugin state (${openLeases.length} open lease(s))`);
70
- return { preview };
71
- },
72
- async migrateLegacyState(params) {
73
- const changes = [];
74
- const warnings = [];
75
- const gatewayInstancePath = resolveLegacyGatewayInstancePath(params.stateDir);
76
- const gatewayInstanceId = await readLegacyGatewayInstanceId(gatewayInstancePath);
77
- const processLeasePath = resolveLegacyProcessLeasePath(params.stateDir);
78
- const openLeases = await readLegacyOpenProcessLeases(processLeasePath);
79
- const processLeaseStore = openAcpxProcessLeaseStateStore(params.context.openPluginStateKeyedStore);
80
- const gatewayStore = params.context.openPluginStateKeyedStore({
81
- namespace: ACPX_GATEWAY_INSTANCE_NAMESPACE,
82
- maxEntries: 1
83
- });
84
- const existingGateway = normalizeAcpxGatewayInstanceRecord(await gatewayStore.lookup(ACPX_GATEWAY_INSTANCE_KEY));
85
- const existingLiveLeases = (await processLeaseStore.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => lease != null && (lease.state === "open" || lease.state === "closing"));
86
- const leaseGatewayIds = new Set(openLeases.map((lease) => lease.gatewayInstanceId));
87
- const onlyLeaseGatewayId = leaseGatewayIds.size === 1 ? [...leaseGatewayIds][0] : null;
88
- const canAdoptLegacyGateway = existingGateway && gatewayInstanceId && existingGateway.instanceId !== gatewayInstanceId && onlyLeaseGatewayId === gatewayInstanceId && existingLiveLeases.length === 0;
89
- const canonicalGatewayInstanceId = canAdoptLegacyGateway || !existingGateway ? gatewayInstanceId ?? onlyLeaseGatewayId : existingGateway.instanceId;
90
- if (openLeases.length > 0 && (!canonicalGatewayInstanceId || [...leaseGatewayIds].some((leaseGatewayId) => leaseGatewayId !== canonicalGatewayInstanceId))) {
91
- 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");
92
- return {
93
- changes,
94
- warnings
95
- };
96
- }
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) {
104
- await gatewayStore.register(ACPX_GATEWAY_INSTANCE_KEY, {
105
- instanceId: canonicalGatewayInstanceId,
106
- createdAt: Date.now()
107
- });
108
- changes.push("Migrated ACPX gateway instance id -> plugin state");
109
- } else if (gatewayInstanceId && existingGateway?.instanceId !== gatewayInstanceId) warnings.push("Skipped ACPX gateway instance id import because plugin state already differs");
110
- if (gatewayInstanceId) await archiveLegacyStateSource({
111
- filePath: gatewayInstancePath,
112
- label: "ACPX gateway-instance-id",
113
- changes,
114
- warnings
115
- });
116
- if (openLeases.length > 0) {
117
- let imported = 0;
118
- let alreadyPresent = 0;
119
- for (const lease of openLeases) if (await processLeaseStore.registerIfAbsent(lease.leaseId, lease)) imported++;
120
- else alreadyPresent++;
121
- changes.push(`Migrated ACPX process leases -> plugin state (${imported} imported, ${alreadyPresent} already present)`);
122
- await archiveLegacyStateSource({
123
- filePath: processLeasePath,
124
- label: "ACPX process-leases",
125
- changes,
126
- warnings
127
- });
128
- }
129
- return {
130
- changes,
131
- warnings
132
- };
133
- }
134
- }, {
135
38
  id: "acpx-session-owner-resources",
136
39
  label: "ACP session owners",
137
40
  doctorOnly: true,
138
41
  phase: "after-session-repair",
42
+ async collectBackupResources(input) {
43
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.collectBackupResources(input);
44
+ },
139
45
  async detectLegacyState(input) {
140
- return (await import("./.setup/session-owner-migration-CdJSYRk1.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
46
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.detectLegacyState(input);
141
47
  },
142
48
  async migrateLegacyState(input) {
143
- return (await import("./.setup/session-owner-migration-CdJSYRk1.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
49
+ return (await import("./.setup/session-owner-migration-C73lfijV.mjs")).acpxSessionOwnerMigration.migrateLegacyState(input);
144
50
  }
145
51
  }];
146
52
  //#endregion