mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 06:57:26 +00:00
ACPX: pin 0.1.15 and tolerate missing --version in health check
This commit is contained in:
@@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"expectedVersion": {
|
"expectedVersion": {
|
||||||
"label": "Expected acpx Version",
|
"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": {
|
"cwd": {
|
||||||
"label": "Default Working Directory",
|
"label": "Default Working Directory",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "OpenClaw ACP runtime backend via acpx",
|
"description": "OpenClaw ACP runtime backend via acpx",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acpx": "0.1.14"
|
"acpx": "0.1.15"
|
||||||
},
|
},
|
||||||
"openclaw": {
|
"openclaw": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export type AcpxPermissionMode = (typeof ACPX_PERMISSION_MODES)[number];
|
|||||||
export const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"] as const;
|
export const ACPX_NON_INTERACTIVE_POLICIES = ["deny", "fail"] as const;
|
||||||
export type AcpxNonInteractivePermissionPolicy = (typeof ACPX_NON_INTERACTIVE_POLICIES)[number];
|
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";
|
export const ACPX_VERSION_ANY = "any";
|
||||||
const ACPX_BIN_NAME = process.platform === "win32" ? "acpx.cmd" : "acpx";
|
const ACPX_BIN_NAME = process.platform === "win32" ? "acpx.cmd" : "acpx";
|
||||||
export const ACPX_PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
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 {
|
import {
|
||||||
ACPX_LOCAL_INSTALL_COMMAND,
|
ACPX_LOCAL_INSTALL_COMMAND,
|
||||||
ACPX_PINNED_VERSION,
|
ACPX_PINNED_VERSION,
|
||||||
@@ -20,12 +23,37 @@ vi.mock("./runtime-internals/process.js", () => ({
|
|||||||
import { checkAcpxVersion, ensureAcpx } from "./ensure.js";
|
import { checkAcpxVersion, ensureAcpx } from "./ensure.js";
|
||||||
|
|
||||||
describe("acpx ensure", () => {
|
describe("acpx ensure", () => {
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resolveSpawnFailureMock.mockReset();
|
resolveSpawnFailureMock.mockReset();
|
||||||
resolveSpawnFailureMock.mockReturnValue(null);
|
resolveSpawnFailureMock.mockReturnValue(null);
|
||||||
spawnAndCollectMock.mockReset();
|
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 () => {
|
it("accepts the pinned acpx version", async () => {
|
||||||
spawnAndCollectMock.mockResolvedValueOnce({
|
spawnAndCollectMock.mockResolvedValueOnce({
|
||||||
stdout: `acpx ${ACPX_PINNED_VERSION}\n`,
|
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 () => {
|
it("accepts command availability when expectedVersion is unset", async () => {
|
||||||
spawnAndCollectMock.mockResolvedValueOnce({
|
spawnAndCollectMock.mockResolvedValueOnce({
|
||||||
stdout: "Usage: acpx [options]\n",
|
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 type { PluginLogger } from "openclaw/plugin-sdk";
|
||||||
import { ACPX_PINNED_VERSION, ACPX_PLUGIN_ROOT, buildAcpxLocalInstallCommand } from "./config.js";
|
import { ACPX_PINNED_VERSION, ACPX_PLUGIN_ROOT, buildAcpxLocalInstallCommand } from "./config.js";
|
||||||
import { resolveSpawnFailure, spawnAndCollect } from "./runtime-internals/process.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;
|
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: {
|
export async function checkAcpxVersion(params: {
|
||||||
command: string;
|
command: string;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -66,6 +109,26 @@ export async function checkAcpxVersion(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((result.code ?? 0) !== 0) {
|
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();
|
const stderr = result.stderr.trim();
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -264,8 +264,8 @@ importers:
|
|||||||
extensions/acpx:
|
extensions/acpx:
|
||||||
dependencies:
|
dependencies:
|
||||||
acpx:
|
acpx:
|
||||||
specifier: 0.1.14
|
specifier: 0.1.15
|
||||||
version: 0.1.14(zod@4.3.6)
|
version: 0.1.15(zod@4.3.6)
|
||||||
|
|
||||||
extensions/bluebubbles: {}
|
extensions/bluebubbles: {}
|
||||||
|
|
||||||
@@ -3131,8 +3131,8 @@ packages:
|
|||||||
link-preview-js:
|
link-preview-js:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@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':
|
||||||
resolution: {commit: 1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67, repo: https://github.com/whiskeysockets/libsignal-node.git, type: git}
|
resolution: {tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67}
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
|
||||||
abbrev@1.1.1:
|
abbrev@1.1.1:
|
||||||
@@ -3160,8 +3160,8 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
acpx@0.1.14:
|
acpx@0.1.15:
|
||||||
resolution: {integrity: sha512-kq1tU7VCOLW3dIK77PpGoJPMsIqmnOSiQJGsWfWiOYgTXYIsbNtP04ilsaobgDd/MUgjo9ttXD1abziQ3OH5Pg==}
|
resolution: {integrity: sha512-1r+tmPT9Oe2Ulv5b4r7O2hCCq5CHVru/H2tcPeTpZek9jR1zBQoBfZ/RcK+9sC9/mnDvWYO5R7Iae64v2LMO+A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -9007,7 +9007,7 @@ snapshots:
|
|||||||
'@cacheable/node-cache': 1.7.6
|
'@cacheable/node-cache': 1.7.6
|
||||||
'@hapi/boom': 9.1.4
|
'@hapi/boom': 9.1.4
|
||||||
async-mutex: 0.5.0
|
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
|
lru-cache: 11.2.6
|
||||||
music-metadata: 11.12.1
|
music-metadata: 11.12.1
|
||||||
p-queue: 9.1.0
|
p-queue: 9.1.0
|
||||||
@@ -9022,7 +9022,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- 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:
|
dependencies:
|
||||||
curve25519-js: 0.0.4
|
curve25519-js: 0.0.4
|
||||||
protobufjs: 6.8.8
|
protobufjs: 6.8.8
|
||||||
@@ -9050,7 +9050,7 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.16.0: {}
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
acpx@0.1.14(zod@4.3.6):
|
acpx@0.1.15(zod@4.3.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@agentclientprotocol/sdk': 0.14.1(zod@4.3.6)
|
'@agentclientprotocol/sdk': 0.14.1(zod@4.3.6)
|
||||||
commander: 13.1.0
|
commander: 13.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user