mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 05:27:26 +00:00
refactor(acp): extract install hint resolver
This commit is contained in:
@@ -331,7 +331,7 @@ Then verify backend health:
|
|||||||
|
|
||||||
### acpx command and version configuration
|
### acpx command and version configuration
|
||||||
|
|
||||||
By default, `@openclaw/acpx` uses the plugin-local pinned binary:
|
By default, the acpx plugin (published as `@openclaw/acpx`) uses the plugin-local pinned binary:
|
||||||
|
|
||||||
1. Command defaults to `extensions/acpx/node_modules/.bin/acpx`.
|
1. Command defaults to `extensions/acpx/node_modules/.bin/acpx`.
|
||||||
2. Expected version defaults to the extension pin.
|
2. Expected version defaults to the extension pin.
|
||||||
|
|||||||
56
src/auto-reply/reply/commands-acp/install-hints.test.ts
Normal file
56
src/auto-reply/reply/commands-acp/install-hints.test.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import type { OpenClawConfig } from "../../../config/config.js";
|
||||||
|
import { resolveAcpInstallCommandHint, resolveConfiguredAcpBackendId } from "./install-hints.js";
|
||||||
|
|
||||||
|
const originalCwd = process.cwd();
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
function withAcpConfig(acp: OpenClawConfig["acp"]): OpenClawConfig {
|
||||||
|
return { acp } as OpenClawConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.chdir(originalCwd);
|
||||||
|
for (const dir of tempDirs.splice(0)) {
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ACP install hints", () => {
|
||||||
|
it("prefers explicit runtime install command", () => {
|
||||||
|
const cfg = withAcpConfig({
|
||||||
|
runtime: { installCommand: "pnpm openclaw plugins install acpx" },
|
||||||
|
});
|
||||||
|
expect(resolveAcpInstallCommandHint(cfg)).toBe("pnpm openclaw plugins install acpx");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses local acpx extension path when present", () => {
|
||||||
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "acp-install-hint-"));
|
||||||
|
tempDirs.push(tempRoot);
|
||||||
|
fs.mkdirSync(path.join(tempRoot, "extensions", "acpx"), { recursive: true });
|
||||||
|
process.chdir(tempRoot);
|
||||||
|
|
||||||
|
const cfg = withAcpConfig({ backend: "acpx" });
|
||||||
|
const hint = resolveAcpInstallCommandHint(cfg);
|
||||||
|
expect(hint).toContain("openclaw plugins install ");
|
||||||
|
expect(hint).toContain(path.join("extensions", "acpx"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to npm install hint for acpx when local extension is absent", () => {
|
||||||
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "acp-install-hint-"));
|
||||||
|
tempDirs.push(tempRoot);
|
||||||
|
process.chdir(tempRoot);
|
||||||
|
|
||||||
|
const cfg = withAcpConfig({ backend: "acpx" });
|
||||||
|
expect(resolveAcpInstallCommandHint(cfg)).toBe("openclaw plugins install acpx");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns generic plugin hint for non-acpx backend", () => {
|
||||||
|
const cfg = withAcpConfig({ backend: "custom-backend" });
|
||||||
|
expect(resolveConfiguredAcpBackendId(cfg)).toBe("custom-backend");
|
||||||
|
expect(resolveAcpInstallCommandHint(cfg)).toContain('ACP backend "custom-backend"');
|
||||||
|
});
|
||||||
|
});
|
||||||
23
src/auto-reply/reply/commands-acp/install-hints.ts
Normal file
23
src/auto-reply/reply/commands-acp/install-hints.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import type { OpenClawConfig } from "../../../config/config.js";
|
||||||
|
|
||||||
|
export function resolveConfiguredAcpBackendId(cfg: OpenClawConfig): string {
|
||||||
|
return cfg.acp?.backend?.trim() || "acpx";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveAcpInstallCommandHint(cfg: OpenClawConfig): string {
|
||||||
|
const configured = cfg.acp?.runtime?.installCommand?.trim();
|
||||||
|
if (configured) {
|
||||||
|
return configured;
|
||||||
|
}
|
||||||
|
const backendId = resolveConfiguredAcpBackendId(cfg).toLowerCase();
|
||||||
|
if (backendId === "acpx") {
|
||||||
|
const localPath = path.resolve(process.cwd(), "extensions/acpx");
|
||||||
|
if (existsSync(localPath)) {
|
||||||
|
return `openclaw plugins install ${localPath}`;
|
||||||
|
}
|
||||||
|
return "openclaw plugins install acpx";
|
||||||
|
}
|
||||||
|
return `Install and enable the plugin that provides ACP backend "${backendId}".`;
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { existsSync } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { toAcpRuntimeErrorText } from "../../../acp/runtime/error-text.js";
|
import { toAcpRuntimeErrorText } from "../../../acp/runtime/error-text.js";
|
||||||
import type { AcpRuntimeError } from "../../../acp/runtime/errors.js";
|
import type { AcpRuntimeError } from "../../../acp/runtime/errors.js";
|
||||||
import type { AcpRuntimeSessionMode } from "../../../acp/runtime/types.js";
|
import type { AcpRuntimeSessionMode } from "../../../acp/runtime/types.js";
|
||||||
import { DISCORD_THREAD_BINDING_CHANNEL } from "../../../channels/thread-bindings-policy.js";
|
import { DISCORD_THREAD_BINDING_CHANNEL } from "../../../channels/thread-bindings-policy.js";
|
||||||
import type { OpenClawConfig } from "../../../config/config.js";
|
|
||||||
import type { AcpSessionRuntimeOptions } from "../../../config/sessions/types.js";
|
import type { AcpSessionRuntimeOptions } from "../../../config/sessions/types.js";
|
||||||
import { normalizeAgentId } from "../../../routing/session-key.js";
|
import { normalizeAgentId } from "../../../routing/session-key.js";
|
||||||
import type { CommandHandlerResult, HandleCommandsParams } from "../commands-types.js";
|
import type { CommandHandlerResult, HandleCommandsParams } from "../commands-types.js";
|
||||||
import { resolveAcpCommandChannel, resolveAcpCommandThreadId } from "./context.js";
|
import { resolveAcpCommandChannel, resolveAcpCommandThreadId } from "./context.js";
|
||||||
|
export { resolveAcpInstallCommandHint, resolveConfiguredAcpBackendId } from "./install-hints.js";
|
||||||
|
|
||||||
export const COMMAND = "/acp";
|
export const COMMAND = "/acp";
|
||||||
export const ACP_SPAWN_USAGE =
|
export const ACP_SPAWN_USAGE =
|
||||||
@@ -404,26 +402,6 @@ export function resolveAcpHelpText(): string {
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveConfiguredAcpBackendId(cfg: OpenClawConfig): string {
|
|
||||||
return cfg.acp?.backend?.trim() || "acpx";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveAcpInstallCommandHint(cfg: OpenClawConfig): string {
|
|
||||||
const configured = cfg.acp?.runtime?.installCommand?.trim();
|
|
||||||
if (configured) {
|
|
||||||
return configured;
|
|
||||||
}
|
|
||||||
const backendId = resolveConfiguredAcpBackendId(cfg).toLowerCase();
|
|
||||||
if (backendId === "acpx") {
|
|
||||||
const localPath = path.resolve(process.cwd(), "extensions/acpx");
|
|
||||||
if (existsSync(localPath)) {
|
|
||||||
return `openclaw plugins install ${localPath}`;
|
|
||||||
}
|
|
||||||
return "openclaw plugins install acpx";
|
|
||||||
}
|
|
||||||
return `Install and enable the plugin that provides ACP backend "${backendId}".`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatRuntimeOptionsText(options: AcpSessionRuntimeOptions): string {
|
export function formatRuntimeOptionsText(options: AcpSessionRuntimeOptions): string {
|
||||||
const extras = options.backendExtras
|
const extras = options.backendExtras
|
||||||
? Object.entries(options.backendExtras)
|
? Object.entries(options.backendExtras)
|
||||||
|
|||||||
Reference in New Issue
Block a user