@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.
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,11 +1,11 @@
1
- import { s as splitCommandParts } from "./command-line-CPBLOiZM.mjs";
2
1
  import { createRequire } from "node:module";
3
2
  import { z } from "zod";
4
3
  import fs from "node:fs";
5
4
  import path from "node:path";
6
5
  import { fileURLToPath } from "node:url";
7
6
  import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
8
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
8
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  //#region extensions/acpx/src/config-schema.ts
10
10
  /**
11
11
  * ACPX plugin configuration schema and public config types. Runtime setup uses
@@ -56,13 +56,145 @@ const AcpxPluginConfigSchema = z.strictObject({
56
56
  })).optional()
57
57
  });
58
58
  //#endregion
59
+ //#region extensions/acpx/src/codex-adapter.ts
60
+ const CODEX_ACP_PACKAGE = "@agentclientprotocol/codex-acp";
61
+ const CODEX_ACP_BIN = "codex-acp";
62
+ const LEGACY_CODEX_ACP_PACKAGE = "@zed-industries/codex-acp";
63
+ const OPENCLAW_CODEX_CONFIG_ARG = "--openclaw-codex-config";
64
+ //#endregion
65
+ //#region extensions/acpx/src/command-line.ts
66
+ /** Match ACPX's persisted argv identity; scalar records keep their original bytes. */
67
+ function renderAgentCommand(command) {
68
+ return typeof command === "string" ? command : command.map((part) => /^[A-Za-z0-9_@%+=:,./^~-]+$/.test(part) ? part : JSON.stringify(part)).join(" ");
69
+ }
70
+ /** Split a command string into argv-like parts using simple quote/backslash rules. */
71
+ function splitCommandParts(value) {
72
+ if (Array.isArray(value)) return value;
73
+ const windows = process.platform === "win32";
74
+ const parts = [];
75
+ let current = "";
76
+ let quote = null;
77
+ let escaping = false;
78
+ let hasPart = false;
79
+ for (const ch of value) {
80
+ if (escaping) {
81
+ current += ch;
82
+ escaping = false;
83
+ hasPart = true;
84
+ continue;
85
+ }
86
+ if (ch === "\\" && quote !== "'" && !windows) {
87
+ escaping = true;
88
+ hasPart = true;
89
+ continue;
90
+ }
91
+ if (windows && ch === "\"" && quote !== "'") {
92
+ const backslashes = current.match(/\\+$/)?.[0].length ?? 0;
93
+ current = current.slice(0, current.length - backslashes) + "\\".repeat(Math.floor(backslashes / 2));
94
+ if (backslashes % 2 === 1) {
95
+ current += "\"";
96
+ continue;
97
+ }
98
+ }
99
+ if (quote) {
100
+ if (ch === quote) quote = null;
101
+ else current += ch;
102
+ continue;
103
+ }
104
+ if (ch === "'" || ch === "\"") {
105
+ quote = ch;
106
+ hasPart = true;
107
+ continue;
108
+ }
109
+ if (/\s/.test(ch)) {
110
+ if (hasPart) {
111
+ parts.push(current);
112
+ current = "";
113
+ hasPart = false;
114
+ }
115
+ continue;
116
+ }
117
+ current += ch;
118
+ hasPart = true;
119
+ }
120
+ if (escaping) current += "\\";
121
+ if (quote) throw new Error("Invalid agent command: unterminated quote");
122
+ if (hasPart) parts.push(current);
123
+ return parts;
124
+ }
125
+ function basename(value) {
126
+ return value.split(/[\\/]/).pop() ?? value;
127
+ }
128
+ function isEnvAssignment(value) {
129
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value);
130
+ }
131
+ function unwrapEnvCommand(parts) {
132
+ const command = parts.at(0);
133
+ if (!command || basename(command) !== "env") return parts;
134
+ let index = 1;
135
+ while (true) {
136
+ const part = parts.at(index);
137
+ if (!part || !isEnvAssignment(part)) break;
138
+ index += 1;
139
+ }
140
+ return parts.slice(index);
141
+ }
142
+ function matchesExecutableName(value, executableName) {
143
+ const normalized = basename(value).toLowerCase();
144
+ return normalized === executableName || normalized === `${executableName}.exe`;
145
+ }
146
+ function matchesPackageSpec(value, packageName) {
147
+ const normalized = value.trim().toLowerCase();
148
+ return normalized === packageName || normalized.startsWith(`${packageName}@`);
149
+ }
150
+ function stripModuleExtension(value) {
151
+ return value.replace(/\.[cm]?js$/i, "").toLowerCase();
152
+ }
153
+ function isAcpCommand(command, params) {
154
+ if (!command) return false;
155
+ const parts = unwrapEnvCommand(splitCommandParts(command));
156
+ if (!parts.length) return false;
157
+ if (parts.some((part) => matchesPackageSpec(part, params.packageName))) return true;
158
+ const commandName = basename(parts[0] ?? "");
159
+ if (matchesExecutableName(commandName, params.executableName)) return true;
160
+ if (!matchesExecutableName(commandName, "node")) return false;
161
+ const scriptName = stripModuleExtension(basename(parts[1] ?? ""));
162
+ return scriptName === params.executableName || scriptName === `${params.executableName}-wrapper`;
163
+ }
164
+ function isOpenClawBridgeCommand(command) {
165
+ const [executable = "", ...args] = command ? unwrapEnvCommand(splitCommandParts(command)) : [];
166
+ const runtime = basename(executable) !== "openclaw" && (executable === process.execPath || /^(?:node|bun)(?:\.exe)?$/i.test(basename(executable)));
167
+ const scriptName = basename(runtime ? args[0] ?? "" : executable);
168
+ return (runtime ? /^openclaw(?:\.[cm]?js)?$/i.test(scriptName) : scriptName === "openclaw") && args[runtime ? 1 : 0] === "acp";
169
+ }
170
+ function isCodexAcpCommand(command) {
171
+ return isAcpCommand(command, {
172
+ packageName: CODEX_ACP_PACKAGE,
173
+ executableName: "codex-acp"
174
+ });
175
+ }
176
+ function isClaudeAcpCommand(command) {
177
+ return isAcpCommand(command, {
178
+ packageName: "@agentclientprotocol/claude-agent-acp",
179
+ executableName: "claude-agent-acp"
180
+ });
181
+ }
182
+ function resolveAgentCommand(params) {
183
+ const normalizedAgentName = normalizeOptionalLowercaseString(params.agentName);
184
+ if (!normalizedAgentName) return;
185
+ return splitCommandParts(params.agentRegistry.resolve(normalizedAgentName));
186
+ }
187
+ //#endregion
59
188
  //#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";
189
+ const MANAGED_MCP_BRIDGES = [[
190
+ "pluginToolsMcpBridge",
191
+ "openclaw-plugin-tools",
192
+ "plugin-tools-serve"
193
+ ], [
194
+ "openClawToolsMcpBridge",
195
+ "openclaw-tools",
196
+ "openclaw-tools-serve"
197
+ ]];
66
198
  const requireFromHere = createRequire(import.meta.url);
67
199
  function isAcpxPluginRoot(dir) {
68
200
  return fs.existsSync(path.join(dir, "openclaw.plugin.json")) && fs.existsSync(path.join(dir, "package.json"));
@@ -142,13 +274,10 @@ function resolveManagedToolsMcpServerConfig(entryPoint, moduleUrl = import.meta.
142
274
  }
143
275
  function resolveConfiguredMcpServers(params) {
144
276
  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);
277
+ for (const [flag, name] of MANAGED_MCP_BRIDGES) if (params[flag] && resolved[name]) throw new Error(`mcpServers.${name} is reserved when ${flag}=true`);
278
+ for (const [flag, name, entryPoint] of MANAGED_MCP_BRIDGES) if (params[flag]) resolved[name] = resolveManagedToolsMcpServerConfig(entryPoint, params.moduleUrl);
149
279
  return resolved;
150
280
  }
151
- /** Convert OpenClaw MCP server config into ACPX runtime MCP server entries. */
152
281
  function toAcpMcpServers(mcpServers) {
153
282
  return Object.entries(mcpServers).map(([name, server]) => ({
154
283
  name,
@@ -160,15 +289,14 @@ function toAcpMcpServers(mcpServers) {
160
289
  }))
161
290
  }));
162
291
  }
163
- /** Validate and normalize raw ACPX plugin config for runtime startup. */
164
292
  function resolveAcpxPluginConfig(params) {
165
293
  const { rawConfig } = params;
166
294
  const parsed = AcpxPluginConfigSchema.safeParse(rawConfig === void 0 ? {} : rawConfig);
167
295
  if (!parsed.success) throw new Error(formatPluginConfigIssue(parsed.error.issues[0]));
168
296
  const normalized = parsed.data;
169
297
  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"));
298
+ const cwd = path.resolve(normalized.cwd ?? workspaceDir);
299
+ const stateDir = path.resolve(normalized.stateDir ?? path.join(params.stateDir ?? resolveStateDir(), "acpx"));
172
300
  const pluginToolsMcpBridge = normalized.pluginToolsMcpBridge === true;
173
301
  const openClawToolsMcpBridge = normalized.openClawToolsMcpBridge === true;
174
302
  const mcpServers = resolveConfiguredMcpServers({
@@ -196,4 +324,4 @@ function resolveAcpxPluginConfig(params) {
196
324
  };
197
325
  }
198
326
  //#endregion
199
- export { ACPX_NATIVE_AGENT_IDS as a, toAcpMcpServers as i, resolveAcpxPluginRoot as n, AcpxNativeAgentsSchema as o, resolveOpenClawRoot as r, resolveAcpxPluginConfig as t };
327
+ export { isClaudeAcpCommand as a, renderAgentCommand as c, CODEX_ACP_BIN as d, CODEX_ACP_PACKAGE as f, AcpxNativeAgentsSchema as g, ACPX_NATIVE_AGENT_IDS as h, toAcpMcpServers as i, resolveAgentCommand as l, OPENCLAW_CODEX_CONFIG_ARG as m, resolveAcpxPluginRoot as n, isCodexAcpCommand as o, LEGACY_CODEX_ACP_PACKAGE as p, resolveOpenClawRoot as r, isOpenClawBridgeCommand as s, resolveAcpxPluginConfig as t, splitCommandParts as u };
@@ -1,24 +1,18 @@
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";
6
6
  import { resolveNodeHostExecutable } from "openclaw/plugin-sdk/node-host";
7
7
  import { createSessionCatalogFamily, importSessionCatalogHistory, isExternalUserText, listAdoptedSessionCatalogSessions, sessionCatalogAdoptedSessionKey } from "openclaw/plugin-sdk/session-catalog";
8
- import fs$1 from "node:fs/promises";
9
8
  import { resolveAcpSessionAvailability } from "openclaw/plugin-sdk/acp-runtime";
10
9
  import process from "node:process";
11
10
  import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
12
11
  import { sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog-paging";
13
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
12
+ import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
13
+ import fs$1 from "node:fs/promises";
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 };
@@ -1,5 +1,5 @@
1
1
  import { getAcpRuntimeBackend, registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "openclaw/plugin-sdk/acp-runtime-backend";
2
- import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
2
+ import { createLazyRuntimeMethodBinder, createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
3
3
  //#region extensions/acpx/src/runtime-proxy.ts
4
4
  /** Start an ACP turn through a lazy runtime resolver without awaiting resolution up front. */
5
5
  function lazyStartRuntimeTurn(resolveRuntime, input) {
@@ -23,50 +23,27 @@ function lazyStartRuntimeTurn(resolveRuntime, input) {
23
23
  }
24
24
  /** Create an ACP runtime facade backed by an async runtime resolver. */
25
25
  function createLazyAcpRuntimeProxy(resolveRuntime) {
26
+ const bind = createLazyRuntimeMethodBinder(resolveRuntime);
26
27
  return {
27
28
  ownerAwareSessions: 1,
28
- async findSession(input) {
29
- return await (await resolveRuntime()).findSession(input);
30
- },
31
- async shutdown() {
32
- await (await resolveRuntime()).shutdown();
33
- },
34
- async ensureSession(input) {
35
- return await (await resolveRuntime()).ensureSession(input);
36
- },
29
+ findSession: bind((runtime) => runtime.findSession.bind(runtime)),
30
+ shutdown: bind((runtime) => runtime.shutdown.bind(runtime)),
31
+ ensureSession: bind((runtime) => runtime.ensureSession.bind(runtime)),
37
32
  startTurn(input) {
38
33
  return lazyStartRuntimeTurn(resolveRuntime, input);
39
34
  },
40
35
  async *runTurn(input) {
41
36
  yield* (await resolveRuntime()).runTurn(input);
42
37
  },
43
- async getCapabilities(input) {
44
- return await (await resolveRuntime()).getCapabilities(input);
45
- },
46
- async getStatus(input) {
47
- return await (await resolveRuntime()).getStatus(input);
48
- },
49
- async setMode(input) {
50
- await (await resolveRuntime()).setMode(input);
51
- },
52
- async setModel(input) {
53
- await (await resolveRuntime()).setModel(input);
54
- },
55
- async setConfigOption(input) {
56
- return await (await resolveRuntime()).setConfigOption(input);
57
- },
58
- async doctor() {
59
- return await (await resolveRuntime()).doctor();
60
- },
61
- async prepareFreshSession(input) {
62
- await (await resolveRuntime()).prepareFreshSession(input);
63
- },
64
- async cancel(input) {
65
- await (await resolveRuntime()).cancel(input);
66
- },
67
- async close(input) {
68
- await (await resolveRuntime()).close(input);
69
- }
38
+ getCapabilities: bind((runtime) => runtime.getCapabilities.bind(runtime)),
39
+ getStatus: bind((runtime) => runtime.getStatus.bind(runtime)),
40
+ setMode: bind((runtime) => runtime.setMode.bind(runtime)),
41
+ setModel: bind((runtime) => runtime.setModel.bind(runtime)),
42
+ setConfigOption: bind((runtime) => runtime.setConfigOption.bind(runtime)),
43
+ doctor: bind((runtime) => runtime.doctor.bind(runtime)),
44
+ prepareFreshSession: bind((runtime) => runtime.prepareFreshSession.bind(runtime)),
45
+ cancel: bind((runtime) => runtime.cancel.bind(runtime)),
46
+ close: bind((runtime) => runtime.close.bind(runtime))
70
47
  };
71
48
  }
72
49
  //#endregion
@@ -76,7 +53,7 @@ function createLazyAcpRuntimeProxy(resolveRuntime) {
76
53
  * immediately, then imports the heavier service only when a session needs it.
77
54
  */
78
55
  const ACPX_BACKEND_ID = "acpx";
79
- const loadServiceModule = createLazyRuntimeModule(() => import("./service-DMae5pPH.mjs"));
56
+ const loadServiceModule = createLazyRuntimeModule(() => import("./service-BXgGdE56.mjs"));
80
57
  function unregisterOwnedRuntime(runtime) {
81
58
  if (runtime && getAcpRuntimeBackend(ACPX_BACKEND_ID)?.runtime === runtime) unregisterAcpRuntimeBackend(ACPX_BACKEND_ID);
82
59
  }