fix(ci): stabilize bundle hooks and mcp path seams

This commit is contained in:
Vincent Koc
2026-03-19 14:23:10 -07:00
parent 247a19a694
commit 3c806a9692
6 changed files with 35 additions and 30 deletions

View File

@@ -223,10 +223,10 @@ bundledChannelRuntimeSetters.setLineRuntime({
},
} as never);
vi.mock("../../../../extensions/matrix/src/matrix/send.js", async () => {
vi.mock("../../../../extensions/matrix/runtime-api.js", async () => {
const actual = await vi.importActual<
typeof import("../../../../extensions/matrix/src/matrix/send.js")
>("../../../../extensions/matrix/src/matrix/send.js");
typeof import("../../../../extensions/matrix/runtime-api.js")
>("../../../../extensions/matrix/runtime-api.js");
return {
...actual,
sendMessageMatrix: sendMessageMatrixMock,

View File

@@ -28,6 +28,11 @@ type HookPackageManifest = {
} & Partial<Record<typeof MANIFEST_KEY, { hooks?: string[] }>>;
const log = createSubsystemLogger("hooks/workspace");
type LoadedHook = {
hook: Hook;
frontmatter: ParsedHookFrontmatter;
};
function filterHookEntries(
entries: HookEntry[],
config?: OpenClawConfig,
@@ -79,7 +84,7 @@ function loadHookFromDir(params: {
source: HookSource;
pluginId?: string;
nameHint?: string;
}): Hook | null {
}): LoadedHook | null {
const hookMdPath = path.join(params.hookDir, "HOOK.md");
const content = readBoundaryFileUtf8({
absolutePath: hookMdPath,
@@ -123,13 +128,16 @@ function loadHookFromDir(params: {
}
return {
name,
description,
source: params.source,
pluginId: params.pluginId,
filePath: hookMdPath,
baseDir,
handlerPath,
hook: {
name,
description,
source: params.source,
pluginId: params.pluginId,
filePath: hookMdPath,
baseDir,
handlerPath,
},
frontmatter,
};
} catch (err) {
const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
@@ -141,7 +149,11 @@ function loadHookFromDir(params: {
/**
* Scan a directory for hooks (subdirectories containing HOOK.md)
*/
function loadHooksFromDir(params: { dir: string; source: HookSource; pluginId?: string }): Hook[] {
function loadHooksFromDir(params: {
dir: string;
source: HookSource;
pluginId?: string;
}): LoadedHook[] {
const { dir, source, pluginId } = params;
if (!fs.existsSync(dir)) {
@@ -153,7 +165,7 @@ function loadHooksFromDir(params: { dir: string; source: HookSource; pluginId?:
return [];
}
const hooks: Hook[] = [];
const hooks: LoadedHook[] = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
@@ -211,16 +223,7 @@ export function loadHookEntriesFromDir(params: {
source: params.source,
pluginId: params.pluginId,
});
return hooks.map((hook) => {
let frontmatter: ParsedHookFrontmatter = {};
const raw = readBoundaryFileUtf8({
absolutePath: hook.filePath,
rootPath: hook.baseDir,
boundaryLabel: "hook directory",
});
if (raw !== null) {
frontmatter = parseFrontmatter(raw);
}
return hooks.map(({ hook, frontmatter }) => {
const entry: HookEntry = {
hook: {
...hook,

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { clearPluginDiscoveryCache } from "./discovery.js";
import { clearPluginManifestRegistryCache } from "./manifest-registry.js";
export function createBundleMcpTempHarness() {
@@ -13,6 +14,7 @@ export function createBundleMcpTempHarness() {
return dir;
},
async cleanup() {
clearPluginDiscoveryCache();
clearPluginManifestRegistryCache();
await Promise.all(
tempDirs

View File

@@ -13,7 +13,6 @@ import {
} from "./bundle-manifest.js";
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
import { loadPluginManifestRegistry } from "./manifest-registry.js";
import { safeRealpathSync } from "./path-safety.js";
import type { PluginBundleFormat } from "./types.js";
export type BundleMcpServerConfig = Record<string, unknown>;
@@ -122,8 +121,8 @@ function expandBundleRootPlaceholders(value: string, rootDir: string): string {
return value.split(CLAUDE_PLUGIN_ROOT_PLACEHOLDER).join(rootDir);
}
function canonicalizeBundlePath(targetPath: string): string {
return path.normalize(safeRealpathSync(targetPath) ?? path.resolve(targetPath));
function normalizeBundlePath(targetPath: string): string {
return path.normalize(path.resolve(targetPath));
}
function normalizeExpandedAbsolutePath(value: string): string {
@@ -194,7 +193,7 @@ function loadBundleFileBackedMcpConfig(params: {
rootDir: string;
relativePath: string;
}): BundleMcpConfig {
const rootDir = canonicalizeBundlePath(params.rootDir);
const rootDir = normalizeBundlePath(params.rootDir);
const absolutePath = path.resolve(rootDir, params.relativePath);
const opened = openBoundaryFileSync({
absolutePath,
@@ -212,7 +211,7 @@ function loadBundleFileBackedMcpConfig(params: {
}
const raw = JSON.parse(fs.readFileSync(opened.fd, "utf-8")) as unknown;
const servers = extractMcpServerMap(raw);
const baseDir = canonicalizeBundlePath(path.dirname(absolutePath));
const baseDir = normalizeBundlePath(path.dirname(absolutePath));
return {
mcpServers: Object.fromEntries(
Object.entries(servers).map(([serverName, server]) => [
@@ -233,7 +232,7 @@ function loadBundleInlineMcpConfig(params: {
if (!isRecord(params.raw.mcpServers)) {
return { mcpServers: {} };
}
const baseDir = canonicalizeBundlePath(params.baseDir);
const baseDir = normalizeBundlePath(params.baseDir);
const servers = extractMcpServerMap(params.raw.mcpServers);
return {
mcpServers: Object.fromEntries(