@openclaw/acpx 2026.10.10 → 2026.11.5-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.
@@ -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
  // 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({
@@ -186,13 +193,12 @@ function resolveAgentCommand(params) {
186
193
  const MANAGED_MCP_BRIDGES = [[
187
194
  "pluginToolsMcpBridge",
188
195
  "openclaw-plugin-tools",
189
- "plugin-tools-serve"
196
+ "pluginToolsServe"
190
197
  ], [
191
198
  "openClawToolsMcpBridge",
192
199
  "openclaw-tools",
193
- "openclaw-tools-serve"
200
+ "openclawToolsServe"
194
201
  ]];
195
- const requireFromHere = createRequire(import.meta.url);
196
202
  function isAcpxPluginRoot(dir) {
197
203
  return fs.existsSync(path.join(dir, "openclaw.plugin.json")) && fs.existsSync(path.join(dir, "package.json"));
198
204
  }
@@ -263,28 +269,12 @@ function resolveHostPackageRoot(argv1) {
263
269
  cursor = parent;
264
270
  }
265
271
  }
266
- function resolveTsxImportSpecifier() {
267
- try {
268
- return requireFromHere.resolve("tsx");
269
- } catch {
270
- return "tsx";
271
- }
272
- }
273
272
  function resolveManagedToolsMcpServerConfig(entryPoint, moduleUrl = import.meta.url) {
274
273
  const openClawRoot = resolveOpenClawRoot(resolveAcpxPluginRoot(moduleUrl));
275
- const distEntry = path.join(openClawRoot, "dist", "mcp", `${entryPoint}.js`);
276
- if (fs.existsSync(distEntry)) return {
277
- command: process.execPath,
278
- args: [distEntry]
279
- };
280
- const sourceEntry = path.join(openClawRoot, "src", "mcp", `${entryPoint}.ts`);
274
+ const entry = resolvePackageRuntimeProcessEntrypointUrl(entryPoint, openClawRoot);
281
275
  return {
282
276
  command: process.execPath,
283
- args: [
284
- "--import",
285
- resolveTsxImportSpecifier(),
286
- sourceEntry
287
- ]
277
+ args: resolveRuntimeWorkerArgv(entry)
288
278
  };
289
279
  }
290
280
  function resolveConfiguredMcpServers(params) {
@@ -321,7 +311,7 @@ function resolveAcpxPluginConfig(params) {
321
311
  moduleUrl: params.moduleUrl
322
312
  });
323
313
  const agents = Object.fromEntries(Object.entries(normalized.agents ?? {}).map(([name, entry]) => {
324
- const cmd = entry.command.trim();
314
+ const cmd = entry.command;
325
315
  const command = path.isAbsolute(cmd) && fs.existsSync(cmd) ? [cmd] : splitCommandParts(cmd);
326
316
  return [normalizeLowercaseStringOrEmpty(name), [...command, ...entry.args ?? []]];
327
317
  }));
@@ -334,6 +324,8 @@ function resolveAcpxPluginConfig(params) {
334
324
  pluginToolsMcpBridge,
335
325
  openClawToolsMcpBridge,
336
326
  timeoutSeconds: normalized.timeoutSeconds,
327
+ agentProcessEnvAllow: normalized.agentProcessEnvAllow,
328
+ agentProcessEnvBlock: normalized.agentProcessEnvBlock,
337
329
  mcpServers,
338
330
  agents
339
331
  };
@@ -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-Co5515li.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-pUr6hnVA.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
  // 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);
@@ -340,19 +331,6 @@ async function readPiSessionById(threadId, env) {
340
331
  // extensions/acpx/src/pi-session-catalog.ts
341
332
  const MAX_SEARCH_LENGTH = 500;
342
333
  const isExactPiSessionCursor = sessionCatalogPaging.isExactCursor;
343
- function textFromContent(content) {
344
- if (typeof content === "string") return content;
345
- if (!Array.isArray(content)) return "";
346
- return content.flatMap((part) => {
347
- if (!isRecord(part)) return [];
348
- if (part.type === "text" && typeof part.text === "string") return [part.text];
349
- if (part.type === "image") {
350
- const mimeType = normalizeBoundedOptionalString(part.mimeType, 128);
351
- return [mimeType ? `[image: ${mimeType}]` : "[image]"];
352
- }
353
- return [];
354
- }).join("\n");
355
- }
356
334
  const PI_PARAMETER_MESSAGES = {
357
335
  listNotObject: "Pi session list parameters must be an object",
358
336
  unknownListParameter: (key) => `unknown Pi session list parameter: ${key}`,
@@ -419,7 +397,7 @@ function piMessageItems(entry) {
419
397
  ...modelRef ? { model: modelRef } : {}
420
398
  };
421
399
  if (role === "user") {
422
- const text = textFromContent(message.content);
400
+ const text = piMessageText(message.content, true);
423
401
  return text ? [{
424
402
  ...common,
425
403
  type: "userMessage",
@@ -428,7 +406,7 @@ function piMessageItems(entry) {
428
406
  }
429
407
  if (role === "toolResult") {
430
408
  const toolName = normalizeBoundedOptionalString(message.toolName, 256);
431
- const text = textFromContent(message.content);
409
+ const text = piMessageText(message.content, true);
432
410
  return [{
433
411
  ...common,
434
412
  type: "toolResult",
@@ -453,7 +431,7 @@ function piMessageItems(entry) {
453
431
  if (role === "custom" || role === "hookMessage") {
454
432
  if (message.display !== true) return [];
455
433
  const customType = normalizeBoundedOptionalString(message.customType, 256);
456
- const text = textFromContent(message.content);
434
+ const text = piMessageText(message.content, true);
457
435
  return text ? [{
458
436
  ...common,
459
437
  type: "other",
@@ -504,7 +482,7 @@ function piTranscriptItems(entries) {
504
482
  text: entry.summary
505
483
  }];
506
484
  if (entry.type === "custom_message" && entry.display === true) {
507
- const text = textFromContent(entry.content);
485
+ const text = piMessageText(entry.content, true);
508
486
  return text ? [{
509
487
  ...common,
510
488
  type: "other",
@@ -13,8 +13,8 @@ const PI_SESSION_ID_PATTERN = /^(?!-)[A-Za-z0-9._:-]{1,256}$/u;
13
13
  function piHome(env) {
14
14
  return (process.platform === "win32" ? env.USERPROFILE?.trim() : env.HOME?.trim()) || os.homedir();
15
15
  }
16
- function isPiSessionCatalogPathAbsolute(value, platform = process.platform) {
17
- if (platform !== "win32") return path.posix.isAbsolute(value);
16
+ function isPiSessionCatalogPathAbsolute(value) {
17
+ if (process.platform !== "win32") return path.posix.isAbsolute(value);
18
18
  const root = path.win32.parse(value).root;
19
19
  return path.win32.isAbsolute(value) && root !== "\\" && root !== "/";
20
20
  }