diff --git a/extensions/matrix/runtime-api.ts b/extensions/matrix/runtime-api.ts index bc8163c9969..04957e707c5 100644 --- a/extensions/matrix/runtime-api.ts +++ b/extensions/matrix/runtime-api.ts @@ -2,3 +2,4 @@ // helpers without traversing the full plugin-sdk/runtime graph. export * from "./src/auth-precedence.js"; export * from "./helper-api.js"; +export { sendMessageMatrix } from "./src/matrix/send.js"; diff --git a/extensions/matrix/src/matrix/thread-bindings.ts b/extensions/matrix/src/matrix/thread-bindings.ts index edbbde5d000..593d88ed7eb 100644 --- a/extensions/matrix/src/matrix/thread-bindings.ts +++ b/extensions/matrix/src/matrix/thread-bindings.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { sendMessageMatrix } from "../../runtime-api.js"; import { readJsonFileWithFallback, registerSessionBindingAdapter, @@ -10,7 +11,6 @@ import { import { resolveMatrixStoragePaths } from "./client/storage.js"; import type { MatrixAuth } from "./client/types.js"; import type { MatrixClient } from "./sdk.js"; -import { sendMessageMatrix } from "./send.js"; import { deleteMatrixThreadBindingManagerEntry, getMatrixThreadBindingManager, diff --git a/src/channels/plugins/contracts/registry.ts b/src/channels/plugins/contracts/registry.ts index cc2b8b7f34a..cf12d4f4355 100644 --- a/src/channels/plugins/contracts/registry.ts +++ b/src/channels/plugins/contracts/registry.ts @@ -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, diff --git a/src/hooks/workspace.ts b/src/hooks/workspace.ts index 351690ab9d3..b4c2fa4a1f3 100644 --- a/src/hooks/workspace.ts +++ b/src/hooks/workspace.ts @@ -28,6 +28,11 @@ type HookPackageManifest = { } & Partial>; 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, diff --git a/src/plugins/bundle-mcp.test-support.ts b/src/plugins/bundle-mcp.test-support.ts index 8b6723e7e13..009078f8f8a 100644 --- a/src/plugins/bundle-mcp.test-support.ts +++ b/src/plugins/bundle-mcp.test-support.ts @@ -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 diff --git a/src/plugins/bundle-mcp.ts b/src/plugins/bundle-mcp.ts index ebe1b369f3c..c4e2f0651bb 100644 --- a/src/plugins/bundle-mcp.ts +++ b/src/plugins/bundle-mcp.ts @@ -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; @@ -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(