chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import {
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../agents/agent-scope.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
@@ -12,15 +9,9 @@ import type { PluginLogger } from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("plugins");
|
||||
|
||||
export function registerPluginCliCommands(
|
||||
program: Command,
|
||||
cfg?: ClawdbotConfig,
|
||||
) {
|
||||
export function registerPluginCliCommands(program: Command, cfg?: ClawdbotConfig) {
|
||||
const config = cfg ?? loadConfig();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(
|
||||
config,
|
||||
resolveDefaultAgentId(config),
|
||||
);
|
||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
const logger: PluginLogger = {
|
||||
info: (msg: string) => log.info(msg),
|
||||
warn: (msg: string) => log.warn(msg),
|
||||
@@ -43,15 +34,11 @@ export function registerPluginCliCommands(
|
||||
});
|
||||
if (result && typeof (result as Promise<void>).then === "function") {
|
||||
void (result as Promise<void>).catch((err) => {
|
||||
log.warn(
|
||||
`plugin CLI register failed (${entry.pluginId}): ${String(err)}`,
|
||||
);
|
||||
log.warn(`plugin CLI register failed (${entry.pluginId}): ${String(err)}`);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn(
|
||||
`plugin CLI register failed (${entry.pluginId}): ${String(err)}`,
|
||||
);
|
||||
log.warn(`plugin CLI register failed (${entry.pluginId}): ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,19 +46,11 @@ describe("discoverClawdbotPlugins", () => {
|
||||
|
||||
const globalExt = path.join(stateDir, "extensions");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(globalExt, "alpha.ts"),
|
||||
"export default function () {}",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(globalExt, "alpha.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const workspaceExt = path.join(workspaceDir, ".clawdbot", "extensions");
|
||||
fs.mkdirSync(workspaceExt, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(workspaceExt, "beta.ts"),
|
||||
"export default function () {}",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(workspaceExt, "beta.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const { candidates } = await withStateDir(stateDir, async () => {
|
||||
const { discoverClawdbotPlugins } = await import("./discovery.js");
|
||||
@@ -145,11 +137,7 @@ describe("discoverClawdbotPlugins", () => {
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(packDir, "index.js"),
|
||||
"module.exports = {}",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(packDir, "index.js"), "module.exports = {}", "utf-8");
|
||||
|
||||
const { candidates } = await withStateDir(stateDir, async () => {
|
||||
const { discoverClawdbotPlugins } = await import("./discovery.js");
|
||||
|
||||
@@ -50,9 +50,7 @@ function readPackageManifest(dir: string): PackageManifest | null {
|
||||
function resolvePackageExtensions(manifest: PackageManifest): string[] {
|
||||
const raw = manifest.clawdbot?.extensions;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function deriveIdHint(params: {
|
||||
|
||||
@@ -16,10 +16,7 @@ function makeTempDir() {
|
||||
|
||||
function resolveNpmCliJs() {
|
||||
const fromEnv = process.env.npm_execpath;
|
||||
if (
|
||||
fromEnv?.includes(`${path.sep}npm${path.sep}`) &&
|
||||
fromEnv?.endsWith("npm-cli.js")
|
||||
) {
|
||||
if (fromEnv?.includes(`${path.sep}npm${path.sep}`) && fromEnv?.endsWith("npm-cli.js")) {
|
||||
return fromEnv ?? null;
|
||||
}
|
||||
|
||||
@@ -64,20 +61,12 @@ function packToArchive({
|
||||
const res = spawnSync(cmd, args, { encoding: "utf-8" });
|
||||
expect(res.status).toBe(0);
|
||||
if (res.status !== 0) {
|
||||
throw new Error(
|
||||
`npm pack failed: ${res.stderr || res.stdout || "<no output>"}`,
|
||||
);
|
||||
throw new Error(`npm pack failed: ${res.stderr || res.stdout || "<no output>"}`);
|
||||
}
|
||||
|
||||
const packed = (res.stdout || "")
|
||||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.at(-1);
|
||||
const packed = (res.stdout || "").trim().split(/\r?\n/).filter(Boolean).at(-1);
|
||||
if (!packed) {
|
||||
throw new Error(
|
||||
`npm pack did not output a filename: ${res.stdout || "<no stdout>"}`,
|
||||
);
|
||||
throw new Error(`npm pack did not output a filename: ${res.stdout || "<no stdout>"}`);
|
||||
}
|
||||
|
||||
const src = path.join(outDir, packed);
|
||||
@@ -128,11 +117,7 @@ describe("installPluginFromArchive", () => {
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pkgDir, "dist", "index.js"),
|
||||
"export {};",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
|
||||
|
||||
const archivePath = packToArchive({
|
||||
pkgDir,
|
||||
@@ -147,15 +132,9 @@ describe("installPluginFromArchive", () => {
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(result.pluginId).toBe("voice-call");
|
||||
expect(result.targetDir).toBe(
|
||||
path.join(stateDir, "extensions", "voice-call"),
|
||||
);
|
||||
expect(fs.existsSync(path.join(result.targetDir, "package.json"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(fs.existsSync(path.join(result.targetDir, "dist", "index.js"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(result.targetDir).toBe(path.join(stateDir, "extensions", "voice-call"));
|
||||
expect(fs.existsSync(path.join(result.targetDir, "package.json"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(result.targetDir, "dist", "index.js"))).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects installing when plugin already exists", async () => {
|
||||
@@ -172,11 +151,7 @@ describe("installPluginFromArchive", () => {
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pkgDir, "dist", "index.js"),
|
||||
"export {};",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
|
||||
|
||||
const archivePath = packToArchive({
|
||||
pkgDir,
|
||||
|
||||
@@ -31,9 +31,7 @@ const defaultLogger: PluginInstallLogger = {};
|
||||
function unscopedPackageName(name: string): string {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
return trimmed.includes("/")
|
||||
? (trimmed.split("/").pop() ?? trimmed)
|
||||
: trimmed;
|
||||
return trimmed.includes("/") ? (trimmed.split("/").pop() ?? trimmed) : trimmed;
|
||||
}
|
||||
|
||||
function safeDirName(input: string): string {
|
||||
@@ -77,20 +75,14 @@ async function ensureClawdbotExtensions(manifest: PackageManifest) {
|
||||
if (!Array.isArray(extensions)) {
|
||||
throw new Error("package.json missing clawdbot.extensions");
|
||||
}
|
||||
const list = extensions
|
||||
.map((e) => (typeof e === "string" ? e.trim() : ""))
|
||||
.filter(Boolean);
|
||||
const list = extensions.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
|
||||
if (list.length === 0) {
|
||||
throw new Error("package.json clawdbot.extensions is empty");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async function withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
label: string,
|
||||
): Promise<T> {
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
return await Promise.race([
|
||||
@@ -132,11 +124,7 @@ export async function installPluginFromArchive(params: {
|
||||
|
||||
logger.info?.(`Extracting ${archivePath}…`);
|
||||
try {
|
||||
await withTimeout(
|
||||
tar.x({ file: archivePath, cwd: extractDir }),
|
||||
timeoutMs,
|
||||
"extract archive",
|
||||
);
|
||||
await withTimeout(tar.x({ file: archivePath, cwd: extractDir }), timeoutMs, "extract archive");
|
||||
} catch (err) {
|
||||
return { ok: false, error: `failed to extract archive: ${String(err)}` };
|
||||
}
|
||||
@@ -192,10 +180,10 @@ export async function installPluginFromArchive(params: {
|
||||
const hasDeps = Object.keys(deps).length > 0;
|
||||
if (hasDeps) {
|
||||
logger.info?.("Installing plugin dependencies…");
|
||||
const npmRes = await runCommandWithTimeout(
|
||||
["npm", "install", "--omit=dev", "--silent"],
|
||||
{ timeoutMs: Math.max(timeoutMs, 300_000), cwd: targetDir },
|
||||
);
|
||||
const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], {
|
||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
||||
cwd: targetDir,
|
||||
});
|
||||
if (npmRes.code !== 0) {
|
||||
return {
|
||||
ok: false,
|
||||
|
||||
@@ -5,11 +5,7 @@ import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { discoverClawdbotPlugins } from "./discovery.js";
|
||||
import {
|
||||
createPluginRegistry,
|
||||
type PluginRecord,
|
||||
type PluginRegistry,
|
||||
} from "./registry.js";
|
||||
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
||||
import type {
|
||||
ClawdbotPluginConfigSchema,
|
||||
ClawdbotPluginDefinition,
|
||||
@@ -34,10 +30,7 @@ type NormalizedPluginsConfig = {
|
||||
allow: string[];
|
||||
deny: string[];
|
||||
loadPaths: string[];
|
||||
entries: Record<
|
||||
string,
|
||||
{ enabled?: boolean; config?: Record<string, unknown> }
|
||||
>;
|
||||
entries: Record<string, { enabled?: boolean; config?: Record<string, unknown> }>;
|
||||
};
|
||||
|
||||
const registryCache = new Map<string, PluginRegistry>();
|
||||
@@ -46,14 +39,10 @@ const defaultLogger = () => createSubsystemLogger("plugins");
|
||||
|
||||
const normalizeList = (value: unknown): string[] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
};
|
||||
|
||||
const normalizePluginEntries = (
|
||||
entries: unknown,
|
||||
): NormalizedPluginsConfig["entries"] => {
|
||||
const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entries"] => {
|
||||
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
||||
return {};
|
||||
}
|
||||
@@ -68,9 +57,7 @@ const normalizePluginEntries = (
|
||||
normalized[key] = {
|
||||
enabled: typeof entry.enabled === "boolean" ? entry.enabled : undefined,
|
||||
config:
|
||||
entry.config &&
|
||||
typeof entry.config === "object" &&
|
||||
!Array.isArray(entry.config)
|
||||
entry.config && typeof entry.config === "object" && !Array.isArray(entry.config)
|
||||
? (entry.config as Record<string, unknown>)
|
||||
: undefined,
|
||||
};
|
||||
@@ -78,9 +65,7 @@ const normalizePluginEntries = (
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const normalizePluginsConfig = (
|
||||
config?: ClawdbotConfig["plugins"],
|
||||
): NormalizedPluginsConfig => {
|
||||
const normalizePluginsConfig = (config?: ClawdbotConfig["plugins"]): NormalizedPluginsConfig => {
|
||||
return {
|
||||
enabled: config?.enabled !== false,
|
||||
allow: normalizeList(config?.allow),
|
||||
@@ -94,9 +79,7 @@ function buildCacheKey(params: {
|
||||
workspaceDir?: string;
|
||||
plugins: NormalizedPluginsConfig;
|
||||
}): string {
|
||||
const workspaceKey = params.workspaceDir
|
||||
? resolveUserPath(params.workspaceDir)
|
||||
: "";
|
||||
const workspaceKey = params.workspaceDir ? resolveUserPath(params.workspaceDir) : "";
|
||||
return `${workspaceKey}::${JSON.stringify(params.plugins)}`;
|
||||
}
|
||||
|
||||
@@ -213,16 +196,11 @@ function createPluginRecord(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function pushDiagnostics(
|
||||
diagnostics: PluginDiagnostic[],
|
||||
append: PluginDiagnostic[],
|
||||
) {
|
||||
function pushDiagnostics(diagnostics: PluginDiagnostic[], append: PluginDiagnostic[]) {
|
||||
diagnostics.push(...append);
|
||||
}
|
||||
|
||||
export function loadClawdbotPlugins(
|
||||
options: PluginLoadOptions = {},
|
||||
): PluginRegistry {
|
||||
export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegistry {
|
||||
const cfg = options.config ?? {};
|
||||
const logger = options.logger ?? defaultLogger();
|
||||
const normalized = normalizePluginsConfig(cfg.plugins);
|
||||
@@ -238,10 +216,7 @@ export function loadClawdbotPlugins(
|
||||
|
||||
const { registry, createApi } = createPluginRegistry({
|
||||
logger,
|
||||
coreGatewayHandlers: options.coreGatewayHandlers as Record<
|
||||
string,
|
||||
GatewayRequestHandler
|
||||
>,
|
||||
coreGatewayHandlers: options.coreGatewayHandlers as Record<string, GatewayRequestHandler>,
|
||||
});
|
||||
|
||||
const discovery = discoverClawdbotPlugins({
|
||||
@@ -313,8 +288,7 @@ export function loadClawdbotPlugins(
|
||||
definition?.configSchema &&
|
||||
typeof definition.configSchema === "object" &&
|
||||
(definition.configSchema as { uiHints?: unknown }).uiHints &&
|
||||
typeof (definition.configSchema as { uiHints?: unknown }).uiHints ===
|
||||
"object" &&
|
||||
typeof (definition.configSchema as { uiHints?: unknown }).uiHints === "object" &&
|
||||
!Array.isArray((definition.configSchema as { uiHints?: unknown }).uiHints)
|
||||
? ((definition.configSchema as { uiHints?: unknown }).uiHints as Record<
|
||||
string,
|
||||
@@ -365,8 +339,7 @@ export function loadClawdbotPlugins(
|
||||
level: "warn",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message:
|
||||
"plugin register returned a promise; async registration is ignored",
|
||||
message: "plugin register returned a promise; async registration is ignored",
|
||||
});
|
||||
}
|
||||
registry.plugins.push(record);
|
||||
|
||||
@@ -78,9 +78,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
services: [],
|
||||
diagnostics: [],
|
||||
};
|
||||
const coreGatewayMethods = new Set(
|
||||
Object.keys(registryParams.coreGatewayHandlers ?? {}),
|
||||
);
|
||||
const coreGatewayMethods = new Set(Object.keys(registryParams.coreGatewayHandlers ?? {}));
|
||||
|
||||
const pushDiagnostic = (diag: PluginDiagnostic) => {
|
||||
registry.diagnostics.push(diag);
|
||||
@@ -93,9 +91,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
) => {
|
||||
const names = opts?.names ?? (opts?.name ? [opts.name] : []);
|
||||
const factory: ClawdbotPluginToolFactory =
|
||||
typeof tool === "function"
|
||||
? tool
|
||||
: (_ctx: ClawdbotPluginToolContext) => tool;
|
||||
typeof tool === "function" ? tool : (_ctx: ClawdbotPluginToolContext) => tool;
|
||||
|
||||
if (typeof tool !== "function") {
|
||||
names.push(tool.name);
|
||||
@@ -138,9 +134,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
registrar: ClawdbotPluginCliRegistrar,
|
||||
opts?: { commands?: string[] },
|
||||
) => {
|
||||
const commands = (opts?.commands ?? [])
|
||||
.map((cmd) => cmd.trim())
|
||||
.filter(Boolean);
|
||||
const commands = (opts?.commands ?? []).map((cmd) => cmd.trim()).filter(Boolean);
|
||||
record.cliCommands.push(...commands);
|
||||
registry.cliRegistrars.push({
|
||||
pluginId: record.id,
|
||||
@@ -150,10 +144,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
};
|
||||
|
||||
const registerService = (
|
||||
record: PluginRecord,
|
||||
service: ClawdbotPluginService,
|
||||
) => {
|
||||
const registerService = (record: PluginRecord, service: ClawdbotPluginService) => {
|
||||
const id = service.id.trim();
|
||||
if (!id) return;
|
||||
record.services.push(id);
|
||||
@@ -188,8 +179,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
pluginConfig: params.pluginConfig,
|
||||
logger: normalizeLogger(registryParams.logger),
|
||||
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
||||
registerGatewayMethod: (method, handler) =>
|
||||
registerGatewayMethod(record, method, handler),
|
||||
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
|
||||
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
|
||||
registerService: (service) => registerService(record, service),
|
||||
resolvePath: (input: string) => resolveUserPath(input),
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../agents/agent-scope.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
|
||||
@@ -63,9 +63,7 @@ export type ClawdbotPluginCliContext = {
|
||||
logger: PluginLogger;
|
||||
};
|
||||
|
||||
export type ClawdbotPluginCliRegistrar = (
|
||||
ctx: ClawdbotPluginCliContext,
|
||||
) => void | Promise<void>;
|
||||
export type ClawdbotPluginCliRegistrar = (ctx: ClawdbotPluginCliContext) => void | Promise<void>;
|
||||
|
||||
export type ClawdbotPluginServiceContext = {
|
||||
config: ClawdbotConfig;
|
||||
@@ -107,14 +105,8 @@ export type ClawdbotPluginApi = {
|
||||
tool: AnyAgentTool | ClawdbotPluginToolFactory,
|
||||
opts?: { name?: string; names?: string[] },
|
||||
) => void;
|
||||
registerGatewayMethod: (
|
||||
method: string,
|
||||
handler: GatewayRequestHandler,
|
||||
) => void;
|
||||
registerCli: (
|
||||
registrar: ClawdbotPluginCliRegistrar,
|
||||
opts?: { commands?: string[] },
|
||||
) => void;
|
||||
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;
|
||||
registerCli: (registrar: ClawdbotPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
||||
registerService: (service: ClawdbotPluginService) => void;
|
||||
resolvePath: (input: string) => string;
|
||||
};
|
||||
|
||||
@@ -65,9 +65,7 @@ describe("voice-call plugin", () => {
|
||||
})),
|
||||
speak: vi.fn(async () => ({ success: true })),
|
||||
endCall: vi.fn(async () => ({ success: true })),
|
||||
getCall: vi.fn((id: string) =>
|
||||
id === "call-1" ? { callId: "call-1" } : undefined,
|
||||
),
|
||||
getCall: vi.fn((id: string) => (id === "call-1" ? { callId: "call-1" } : undefined)),
|
||||
getCallByProviderCallId: vi.fn(() => undefined),
|
||||
},
|
||||
stop: vi.fn(async () => {}),
|
||||
@@ -165,10 +163,9 @@ describe("voice-call plugin", () => {
|
||||
resolvePath: (p: string) => p,
|
||||
});
|
||||
|
||||
await program.parseAsync(
|
||||
["voicecall", "start", "--to", "+1", "--message", "Hello"],
|
||||
{ from: "user" },
|
||||
);
|
||||
await program.parseAsync(["voicecall", "start", "--to", "+1", "--message", "Hello"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(logSpy).toHaveBeenCalled();
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user