mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-09 15:35:17 +00:00
ACPX: pin 0.1.15 and tolerate missing --version in health check
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"expectedVersion": {
|
||||
"label": "Expected acpx Version",
|
||||
"help": "Exact version to enforce (for example 0.1.14) or \"any\" to skip strict version matching."
|
||||
"help": "Exact version to enforce (for example 0.1.15) or \"any\" to skip strict version matching."
|
||||
},
|
||||
"cwd": {
|
||||
"label": "Default Working Directory",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw ACP runtime backend via acpx",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"acpx": "0.1.14"
|
||||
"acpx": "0.1.15"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
|
||||
@@ -8,7 +8,7 @@ export type AcpxPermissionMode = (typeof ACPX_PERMISSION_MODES)[number];
|
||||
export const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"] as const;
|
||||
export type AcpxNonInteractivePermissionPolicy = (typeof ACPX_NON_INTERACTIVE_POLICIES)[number];
|
||||
|
||||
export const ACPX_PINNED_VERSION = "0.1.14";
|
||||
export const ACPX_PINNED_VERSION = "0.1.15";
|
||||
export const ACPX_VERSION_ANY = "any";
|
||||
const ACPX_BIN_NAME = process.platform === "win32" ? "acpx.cmd" : "acpx";
|
||||
export const ACPX_PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
ACPX_LOCAL_INSTALL_COMMAND,
|
||||
ACPX_PINNED_VERSION,
|
||||
@@ -20,12 +23,37 @@ vi.mock("./runtime-internals/process.js", () => ({
|
||||
import { checkAcpxVersion, ensureAcpx } from "./ensure.js";
|
||||
|
||||
describe("acpx ensure", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
resolveSpawnFailureMock.mockReset();
|
||||
resolveSpawnFailureMock.mockReturnValue(null);
|
||||
spawnAndCollectMock.mockReset();
|
||||
});
|
||||
|
||||
function makeTempAcpxInstall(version: string): string {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "acpx-ensure-test-"));
|
||||
tempDirs.push(root);
|
||||
const packageRoot = path.join(root, "node_modules", "acpx");
|
||||
fs.mkdirSync(path.join(packageRoot, "dist"), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, "node_modules", ".bin"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({ name: "acpx", version }, null, 2),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(packageRoot, "dist", "cli.js"), "#!/usr/bin/env node\n", "utf8");
|
||||
const binPath = path.join(root, "node_modules", ".bin", "acpx");
|
||||
fs.symlinkSync(path.join(packageRoot, "dist", "cli.js"), binPath);
|
||||
return binPath;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts the pinned acpx version", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: `acpx ${ACPX_PINNED_VERSION}\n`,
|
||||
@@ -75,6 +103,28 @@ describe("acpx ensure", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to package.json version when --version is unsupported", async () => {
|
||||
const command = makeTempAcpxInstall(ACPX_PINNED_VERSION);
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: "",
|
||||
stderr: "error: unknown option '--version'",
|
||||
code: 2,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const result = await checkAcpxVersion({
|
||||
command,
|
||||
cwd: path.dirname(path.dirname(command)),
|
||||
expectedVersion: ACPX_PINNED_VERSION,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
version: ACPX_PINNED_VERSION,
|
||||
expectedVersion: ACPX_PINNED_VERSION,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts command availability when expectedVersion is unset", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: "Usage: acpx [options]\n",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { PluginLogger } from "openclaw/plugin-sdk";
|
||||
import { ACPX_PINNED_VERSION, ACPX_PLUGIN_ROOT, buildAcpxLocalInstallCommand } from "./config.js";
|
||||
import { resolveSpawnFailure, spawnAndCollect } from "./runtime-internals/process.js";
|
||||
@@ -29,6 +31,47 @@ function isExpectedVersionConfigured(value: string | undefined): value is string
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function supportsPathResolution(command: string): boolean {
|
||||
return path.isAbsolute(command) || command.includes("/") || command.includes("\\");
|
||||
}
|
||||
|
||||
function isUnsupportedVersionProbe(stdout: string, stderr: string): boolean {
|
||||
const combined = `${stdout}\n${stderr}`.toLowerCase();
|
||||
return combined.includes("unknown option") && combined.includes("--version");
|
||||
}
|
||||
|
||||
function resolveVersionFromPackage(command: string, cwd: string): string | null {
|
||||
if (!supportsPathResolution(command)) {
|
||||
return null;
|
||||
}
|
||||
const commandPath = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
||||
let current: string;
|
||||
try {
|
||||
current = path.dirname(fs.realpathSync(commandPath));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
while (true) {
|
||||
const packageJsonPath = path.join(current, "package.json");
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
|
||||
name?: unknown;
|
||||
version?: unknown;
|
||||
};
|
||||
if (parsed.name === "acpx" && typeof parsed.version === "string" && parsed.version.trim()) {
|
||||
return parsed.version.trim();
|
||||
}
|
||||
} catch {
|
||||
// no-op; continue walking up
|
||||
}
|
||||
const parent = path.dirname(current);
|
||||
if (parent === current) {
|
||||
return null;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAcpxVersion(params: {
|
||||
command: string;
|
||||
cwd?: string;
|
||||
@@ -66,6 +109,26 @@ export async function checkAcpxVersion(params: {
|
||||
}
|
||||
|
||||
if ((result.code ?? 0) !== 0) {
|
||||
if (hasExpectedVersion && isUnsupportedVersionProbe(result.stdout, result.stderr)) {
|
||||
const installedVersion = resolveVersionFromPackage(params.command, cwd);
|
||||
if (installedVersion) {
|
||||
if (expectedVersion && installedVersion !== expectedVersion) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "version-mismatch",
|
||||
message: `acpx version mismatch: found ${installedVersion}, expected ${expectedVersion}`,
|
||||
expectedVersion,
|
||||
installCommand,
|
||||
installedVersion,
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
version: installedVersion,
|
||||
expectedVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
const stderr = result.stderr.trim();
|
||||
return {
|
||||
ok: false,
|
||||
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -264,8 +264,8 @@ importers:
|
||||
extensions/acpx:
|
||||
dependencies:
|
||||
acpx:
|
||||
specifier: 0.1.14
|
||||
version: 0.1.14(zod@4.3.6)
|
||||
specifier: 0.1.15
|
||||
version: 0.1.15(zod@4.3.6)
|
||||
|
||||
extensions/bluebubbles: {}
|
||||
|
||||
@@ -3131,8 +3131,8 @@ packages:
|
||||
link-preview-js:
|
||||
optional: true
|
||||
|
||||
'@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
resolution: {commit: 1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67, repo: https://github.com/whiskeysockets/libsignal-node.git, type: git}
|
||||
'@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
resolution: {tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67}
|
||||
version: 2.0.1
|
||||
|
||||
abbrev@1.1.1:
|
||||
@@ -3160,8 +3160,8 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acpx@0.1.14:
|
||||
resolution: {integrity: sha512-kq1tU7VCOLW3dIK77PpGoJPMsIqmnOSiQJGsWfWiOYgTXYIsbNtP04ilsaobgDd/MUgjo9ttXD1abziQ3OH5Pg==}
|
||||
acpx@0.1.15:
|
||||
resolution: {integrity: sha512-1r+tmPT9Oe2Ulv5b4r7O2hCCq5CHVru/H2tcPeTpZek9jR1zBQoBfZ/RcK+9sC9/mnDvWYO5R7Iae64v2LMO+A==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -9007,7 +9007,7 @@ snapshots:
|
||||
'@cacheable/node-cache': 1.7.6
|
||||
'@hapi/boom': 9.1.4
|
||||
async-mutex: 0.5.0
|
||||
libsignal: '@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67'
|
||||
libsignal: '@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67'
|
||||
lru-cache: 11.2.6
|
||||
music-metadata: 11.12.1
|
||||
p-queue: 9.1.0
|
||||
@@ -9022,7 +9022,7 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@whiskeysockets/libsignal-node@git+https://github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
'@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67':
|
||||
dependencies:
|
||||
curve25519-js: 0.0.4
|
||||
protobufjs: 6.8.8
|
||||
@@ -9050,7 +9050,7 @@ snapshots:
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
acpx@0.1.14(zod@4.3.6):
|
||||
acpx@0.1.15(zod@4.3.6):
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk': 0.14.1(zod@4.3.6)
|
||||
commander: 13.1.0
|
||||
|
||||
Reference in New Issue
Block a user