@openclaw/acpx 2026.9.7 → 2026.9.8

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 CHANGED
@@ -12,6 +12,12 @@ openclaw plugins install @openclaw/acpx
12
12
 
13
13
  Restart the Gateway after installing or updating the plugin.
14
14
 
15
+ The generated Codex and Claude ACP wrappers prefer the installed adapter while its
16
+ entry file exists. If plugin capture cleanup removes that file, the next launch
17
+ uses the existing pinned `npm exec` / `npx` fallback. This may require registry
18
+ access when the pinned package is not cached. Restart the Gateway after updating
19
+ to regenerate wrappers written by an older plugin version.
20
+
15
21
  ## What it provides
16
22
 
17
23
  - ACP-backed agent runtime sessions.
package/assets/icon.png CHANGED
Binary file
@@ -1,3 +1,4 @@
1
+ import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
1
2
  //#region extensions/acpx/src/codex-adapter.ts
2
3
  const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
3
4
  const CODEX_ACP_BIN = "codex-acp";
@@ -66,10 +67,6 @@ function splitCommandParts(value) {
66
67
  }
67
68
  const OPENCLAW_BRIDGE_EXECUTABLE = "openclaw";
68
69
  const OPENCLAW_BRIDGE_SUBCOMMAND = "acp";
69
- function normalizeAgentName(value) {
70
- const normalized = value?.trim().toLowerCase();
71
- return normalized ? normalized : void 0;
72
- }
73
70
  function basename(value) {
74
71
  return value.split(/[\\/]/).pop() ?? value;
75
72
  }
@@ -130,9 +127,9 @@ function isClaudeAcpCommand(command) {
130
127
  });
131
128
  }
132
129
  function resolveAgentCommand(params) {
133
- const normalizedAgentName = normalizeAgentName(params.agentName);
130
+ const normalizedAgentName = normalizeOptionalLowercaseString(params.agentName);
134
131
  if (!normalizedAgentName) return;
135
132
  return splitCommandParts(params.agentRegistry.resolve(normalizedAgentName));
136
133
  }
137
134
  //#endregion
138
- export { renderAgentCommand as a, CODEX_ACP_BIN as c, OPENCLAW_CODEX_CONFIG_ARG as d, normalizeAgentName as i, CODEX_ACP_PACKAGE as l, isCodexAcpCommand as n, resolveAgentCommand as o, isOpenClawBridgeCommand as r, splitCommandParts as s, isClaudeAcpCommand as t, LEGACY_CODEX_ACP_PACKAGE as u };
135
+ export { resolveAgentCommand as a, CODEX_ACP_PACKAGE as c, renderAgentCommand as i, LEGACY_CODEX_ACP_PACKAGE as l, isCodexAcpCommand as n, splitCommandParts as o, isOpenClawBridgeCommand as r, CODEX_ACP_BIN as s, isClaudeAcpCommand as t, OPENCLAW_CODEX_CONFIG_ARG as u };
@@ -1,10 +1,11 @@
1
- import { s as splitCommandParts } from "./command-line-CPBLOiZM.mjs";
1
+ import { o as splitCommandParts } from "./command-line-B72femaL.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { z } from "zod";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
8
+ import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
8
9
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
9
10
  //#region extensions/acpx/src/config-schema.ts
10
11
  /**
@@ -57,12 +58,15 @@ const AcpxPluginConfigSchema = z.strictObject({
57
58
  });
58
59
  //#endregion
59
60
  //#region extensions/acpx/src/config.ts
60
- /**
61
- * Resolves ACPX plugin config from raw user configuration. It locates the
62
- * plugin root, injects optional MCP bridge servers, and applies runtime defaults.
63
- */
64
- const ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME = "openclaw-plugin-tools";
65
- const ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME = "openclaw-tools";
61
+ const MANAGED_MCP_BRIDGES = [[
62
+ "pluginToolsMcpBridge",
63
+ "openclaw-plugin-tools",
64
+ "plugin-tools-serve"
65
+ ], [
66
+ "openClawToolsMcpBridge",
67
+ "openclaw-tools",
68
+ "openclaw-tools-serve"
69
+ ]];
66
70
  const requireFromHere = createRequire(import.meta.url);
67
71
  function isAcpxPluginRoot(dir) {
68
72
  return fs.existsSync(path.join(dir, "openclaw.plugin.json")) && fs.existsSync(path.join(dir, "package.json"));
@@ -142,13 +146,10 @@ function resolveManagedToolsMcpServerConfig(entryPoint, moduleUrl = import.meta.
142
146
  }
143
147
  function resolveConfiguredMcpServers(params) {
144
148
  const resolved = { ...params.mcpServers };
145
- if (params.pluginToolsMcpBridge && resolved[ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME]) throw new Error(`mcpServers.${ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME} is reserved when pluginToolsMcpBridge=true`);
146
- if (params.openClawToolsMcpBridge && resolved[ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME]) throw new Error(`mcpServers.${ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME} is reserved when openClawToolsMcpBridge=true`);
147
- if (params.pluginToolsMcpBridge) resolved[ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME] = resolveManagedToolsMcpServerConfig("plugin-tools-serve", params.moduleUrl);
148
- if (params.openClawToolsMcpBridge) resolved[ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME] = resolveManagedToolsMcpServerConfig("openclaw-tools-serve", params.moduleUrl);
149
+ for (const [flag, name] of MANAGED_MCP_BRIDGES) if (params[flag] && resolved[name]) throw new Error(`mcpServers.${name} is reserved when ${flag}=true`);
150
+ for (const [flag, name, entryPoint] of MANAGED_MCP_BRIDGES) if (params[flag]) resolved[name] = resolveManagedToolsMcpServerConfig(entryPoint, params.moduleUrl);
149
151
  return resolved;
150
152
  }
151
- /** Convert OpenClaw MCP server config into ACPX runtime MCP server entries. */
152
153
  function toAcpMcpServers(mcpServers) {
153
154
  return Object.entries(mcpServers).map(([name, server]) => ({
154
155
  name,
@@ -160,15 +161,14 @@ function toAcpMcpServers(mcpServers) {
160
161
  }))
161
162
  }));
162
163
  }
163
- /** Validate and normalize raw ACPX plugin config for runtime startup. */
164
164
  function resolveAcpxPluginConfig(params) {
165
165
  const { rawConfig } = params;
166
166
  const parsed = AcpxPluginConfigSchema.safeParse(rawConfig === void 0 ? {} : rawConfig);
167
167
  if (!parsed.success) throw new Error(formatPluginConfigIssue(parsed.error.issues[0]));
168
168
  const normalized = parsed.data;
169
169
  const workspaceDir = params.workspaceDir?.trim() || process.cwd();
170
- const cwd = path.resolve(normalized.cwd?.trim() || workspaceDir);
171
- const stateDir = path.resolve(normalized.stateDir?.trim() || path.join(workspaceDir, "state"));
170
+ const cwd = path.resolve(normalized.cwd ?? workspaceDir);
171
+ const stateDir = path.resolve(normalized.stateDir ?? path.join(params.stateDir ?? resolveStateDir(), "acpx"));
172
172
  const pluginToolsMcpBridge = normalized.pluginToolsMcpBridge === true;
173
173
  const openClawToolsMcpBridge = normalized.openClawToolsMcpBridge === true;
174
174
  const mcpServers = resolveConfiguredMcpServers({
@@ -1,5 +1,5 @@
1
1
  import { c as PI_SESSION_READ_COMMAND, i as PI_LOCAL_SESSION_HOST_ID, l as PI_TERMINAL_RESUME_COMMAND, n as piSessionStore, o as PI_SESSIONS_LIST_COMMAND, r as piSessionStoreAvailable, s as PI_SESSION_ID_PATTERN, t as piAcpSessionStoreRoot } from "./pi-session-paths-CIvyk6KB.mjs";
2
- import { parseDateFirstTimestampMs } from "openclaw/plugin-sdk/number-runtime";
2
+ import { parseDateFirstTimestampMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
3
3
  import { createReadStream } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -10,15 +10,9 @@ import { resolveAcpSessionAvailability } from "openclaw/plugin-sdk/acp-runtime";
10
10
  import process from "node:process";
11
11
  import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
12
12
  import { sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog-paging";
13
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
13
+ import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
14
14
  import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
15
15
  import { isPathStrictlyInside, readFileRangeAsync } from "openclaw/plugin-sdk/file-access-runtime";
16
- //#region extensions/acpx/src/pi-session-timestamp.ts
17
- /** Preserve Pi JSONL's date-first string contract while accepting numeric millisecond values. */
18
- function parsePiSessionTimestampMs(value) {
19
- return parseDateFirstTimestampMs(value);
20
- }
21
- //#endregion
22
16
  //#region extensions/acpx/src/pi-session-store.ts
23
17
  const MAX_DISCOVERY_FILES = 1e4;
24
18
  const SUMMARY_SCAN_BATCH_SIZE = 100;
@@ -29,7 +23,6 @@ const APPEND_PROOF_EDGE_BYTES = 65536;
29
23
  const IO_CONCURRENCY = 8;
30
24
  const PI_FILE_CANDIDATE_CACHE_TTL_MS = 32e3;
31
25
  const PI_FILE_CANDIDATE_CACHE_MAX_ENTRIES = 8;
32
- const SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
33
26
  const summaryCache = /* @__PURE__ */ new Map();
34
27
  const threadFileCache = /* @__PURE__ */ new Map();
35
28
  const piFileCandidateCache = /* @__PURE__ */ new Map();
@@ -153,15 +146,11 @@ async function piFileCandidates(env) {
153
146
  function parsePiJsonLines(content) {
154
147
  return content.split(/\r?\n/u).flatMap((line) => {
155
148
  if (!line.trim()) return [];
156
- try {
157
- const value = JSON.parse(line);
158
- return isRecord(value) ? [value] : [];
159
- } catch {
160
- return [];
161
- }
149
+ const value = safeParseJson(line);
150
+ return isRecord(value) ? [value] : [];
162
151
  });
163
152
  }
164
- function textFromContent$2(content) {
153
+ function piMessageText(content) {
165
154
  if (typeof content === "string") return content;
166
155
  if (!Array.isArray(content)) return "";
167
156
  return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n");
@@ -178,7 +167,7 @@ function processSummaryLine(state, line) {
178
167
  return;
179
168
  }
180
169
  if (entry.type === "session_info") state.name = normalizeBoundedOptionalString(entry.name, 1e3);
181
- else if (!state.firstMessage && entry.type === "message" && isRecord(entry.message) && entry.message.role === "user") state.firstMessage = normalizeBoundedOptionalString(textFromContent$2(entry.message.content), 1e3);
170
+ else if (!state.firstMessage && entry.type === "message" && isRecord(entry.message) && entry.message.role === "user") state.firstMessage = normalizeBoundedOptionalString(piMessageText(entry.message.content), 1e3);
182
171
  }
183
172
  function appendSummaryBytes(state, bytes) {
184
173
  if (state.discarding || bytes.length === 0) return;
@@ -268,9 +257,9 @@ async function readPiSessionSummary(candidate) {
268
257
  const { header, name, firstMessage } = projectedState;
269
258
  const version = header?.type === "session" && typeof header.version === "number" ? header.version : 1;
270
259
  const threadId = header?.type === "session" ? normalizeBoundedOptionalString(header.id, 256) : void 0;
271
- if (header && threadId && SESSION_ID_PATTERN.test(threadId)) {
260
+ if (header && threadId && PI_SESSION_ID_PATTERN.test(threadId)) {
272
261
  const cwd = normalizeBoundedOptionalString(header.cwd, 4096);
273
- const createdAt = parsePiSessionTimestampMs(header.timestamp);
262
+ const createdAt = parseDateFirstTimestampMs(header.timestamp);
274
263
  summary = {
275
264
  file: candidate.file,
276
265
  version,
@@ -383,7 +372,7 @@ async function readPiSessionById(threadId, env) {
383
372
  //#region extensions/acpx/src/pi-session-catalog.ts
384
373
  const MAX_SEARCH_LENGTH = 500;
385
374
  const isExactPiSessionCursor = sessionCatalogPaging.isExactCursor;
386
- function textFromContent$1(content) {
375
+ function textFromContent(content) {
387
376
  if (typeof content === "string") return content;
388
377
  if (!Array.isArray(content)) return "";
389
378
  return content.flatMap((part) => {
@@ -421,12 +410,6 @@ async function listLocalPiSessionPage(value) {
421
410
  ...hasMore ? { nextCursor: sessionCatalogPaging.encodeCursor(offset + page.length) } : {}
422
411
  };
423
412
  }
424
- function isoTimestamp(message, entry) {
425
- const value = parsePiSessionTimestampMs(message.timestamp) ?? parsePiSessionTimestampMs(entry.timestamp);
426
- if (value === void 0) return;
427
- const date = new Date(value);
428
- return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
429
- }
430
413
  function jsonText(value, maxLength = 2e4) {
431
414
  try {
432
415
  const text = JSON.stringify(value);
@@ -458,7 +441,7 @@ function piMessageItems(entry) {
458
441
  const message = entry.message;
459
442
  const role = message.role;
460
443
  const id = normalizeBoundedOptionalString(entry.id, 256);
461
- const timestamp = isoTimestamp(message, entry);
444
+ const timestamp = timestampMsToIsoString(parseDateFirstTimestampMs(message.timestamp) ?? parseDateFirstTimestampMs(entry.timestamp));
462
445
  const model = normalizeBoundedOptionalString(message.model, 256);
463
446
  const provider = normalizeBoundedOptionalString(message.provider, 256);
464
447
  const modelRef = provider && model ? `${provider}/${model}` : model;
@@ -468,7 +451,7 @@ function piMessageItems(entry) {
468
451
  ...modelRef ? { model: modelRef } : {}
469
452
  };
470
453
  if (role === "user") {
471
- const text = textFromContent$1(message.content);
454
+ const text = textFromContent(message.content);
472
455
  return text ? [{
473
456
  ...common,
474
457
  type: "userMessage",
@@ -477,7 +460,7 @@ function piMessageItems(entry) {
477
460
  }
478
461
  if (role === "toolResult") {
479
462
  const toolName = normalizeBoundedOptionalString(message.toolName, 256);
480
- const text = textFromContent$1(message.content);
463
+ const text = textFromContent(message.content);
481
464
  return [{
482
465
  ...common,
483
466
  type: "toolResult",
@@ -502,7 +485,7 @@ function piMessageItems(entry) {
502
485
  if (role === "custom" || role === "hookMessage") {
503
486
  if (message.display !== true) return [];
504
487
  const customType = normalizeBoundedOptionalString(message.customType, 256);
505
- const text = textFromContent$1(message.content);
488
+ const text = textFromContent(message.content);
506
489
  return text ? [{
507
490
  ...common,
508
491
  type: "other",
@@ -547,18 +530,13 @@ function piTranscriptItems(entries) {
547
530
  ...id ? { id } : {},
548
531
  ...timestamp ? { timestamp } : {}
549
532
  };
550
- if (entry.type === "compaction" && typeof entry.summary === "string") return [{
551
- ...common,
552
- type: "other",
553
- text: entry.summary
554
- }];
555
- if (entry.type === "branch_summary" && typeof entry.summary === "string") return [{
533
+ if ((entry.type === "compaction" || entry.type === "branch_summary") && typeof entry.summary === "string") return [{
556
534
  ...common,
557
535
  type: "other",
558
536
  text: entry.summary
559
537
  }];
560
538
  if (entry.type === "custom_message" && entry.display === true) {
561
- const text = textFromContent$1(entry.content);
539
+ const text = textFromContent(entry.content);
562
540
  return text ? [{
563
541
  ...common,
564
542
  type: "other",
@@ -594,12 +572,10 @@ function parseCompletePiRows(tail) {
594
572
  for (let index = 0; index < tail.length; index += 1) {
595
573
  if (tail[index] !== 10) continue;
596
574
  const line = tail.subarray(lineStart, index).toString("utf8").trim();
597
- if (line) try {
598
- const value = JSON.parse(line);
575
+ if (line) {
576
+ const value = safeParseJson(line);
599
577
  if (!isRecord(value)) break;
600
578
  entries.push(value);
601
- } catch {
602
- break;
603
579
  }
604
580
  classifiedBytes = index + 1;
605
581
  lineStart = index + 1;
@@ -609,11 +585,6 @@ function parseCompletePiRows(tail) {
609
585
  classifiedBytes
610
586
  };
611
587
  }
612
- function textFromContent(content) {
613
- if (typeof content === "string") return content;
614
- if (!Array.isArray(content)) return;
615
- return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n") || void 0;
616
- }
617
588
  function readFilePath(probe) {
618
589
  return isRecord(probe.upstreamRef) && typeof probe.upstreamRef.filePath === "string" ? probe.upstreamRef.filePath : void 0;
619
590
  }
@@ -663,10 +634,10 @@ async function checkPiSessionUpstreamActivity(probe) {
663
634
  let occurredAt;
664
635
  for (const entry of entries) {
665
636
  if (entry.type !== "message" || !isRecord(entry.message) || entry.message.role !== "user") continue;
666
- const text = textFromContent(entry.message.content);
637
+ const text = piMessageText(entry.message.content);
667
638
  if (!isExternalUserText(probe, text)) continue;
668
639
  humanTurns += 1;
669
- occurredAt = Math.max(occurredAt ?? 0, parsePiSessionTimestampMs(entry.message.timestamp) ?? parsePiSessionTimestampMs(entry.timestamp) ?? stat.mtimeMs);
640
+ occurredAt = Math.max(occurredAt ?? 0, parseDateFirstTimestampMs(entry.message.timestamp) ?? parseDateFirstTimestampMs(entry.timestamp) ?? stat.mtimeMs);
670
641
  }
671
642
  const nextOffset = markerOffset + classifiedBytes;
672
643
  return {
@@ -781,12 +752,6 @@ async function createAdoptedPiSession(params) {
781
752
  function assertPiLocalAccess(hostId, allowProcessHomeFallback) {
782
753
  if (hostId === "gateway" && allowProcessHomeFallback === false && piSessionStore(process.env).usesProcessHomeFallback) throw new Error("local Pi sessions are unavailable in isolated state");
783
754
  }
784
- async function listPiSessions(params) {
785
- return await listLocalPiSessionPage(params);
786
- }
787
- async function readPiSession(params) {
788
- return await readLocalPiTranscriptPage(params);
789
- }
790
755
  function createPiSessionCatalogRuntime(api) {
791
756
  return createSessionCatalogFamily({
792
757
  runtime: api.runtime,
@@ -880,4 +845,4 @@ function createPiSessionCatalogRuntime(api) {
880
845
  }, isExactPiSessionCursor);
881
846
  }
882
847
  //#endregion
883
- export { createPiSessionCatalogRuntime, listPiSessions, readPiSession, requireLocalPiSession };
848
+ export { createPiSessionCatalogRuntime, listLocalPiSessionPage as listPiSessions, readLocalPiTranscriptPage as readPiSession, requireLocalPiSession };
@@ -0,0 +1,311 @@
1
+ import { i as renderAgentCommand, o as splitCommandParts } from "./command-line-B72femaL.mjs";
2
+ import "node:module";
3
+ import { asFiniteNumber, asOptionalObjectRecord, asRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ import { createHash } from "node:crypto";
5
+ //#region \0rolldown/runtime.js
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __exportAll = (all, no_symbols) => {
11
+ let target = {};
12
+ for (var name in all) __defProp(target, name, {
13
+ get: all[name],
14
+ enumerable: true
15
+ });
16
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
17
+ return target;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
21
+ key = keys[i];
22
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
23
+ get: ((k) => from[k]).bind(null, key),
24
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
25
+ });
26
+ }
27
+ return to;
28
+ };
29
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
30
+ //#endregion
31
+ //#region packages/normalization-core/src/record-coerce.ts
32
+ /** Type guard for non-array object records at browser-safe boundaries. */
33
+ function isRecord$1(value) {
34
+ return value !== null && typeof value === "object" && !Array.isArray(value);
35
+ }
36
+ /** Returns a non-array record or null. */
37
+ function asNullableRecord(value) {
38
+ return isRecord$1(value) ? value : null;
39
+ }
40
+ //#endregion
41
+ //#region src/config/channel-config-normalization.ts
42
+ /** Applies one channel-specific doctor migration to every object-shaped account. */
43
+ function normalizeChannelAccounts(params) {
44
+ const rawAccounts = asNullableRecord(params.entry.accounts);
45
+ if (!rawAccounts) return {
46
+ entry: params.entry,
47
+ changed: false
48
+ };
49
+ let changed = false;
50
+ const accounts = { ...rawAccounts };
51
+ for (const [accountId, value] of Object.entries(rawAccounts)) {
52
+ const account = asNullableRecord(value);
53
+ if (!account) continue;
54
+ const normalized = params.normalizeAccount({
55
+ account,
56
+ accountId,
57
+ pathPrefix: `${params.pathPrefix}.accounts.${accountId}`,
58
+ changes: params.changes
59
+ });
60
+ if (normalized.changed) {
61
+ accounts[accountId] = normalized.entry;
62
+ changed = true;
63
+ }
64
+ }
65
+ return changed ? {
66
+ entry: {
67
+ ...params.entry,
68
+ accounts
69
+ },
70
+ changed: true
71
+ } : {
72
+ entry: params.entry,
73
+ changed: false
74
+ };
75
+ }
76
+ /** Applies the same channel-specific doctor migration at root and account scope. */
77
+ function normalizeChannelConfigEntries(params) {
78
+ const changes = params.changes ?? [];
79
+ const channels = params.cfg.channels;
80
+ const entry = asNullableRecord(channels?.[params.channelId]);
81
+ if (!entry) return {
82
+ config: params.cfg,
83
+ changes
84
+ };
85
+ const channelPath = `channels.${params.channelId}`;
86
+ const root = params.normalizeEntry({
87
+ entry,
88
+ pathPrefix: channelPath,
89
+ changes
90
+ });
91
+ const accounts = normalizeChannelAccounts({
92
+ entry: root.entry,
93
+ pathPrefix: channelPath,
94
+ changes,
95
+ normalizeAccount: (accountParams) => params.normalizeEntry({
96
+ entry: accountParams.account,
97
+ accountId: accountParams.accountId,
98
+ pathPrefix: accountParams.pathPrefix,
99
+ changes: accountParams.changes
100
+ })
101
+ });
102
+ if (!root.changed && !accounts.changed) return {
103
+ config: params.cfg,
104
+ changes
105
+ };
106
+ return {
107
+ config: {
108
+ ...params.cfg,
109
+ channels: {
110
+ ...channels,
111
+ [params.channelId]: accounts.entry
112
+ }
113
+ },
114
+ changes
115
+ };
116
+ }
117
+ //#endregion
118
+ //#region src/plugin-sdk/legacy-webhook-listener-migration.ts
119
+ /** Preserve explicitly configured webhook listeners while moving ingress onto Gateway routes. */
120
+ function createLegacyWebhookListenerDoctorContract(params) {
121
+ const portKey = params.portKey ?? "webhookPort";
122
+ const hostKey = params.hostKey === void 0 ? "webhookHost" : params.hostKey;
123
+ const source = (entry) => params.webhookKey ? asNullableRecord(entry[params.webhookKey]) : entry;
124
+ const hasLegacy = (value) => {
125
+ const entry = asNullableRecord(value);
126
+ const listener = entry && source(entry);
127
+ return Boolean(listener && (Object.hasOwn(listener, portKey) || hostKey && Object.hasOwn(listener, hostKey)));
128
+ };
129
+ const prefix = `channels.${params.channelKey}`;
130
+ return {
131
+ legacyConfigRules: [{
132
+ path: ["channels", params.channelKey],
133
+ message: `${prefix} webhook listeners moved to Gateway routes. Run "openclaw doctor --fix" to preserve explicitly configured listener settings as legacyWebhook.`,
134
+ match: (value) => {
135
+ const accounts = asNullableRecord(asNullableRecord(value)?.accounts);
136
+ return hasLegacy(value) || Object.values(accounts ?? {}).some(hasLegacy);
137
+ }
138
+ }],
139
+ normalizeCompatibilityConfig: ({ cfg }) => {
140
+ const root = asNullableRecord(asNullableRecord(cfg.channels)?.[params.channelKey]);
141
+ const inherited = root && source(root);
142
+ const canonicalRoot = asNullableRecord(root?.legacyWebhook);
143
+ return normalizeChannelConfigEntries({
144
+ cfg,
145
+ channelId: params.channelKey,
146
+ normalizeEntry: ({ entry, accountId, pathPrefix, changes }) => {
147
+ const listener = source(entry);
148
+ if (!listener || !hasLegacy(entry)) return {
149
+ entry,
150
+ changed: false
151
+ };
152
+ const next = { ...entry };
153
+ const port = Object.hasOwn(listener, portKey) ? listener[portKey] : (accountId ? canonicalRoot?.port ?? inherited?.[portKey] : void 0) ?? params.defaultPort;
154
+ const inheritedHost = accountId ? canonicalRoot ? canonicalRoot.host : (hostKey ? inherited?.[hostKey] : void 0) ?? params.defaultHost : params.defaultHost;
155
+ const host = hostKey ? listener[hostKey] ?? inheritedHost : inheritedHost;
156
+ const legacyPath = [pathPrefix, params.webhookKey].filter(Boolean).join(".");
157
+ if (Object.hasOwn(entry, "legacyWebhook")) changes.push(`Removed ${legacyPath} legacy listener keys; ${pathPrefix}.legacyWebhook is already configured.`);
158
+ else if (accountId && root?.legacyWebhook === false) changes.push(`Removed ${legacyPath} legacy listener keys; ${prefix}.legacyWebhook: false keeps this account's inherited listener disabled.`);
159
+ else {
160
+ next.legacyWebhook = {
161
+ port,
162
+ ...host !== void 0 ? { host } : {}
163
+ };
164
+ changes.push(`Moved ${legacyPath} listener settings to ${pathPrefix}.legacyWebhook. Point the external callback or reverse proxy at the Gateway port and webhook path, verify delivery, then set legacyWebhook: false to disable legacy forwarding.`);
165
+ }
166
+ const updated = params.webhookKey ? { ...listener } : next;
167
+ delete updated[portKey];
168
+ if (hostKey) delete updated[hostKey];
169
+ if (params.webhookKey) {
170
+ if (Object.keys(updated).length) next[params.webhookKey] = updated;
171
+ else delete next[params.webhookKey];
172
+ }
173
+ return {
174
+ entry: next,
175
+ changed: true
176
+ };
177
+ }
178
+ });
179
+ }
180
+ };
181
+ }
182
+ //#endregion
183
+ //#region \0openclaw:bundled-sdk:openclaw/plugin-sdk/runtime-doctor-migrations
184
+ var runtime_doctor_migrations_exports = /* @__PURE__ */ __exportAll({ createLegacyWebhookListenerDoctorContract: () => createLegacyWebhookListenerDoctorContract });
185
+ import * as import_openclaw_plugin_sdk_runtime_doctor_migrations from "openclaw/plugin-sdk/runtime-doctor-migrations";
186
+ __reExport(runtime_doctor_migrations_exports, import_openclaw_plugin_sdk_runtime_doctor_migrations);
187
+ //#endregion
188
+ //#region extensions/acpx/src/state.ts
189
+ const ACPX_PROCESS_LEASE_NAMESPACE = "process-leases";
190
+ const ACPX_PROCESS_LEASE_MAX_ENTRIES = 4096;
191
+ const ACPX_LEGACY_PROCESS_LEASE_FILE = "process-leases.json";
192
+ const ACPX_GATEWAY_INSTANCE_NAMESPACE = "gateway-instance";
193
+ const ACPX_GATEWAY_INSTANCE_KEY = "current";
194
+ const ACPX_LEGACY_GATEWAY_INSTANCE_FILE = "gateway-instance-id";
195
+ function normalizeAcpxGatewayInstanceRecord(value) {
196
+ const record = asOptionalObjectRecord(value);
197
+ const instanceId = normalizeOptionalString(record?.instanceId);
198
+ if (!instanceId) return;
199
+ return {
200
+ instanceId,
201
+ createdAt: Math.trunc(asFiniteNumber(record?.createdAt) ?? 0)
202
+ };
203
+ }
204
+ //#endregion
205
+ //#region extensions/acpx/src/process-lease.ts
206
+ const OPENCLAW_ACPX_LEASE_ID_ARG = "--openclaw-acpx-lease-id";
207
+ const OPENCLAW_GATEWAY_INSTANCE_ID_ARG = "--openclaw-gateway-instance-id";
208
+ /** Synthetic session identity for generated-wrapper health probes. */
209
+ const ACPX_PROBE_LEASE_SESSION_KEY = "openclaw:acpx:probe";
210
+ function readAcpxProcessLeaseIdentity(command) {
211
+ 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 ?? [];
212
+ const leaseIndex = parts.lastIndexOf(OPENCLAW_ACPX_LEASE_ID_ARG);
213
+ const gatewayIndex = parts.lastIndexOf(OPENCLAW_GATEWAY_INSTANCE_ID_ARG);
214
+ const leaseId = leaseIndex >= 0 ? parts[leaseIndex + 1]?.trim() : "";
215
+ const gatewayInstanceId = gatewayIndex >= 0 ? parts[gatewayIndex + 1]?.trim() : "";
216
+ if (!leaseId || !gatewayInstanceId) return;
217
+ return {
218
+ leaseId,
219
+ gatewayInstanceId
220
+ };
221
+ }
222
+ function normalizeAcpxProcessLease(value) {
223
+ const record = asOptionalObjectRecord(value);
224
+ 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" || ![
225
+ "open",
226
+ "closing",
227
+ "closed",
228
+ "lost"
229
+ ].includes(String(record.state))) return;
230
+ return {
231
+ leaseId: record.leaseId,
232
+ gatewayInstanceId: record.gatewayInstanceId,
233
+ sessionKey: record.sessionKey,
234
+ wrapperRoot: record.wrapperRoot,
235
+ wrapperPath: record.wrapperPath,
236
+ rootPid: record.rootPid,
237
+ ...typeof record.processGroupId === "number" ? { processGroupId: record.processGroupId } : {},
238
+ commandHash: record.commandHash,
239
+ startedAt: record.startedAt,
240
+ state: record.state
241
+ };
242
+ }
243
+ function normalizeAcpxProcessLeaseFile(value) {
244
+ const root = asRecord(value);
245
+ return {
246
+ version: 1,
247
+ leases: Array.isArray(root.leases) ? root.leases.map(normalizeAcpxProcessLease).filter((lease) => Boolean(lease)) : []
248
+ };
249
+ }
250
+ function openAcpxProcessLeaseStateStore(openKeyedStore) {
251
+ return openKeyedStore({
252
+ namespace: ACPX_PROCESS_LEASE_NAMESPACE,
253
+ maxEntries: ACPX_PROCESS_LEASE_MAX_ENTRIES,
254
+ overflowPolicy: "reject-new"
255
+ });
256
+ }
257
+ function createAcpxProcessLeaseStore(params) {
258
+ let updateQueue = Promise.resolve();
259
+ async function update(mutator) {
260
+ const run = updateQueue.then(mutator);
261
+ updateQueue = run.catch(() => {});
262
+ await run;
263
+ }
264
+ async function readCurrent() {
265
+ await updateQueue;
266
+ return (await params.store.entries()).map((entry) => normalizeAcpxProcessLease(entry.value)).filter((lease) => Boolean(lease));
267
+ }
268
+ return {
269
+ async load(leaseId) {
270
+ await updateQueue;
271
+ return normalizeAcpxProcessLease(await params.store.lookup(leaseId));
272
+ },
273
+ async listOpen(gatewayInstanceId) {
274
+ return (await readCurrent()).filter((lease) => (lease.state === "open" || lease.state === "closing") && (!gatewayInstanceId || lease.gatewayInstanceId === gatewayInstanceId));
275
+ },
276
+ async save(lease) {
277
+ await update(async () => {
278
+ await params.store.register(lease.leaseId, lease);
279
+ });
280
+ },
281
+ async markState(leaseId, state) {
282
+ await update(async () => {
283
+ if (state === "closed" || state === "lost") {
284
+ await params.store.delete(leaseId);
285
+ return;
286
+ }
287
+ const lease = normalizeAcpxProcessLease(await params.store.lookup(leaseId));
288
+ if (lease) await params.store.register(leaseId, {
289
+ ...lease,
290
+ state
291
+ });
292
+ });
293
+ }
294
+ };
295
+ }
296
+ /** Hash a wrapper command so process leases can detect command drift. */
297
+ function hashAcpxProcessCommand(command) {
298
+ return createHash("sha256").update(renderAgentCommand(command)).digest("hex");
299
+ }
300
+ /** Append portable wrapper arguments without changing the executable or argument bytes. */
301
+ function withAcpxLeaseArgs(params) {
302
+ return [
303
+ ...splitCommandParts(params.command),
304
+ OPENCLAW_ACPX_LEASE_ID_ARG,
305
+ params.leaseId,
306
+ OPENCLAW_GATEWAY_INSTANCE_ID_ARG,
307
+ params.gatewayInstanceId
308
+ ];
309
+ }
310
+ //#endregion
311
+ export { __exportAll as _, hashAcpxProcessCommand as a, openAcpxProcessLeaseStateStore as c, ACPX_GATEWAY_INSTANCE_KEY as d, ACPX_GATEWAY_INSTANCE_NAMESPACE as f, runtime_doctor_migrations_exports as g, 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 };