@openclaw/acpx 2026.11.3 → 2026.11.5-beta.2

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.
@@ -1,9 +1,9 @@
1
- import { createRequire } from "node:module";
2
1
  import { z } from "zod";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
6
+ import { resolvePackageRuntimeProcessEntrypointUrl, resolveRuntimeWorkerArgv } from "openclaw/plugin-sdk/process-runtime";
7
7
  import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
8
8
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  //#region extensions/acpx/src/config-schema.ts
@@ -32,6 +32,11 @@ const ACPX_PERMISSION_MODES = [
32
32
  ];
33
33
  const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"];
34
34
  const nonEmptyTrimmedString = (message) => z.string({ error: message }).trim().min(1, { error: message });
35
+ const envNameListSchema = (option) => {
36
+ const message = `${option} must be an array of environment variable names`;
37
+ const name = z.string({ error: message }).regex(/^[^=\s]+$/, { error: message }).refine((value) => !value.includes("\0"), { error: message });
38
+ return z.array(name, { error: message }).optional();
39
+ };
35
40
  const McpServerConfigSchema = z.object({
36
41
  command: nonEmptyTrimmedString("command must be a non-empty string").describe("Command to run the MCP server"),
37
42
  args: z.array(z.string({ error: "args must be an array of strings" }), { error: "args must be an array of strings" }).optional().describe("Arguments to pass to the command"),
@@ -48,6 +53,8 @@ const AcpxPluginConfigSchema = z.strictObject({
48
53
  pluginToolsMcpBridge: z.boolean({ error: "pluginToolsMcpBridge must be a boolean" }).optional(),
49
54
  openClawToolsMcpBridge: z.boolean({ error: "openClawToolsMcpBridge must be a boolean" }).optional(),
50
55
  timeoutSeconds: z.number({ error: "timeoutSeconds must be a number >= 0.001" }).min(.001, { error: "timeoutSeconds must be a number >= 0.001" }).default(120),
56
+ agentProcessEnvAllow: envNameListSchema("agentProcessEnvAllow"),
57
+ agentProcessEnvBlock: envNameListSchema("agentProcessEnvBlock"),
51
58
  piSessionCatalog: z.strictObject({ enabled: z.boolean({ error: "piSessionCatalog.enabled must be a boolean" }).default(true) }).optional(),
52
59
  mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
53
60
  agents: z.record(z.string(), z.strictObject({
@@ -189,13 +196,12 @@ function resolveAgentCommand(params) {
189
196
  const MANAGED_MCP_BRIDGES = [[
190
197
  "pluginToolsMcpBridge",
191
198
  "openclaw-plugin-tools",
192
- "plugin-tools-serve"
199
+ "pluginToolsServe"
193
200
  ], [
194
201
  "openClawToolsMcpBridge",
195
202
  "openclaw-tools",
196
- "openclaw-tools-serve"
203
+ "openclawToolsServe"
197
204
  ]];
198
- const requireFromHere = createRequire(import.meta.url);
199
205
  function isAcpxPluginRoot(dir) {
200
206
  return fs.existsSync(path.join(dir, "openclaw.plugin.json")) && fs.existsSync(path.join(dir, "package.json"));
201
207
  }
@@ -266,28 +272,12 @@ function resolveHostPackageRoot(argv1) {
266
272
  cursor = parent;
267
273
  }
268
274
  }
269
- function resolveTsxImportSpecifier() {
270
- try {
271
- return requireFromHere.resolve("tsx");
272
- } catch {
273
- return "tsx";
274
- }
275
- }
276
275
  function resolveManagedToolsMcpServerConfig(entryPoint, moduleUrl = import.meta.url) {
277
276
  const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
278
- const distEntry = path.join(openClawRoot, "dist", "mcp", `${entryPoint}.js`);
279
- if (fs.existsSync(distEntry)) return {
280
- command: process.execPath,
281
- args: [distEntry]
282
- };
283
- const sourceEntry = path.join(openClawRoot, "src", "mcp", `${entryPoint}.ts`);
277
+ const entry = resolvePackageRuntimeProcessEntrypointUrl(entryPoint, openClawRoot);
284
278
  return {
285
279
  command: process.execPath,
286
- args: [
287
- "--import",
288
- resolveTsxImportSpecifier(),
289
- sourceEntry
290
- ]
280
+ args: resolveRuntimeWorkerArgv(entry)
291
281
  };
292
282
  }
293
283
  function resolveConfiguredMcpServers(params) {
@@ -324,7 +314,7 @@ function resolveAcpxPluginConfig(params) {
324
314
  moduleUrl: params.moduleUrl
325
315
  });
326
316
  const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) => {
327
- const cmd = entry.command.trim();
317
+ const cmd = entry.command;
328
318
  const command = path.isAbsolute(cmd) && fs.existsSync(cmd) ? [cmd] : splitCommandParts(cmd);
329
319
  return [normalizeLowercaseStringOrEmpty(name), [...command, ...entry.args ?? []]];
330
320
  }));
@@ -337,6 +327,8 @@ function resolveAcpxPluginConfig(params) {
337
327
  pluginToolsMcpBridge,
338
328
  openClawToolsMcpBridge,
339
329
  timeoutSeconds: normalized.timeoutSeconds,
330
+ agentProcessEnvAllow: normalized.agentProcessEnvAllow,
331
+ agentProcessEnvBlock: normalized.agentProcessEnvBlock,
340
332
  mcpServers,
341
333
  agents
342
334
  };
@@ -65,12 +65,12 @@ async function runAcpHarnessAttempt(params) {
65
65
  sourceReplyDeliveryMode: input.sourceReplyDeliveryMode
66
66
  };
67
67
  let activeRegistered = false;
68
+ let activeAttachment;
68
69
  let timer;
69
70
  assertActive();
70
71
  try {
71
- setActiveEmbeddedRun(input.sessionId, activeRun, sessionKey, input.sessionFile, agentId);
72
+ activeAttachment = setActiveEmbeddedRun(input.sessionId, activeRun, sessionKey, input.sessionFile, agentId);
72
73
  activeRegistered = true;
73
- input.replyOperation?.attachBackend(activeRun);
74
74
  signal.addEventListener("abort", stopDelivery, { once: true });
75
75
  timer = setTimeout(() => {
76
76
  timedOut = true;
@@ -143,7 +143,8 @@ async function runAcpHarnessAttempt(params) {
143
143
  config: input.config,
144
144
  trigger: input.trigger,
145
145
  modelProviderId: input.provider,
146
- modelId: input.modelId
146
+ modelId: input.modelId,
147
+ contextTokenBudget: input.contextTokenBudget ?? input.model.contextWindow
147
148
  },
148
149
  bootstrapContextRunKind: input.bootstrapContextRunKind
149
150
  });
@@ -315,7 +316,7 @@ async function runAcpHarnessAttempt(params) {
315
316
  settled = true;
316
317
  clearTimeout(timer);
317
318
  signal.removeEventListener("abort", stopDelivery);
318
- if (activeRegistered) clearActiveEmbeddedRun(input.sessionId, activeRun, sessionKey, input.sessionFile);
319
+ if (activeRegistered) clearActiveEmbeddedRun(input.sessionId, activeRun, sessionKey, input.sessionFile, void 0, activeAttachment);
319
320
  }
320
321
  return {
321
322
  terminal: timedOut ? {
@@ -1,4 +1,4 @@
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";
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-Cn5bsvi0.mjs";
2
2
  import { parseDateFirstTimestampMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
3
3
  import { createReadStream } from "node:fs";
4
4
  import path from "node:path";
@@ -12,6 +12,7 @@ import { createAcpSessionCatalogAdoption, readSessionCatalogJsonlActivity } from
12
12
  import { sessionCatalogPaging } from "openclaw/plugin-sdk/session-catalog-paging";
13
13
  import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
14
14
  import fs$1 from "node:fs/promises";
15
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
15
16
  import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
16
17
  import { isPathStrictlyInside } from "openclaw/plugin-sdk/file-access-runtime";
17
18
  //#region extensions/acpx/src/pi-session-store.ts
@@ -131,11 +132,7 @@ async function piFileCandidates(env) {
131
132
  candidates
132
133
  };
133
134
  piFileCandidateCache.set(key, entry);
134
- while (piFileCandidateCache.size > PI_FILE_CANDIDATE_CACHE_MAX_ENTRIES) {
135
- const oldest = piFileCandidateCache.keys().next();
136
- if (oldest.done) break;
137
- piFileCandidateCache.delete(oldest.value);
138
- }
135
+ pruneMapToMaxSize(piFileCandidateCache, PI_FILE_CANDIDATE_CACHE_MAX_ENTRIES);
139
136
  try {
140
137
  return await candidates;
141
138
  } catch (error) {
@@ -150,10 +147,18 @@ function parsePiJsonLines(content) {
150
147
  return isRecord(value) ? [value] : [];
151
148
  });
152
149
  }
153
- function piMessageText(content) {
150
+ function piMessageText(content, includeImages = false) {
154
151
  if (typeof content === "string") return content;
155
152
  if (!Array.isArray(content)) return "";
156
- return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n");
153
+ return content.flatMap((part) => {
154
+ if (!isRecord(part)) return [];
155
+ if (part.type === "text" && typeof part.text === "string") return [part.text];
156
+ if (includeImages && part.type === "image") {
157
+ const mimeType = normalizeBoundedOptionalString(part.mimeType, 128);
158
+ return [mimeType ? `[image: ${mimeType}]` : "[image]"];
159
+ }
160
+ return [];
161
+ }).join("\n");
157
162
  }
158
163
  function processSummaryLine(state, line) {
159
164
  const entry = parsePiJsonLines((line.at(-1) === 13 ? line.subarray(0, -1) : line).toString("utf8"))[0];
@@ -214,19 +219,13 @@ async function readPiSessionSummary(candidate) {
214
219
  let scanState;
215
220
  try {
216
221
  const resumable = cached?.identity === candidate.identity && cached.size < candidate.size ? cached : void 0;
217
- scanState = resumable ? {
218
- ...resumable.scanState,
219
- pending: Buffer.from(resumable.scanState.pending)
220
- } : {
222
+ scanState = resumable ? { ...resumable.scanState } : {
221
223
  pending: Buffer.alloc(0),
222
224
  discarding: false,
223
225
  invalid: false
224
226
  };
225
227
  await scanSummaryAppend(candidate, resumable?.size ?? 0, scanState);
226
- const projectedState = {
227
- ...scanState,
228
- pending: Buffer.from(scanState.pending)
229
- };
228
+ const projectedState = { ...scanState };
230
229
  if (!projectedState.discarding && projectedState.pending.length > 0) processSummaryLine(projectedState, projectedState.pending);
231
230
  const { header, name, firstMessage } = projectedState;
232
231
  const version = header?.type === "session" && typeof header.version === "number" ? header.version : 1;
@@ -254,7 +253,6 @@ async function readPiSessionSummary(candidate) {
254
253
  } catch {
255
254
  return cached?.summary;
256
255
  }
257
- if (cached?.summary?.threadId && cached.summary.threadId !== summary?.threadId) threadFileCache.delete(threadCacheKey(cached.storeRoot, cached.summary.threadId));
258
256
  cacheSummary(candidate.file, {
259
257
  ...candidate,
260
258
  summary,
@@ -269,7 +267,17 @@ function summaryMatches(summary, needle) {
269
267
  summary.threadId,
270
268
  summary.name,
271
269
  summary.cwd
272
- ].some((field) => field?.toLocaleLowerCase().includes(needle));
270
+ ].some((field) => field?.toLocaleLowerCase().normalize("NFC").includes(needle));
271
+ }
272
+ async function* scanPiSummaries(candidates) {
273
+ for (let index = 0; index < candidates.length; index += SUMMARY_SCAN_BATCH_SIZE) {
274
+ const { results } = await runTasksWithConcurrency({
275
+ tasks: candidates.slice(index, index + SUMMARY_SCAN_BATCH_SIZE).map((candidate) => () => readPiSessionSummary(candidate)),
276
+ limit: IO_CONCURRENCY,
277
+ throwOnError: true
278
+ });
279
+ for (const summary of results) if (summary) yield summary;
280
+ }
273
281
  }
274
282
  async function listPiSummaryPage(env, params) {
275
283
  const candidates = await piFileCandidates(env);
@@ -277,18 +285,10 @@ async function listPiSummaryPage(env, params) {
277
285
  for (const file of summaryCache.keys()) if (!activeFiles.has(file)) forgetCachedSummary(file);
278
286
  const target = params.offset + params.limit + 1;
279
287
  const matches = [];
280
- const needle = params.searchTerm?.toLocaleLowerCase();
281
- for (let index = 0; index < candidates.length && matches.length < target; index += SUMMARY_SCAN_BATCH_SIZE) {
282
- const batch = candidates.slice(index, index + SUMMARY_SCAN_BATCH_SIZE);
283
- const { results: summaries } = await runTasksWithConcurrency({
284
- tasks: batch.map((candidate) => () => readPiSessionSummary(candidate)),
285
- limit: IO_CONCURRENCY,
286
- throwOnError: true
287
- });
288
- for (const summary of summaries) if (summary && summaryMatches(summary, needle)) {
289
- matches.push(summary);
290
- if (matches.length >= target) break;
291
- }
288
+ const needle = params.searchTerm?.toLocaleLowerCase().normalize("NFC");
289
+ for await (const summary of scanPiSummaries(candidates)) if (summaryMatches(summary, needle)) {
290
+ matches.push(summary);
291
+ if (matches.length >= target) break;
292
292
  }
293
293
  return {
294
294
  summaries: matches.slice(params.offset, params.offset + params.limit),
@@ -296,16 +296,7 @@ async function listPiSummaryPage(env, params) {
296
296
  };
297
297
  }
298
298
  async function findPiSummary(threadId, env) {
299
- const candidates = await piFileCandidates(env);
300
- for (let index = 0; index < candidates.length; index += SUMMARY_SCAN_BATCH_SIZE) {
301
- const { results: summaries } = await runTasksWithConcurrency({
302
- tasks: candidates.slice(index, index + SUMMARY_SCAN_BATCH_SIZE).map((candidate) => () => readPiSessionSummary(candidate)),
303
- limit: IO_CONCURRENCY,
304
- throwOnError: true
305
- });
306
- const match = summaries.find((summary) => summary?.threadId === threadId);
307
- if (match) return match;
308
- }
299
+ for await (const summary of scanPiSummaries(await piFileCandidates(env))) if (summary.threadId === threadId) return summary;
309
300
  }
310
301
  async function readPiSessionFileBaseline(threadId, env) {
311
302
  const summary = await findPiSummary(threadId, env);
@@ -341,19 +332,6 @@ async function readPiSessionById(threadId, env) {
341
332
  //#region extensions/acpx/src/pi-session-catalog.ts
342
333
  const MAX_SEARCH_LENGTH = 500;
343
334
  const isExactPiSessionCursor = sessionCatalogPaging.isExactCursor;
344
- function textFromContent(content) {
345
- if (typeof content === "string") return content;
346
- if (!Array.isArray(content)) return "";
347
- return content.flatMap((part) => {
348
- if (!isRecord(part)) return [];
349
- if (part.type === "text" && typeof part.text === "string") return [part.text];
350
- if (part.type === "image") {
351
- const mimeType = normalizeBoundedOptionalString(part.mimeType, 128);
352
- return [mimeType ? `[image: ${mimeType}]` : "[image]"];
353
- }
354
- return [];
355
- }).join("\n");
356
- }
357
335
  const PI_PARAMETER_MESSAGES = {
358
336
  listNotObject: "Pi session list parameters must be an object",
359
337
  unknownListParameter: (key) => `unknown Pi session list parameter: ${key}`,
@@ -420,7 +398,7 @@ function piMessageItems(entry) {
420
398
  ...modelRef ? { model: modelRef } : {}
421
399
  };
422
400
  if (role === "user") {
423
- const text = textFromContent(message.content);
401
+ const text = piMessageText(message.content, true);
424
402
  return text ? [{
425
403
  ...common,
426
404
  type: "userMessage",
@@ -429,7 +407,7 @@ function piMessageItems(entry) {
429
407
  }
430
408
  if (role === "toolResult") {
431
409
  const toolName = normalizeBoundedOptionalString(message.toolName, 256);
432
- const text = textFromContent(message.content);
410
+ const text = piMessageText(message.content, true);
433
411
  return [{
434
412
  ...common,
435
413
  type: "toolResult",
@@ -454,7 +432,7 @@ function piMessageItems(entry) {
454
432
  if (role === "custom" || role === "hookMessage") {
455
433
  if (message.display !== true) return [];
456
434
  const customType = normalizeBoundedOptionalString(message.customType, 256);
457
- const text = textFromContent(message.content);
435
+ const text = piMessageText(message.content, true);
458
436
  return text ? [{
459
437
  ...common,
460
438
  type: "other",
@@ -505,7 +483,7 @@ function piTranscriptItems(entries) {
505
483
  text: entry.summary
506
484
  }];
507
485
  if (entry.type === "custom_message" && entry.display === true) {
508
- const text = textFromContent(entry.content);
486
+ const text = piMessageText(entry.content, true);
509
487
  return text ? [{
510
488
  ...common,
511
489
  type: "other",
@@ -14,8 +14,8 @@ const PI_SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
14
14
  function piHome(env) {
15
15
  return (process.platform === "win32" ? env.USERPROFILE?.trim() : env.HOME?.trim()) || os.homedir();
16
16
  }
17
- function isPiSessionCatalogPathAbsolute(value, platform = process.platform) {
18
- if (platform !== "win32") return path.posix.isAbsolute(value);
17
+ function isPiSessionCatalogPathAbsolute(value) {
18
+ if (process.platform !== "win32") return path.posix.isAbsolute(value);
19
19
  const root = path.win32.parse(value).root;
20
20
  return path.win32.isAbsolute(value) && root !== "\\" && root !== "/";
21
21
  }