mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
fix: cache plugin discovery realpaths
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Google Meet: route local Chrome joins through OpenClaw browser control instead of raw default Chrome, so agents use the configured OpenClaw browser profile when opening Meet. Thanks @openclaw.
|
||||
- Plugins/startup: reuse canonical realpath lookups throughout each plugin discovery pass, including package and manifest boundary checks, so Windows npm-global startups no longer repeat expensive path resolution for the same plugin roots. Fixes #65733. Thanks @welfo-beo.
|
||||
- Reply/link understanding: keep media and link preprocessing on stable runtime entrypoints and continue with raw message content if optional enrichment fails, so URL-bearing messages are no longer dropped after stale runtime chunk upgrades. Fixes #68466. Thanks @songshikang0111.
|
||||
- Discord: persist routed model-picker overrides when the hidden `/model` dispatch succeeds but the bound thread session store is still stale, including LM Studio suffixed model ids. Carries forward #61473. Thanks @Nanako0129.
|
||||
- Nodes/CLI: add `openclaw nodes remove --node <id|name|ip>` and `node.pair.remove` so stale gateway-owned node pairing records can be cleaned without hand-editing state files. Thanks @openclaw.
|
||||
|
||||
@@ -92,6 +92,7 @@ function slugifyPluginId(raw: string | undefined, rootDir: string): string {
|
||||
|
||||
function loadBundleManifestFile(params: {
|
||||
rootDir: string;
|
||||
rootRealPath?: string;
|
||||
manifestRelativePath: string;
|
||||
rejectHardlinks: boolean;
|
||||
allowMissing?: boolean;
|
||||
@@ -100,6 +101,7 @@ function loadBundleManifestFile(params: {
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: params.rootDir,
|
||||
...(params.rootRealPath !== undefined ? { rootRealPath: params.rootRealPath } : {}),
|
||||
boundaryLabel: "plugin root",
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
@@ -327,6 +329,7 @@ function buildCursorCapabilities(raw: Record<string, unknown>, rootDir: string):
|
||||
|
||||
export function loadBundleManifest(params: {
|
||||
rootDir: string;
|
||||
rootRealPath?: string;
|
||||
bundleFormat: PluginBundleFormat;
|
||||
rejectHardlinks?: boolean;
|
||||
}): BundleManifestLoadResult {
|
||||
@@ -339,6 +342,7 @@ export function loadBundleManifest(params: {
|
||||
: CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH;
|
||||
const loaded = loadBundleManifestFile({
|
||||
rootDir: params.rootDir,
|
||||
...(params.rootRealPath !== undefined ? { rootRealPath: params.rootRealPath } : {}),
|
||||
manifestRelativePath,
|
||||
rejectHardlinks,
|
||||
allowMissing: params.bundleFormat === "claude",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js";
|
||||
@@ -17,6 +18,21 @@ function makeTempDir() {
|
||||
|
||||
const mkdirSafe = mkdirSafeDir;
|
||||
|
||||
const canCreateDirectorySymlinks = (() => {
|
||||
const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-symlink-probe-"));
|
||||
const targetDir = path.join(probeDir, "target");
|
||||
const linkDir = path.join(probeDir, "link");
|
||||
try {
|
||||
fs.mkdirSync(targetDir);
|
||||
fs.symlinkSync(targetDir, linkDir, process.platform === "win32" ? "junction" : "dir");
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
fs.rmSync(probeDir, { recursive: true, force: true });
|
||||
}
|
||||
})();
|
||||
|
||||
function normalizePathForAssertion(value: string | undefined): string | undefined {
|
||||
if (!value) {
|
||||
return value;
|
||||
@@ -573,6 +589,75 @@ describe("discoverOpenClawPlugins", () => {
|
||||
expectCandidateIds(candidates, { includes: ["pack/one", "pack/two"] });
|
||||
});
|
||||
|
||||
it("reuses one filesystem realpath lookup per package root within a discovery run", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const packageDir = path.join(stateDir, "extensions", "pack");
|
||||
mkdirSafe(path.join(packageDir, "src"));
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir,
|
||||
packageName: "pack",
|
||||
extensions: ["./src/one.ts", "./src/two.ts"],
|
||||
});
|
||||
writePluginEntry(path.join(packageDir, "src", "one.ts"));
|
||||
writePluginEntry(path.join(packageDir, "src", "two.ts"));
|
||||
|
||||
const realpathSync = vi.spyOn(fs, "realpathSync");
|
||||
const { candidates } = discoverOpenClawPlugins({
|
||||
env: buildDiscoveryEnv(stateDir),
|
||||
cache: false,
|
||||
});
|
||||
|
||||
expectCandidateIds(candidates, { includes: ["pack/one", "pack/two"] });
|
||||
expect(
|
||||
realpathSync.mock.calls.filter(
|
||||
([targetPath]) => path.resolve(String(targetPath)) === path.resolve(packageDir),
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it.skipIf(!canCreateDirectorySymlinks)(
|
||||
"reuses the canonical realpath cache entry for symlinked package roots",
|
||||
() => {
|
||||
const stateDir = makeTempDir();
|
||||
const realPackageDir = path.join(stateDir, "real-pack");
|
||||
mkdirSafe(path.join(realPackageDir, "src"));
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: realPackageDir,
|
||||
packageName: "pack",
|
||||
extensions: ["./src/index.ts"],
|
||||
});
|
||||
writePluginEntry(path.join(realPackageDir, "src", "index.ts"));
|
||||
|
||||
const linkedPackageDir = path.join(stateDir, "linked-pack");
|
||||
fs.symlinkSync(
|
||||
realPackageDir,
|
||||
linkedPackageDir,
|
||||
process.platform === "win32" ? "junction" : "dir",
|
||||
);
|
||||
const canonicalPackageDir = fs.realpathSync(realPackageDir);
|
||||
|
||||
const realpathSync = vi.spyOn(fs, "realpathSync");
|
||||
const { candidates } = discoverOpenClawPlugins({
|
||||
extraPaths: [linkedPackageDir, canonicalPackageDir],
|
||||
env: buildDiscoveryEnv(stateDir),
|
||||
cache: false,
|
||||
});
|
||||
|
||||
expectCandidateIds(candidates, { includes: ["pack"] });
|
||||
expect(
|
||||
realpathSync.mock.calls.filter(([targetPath]) => {
|
||||
const resolved = path.resolve(String(targetPath));
|
||||
return (
|
||||
resolved === path.resolve(linkedPackageDir) ||
|
||||
resolved === path.resolve(canonicalPackageDir)
|
||||
);
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("uses explicit runtime extension entries for installed package plugins", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "extensions", "runtime-pack");
|
||||
|
||||
@@ -158,9 +158,10 @@ type CandidateBlockIssue = {
|
||||
function checkSourceEscapesRoot(params: {
|
||||
source: string;
|
||||
rootDir: string;
|
||||
realpathCache: Map<string, string>;
|
||||
}): CandidateBlockIssue | null {
|
||||
const sourceRealPath = safeRealpathSync(params.source);
|
||||
const rootRealPath = safeRealpathSync(params.rootDir);
|
||||
const sourceRealPath = safeRealpathSync(params.source, params.realpathCache);
|
||||
const rootRealPath = safeRealpathSync(params.rootDir, params.realpathCache);
|
||||
if (!sourceRealPath || !rootRealPath) {
|
||||
return null;
|
||||
}
|
||||
@@ -259,10 +260,12 @@ function findCandidateBlockIssue(params: {
|
||||
rootDir: string;
|
||||
origin: PluginOrigin;
|
||||
ownershipUid?: number | null;
|
||||
realpathCache: Map<string, string>;
|
||||
}): CandidateBlockIssue | null {
|
||||
const escaped = checkSourceEscapesRoot({
|
||||
source: params.source,
|
||||
rootDir: params.rootDir,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
if (escaped) {
|
||||
return escaped;
|
||||
@@ -294,12 +297,14 @@ function isUnsafePluginCandidate(params: {
|
||||
origin: PluginOrigin;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
ownershipUid?: number | null;
|
||||
realpathCache: Map<string, string>;
|
||||
}): boolean {
|
||||
const issue = findCandidateBlockIssue({
|
||||
source: params.source,
|
||||
rootDir: params.rootDir,
|
||||
origin: params.origin,
|
||||
ownershipUid: params.ownershipUid,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
if (!issue) {
|
||||
return false;
|
||||
@@ -348,12 +353,16 @@ function shouldIgnoreScannedDirectory(dirName: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolvesToSameDirectory(left?: string, right?: string): boolean {
|
||||
function resolvesToSameDirectory(
|
||||
left: string | undefined,
|
||||
right: string | undefined,
|
||||
realpathCache: Map<string, string>,
|
||||
): boolean {
|
||||
if (!left || !right) {
|
||||
return false;
|
||||
}
|
||||
const leftRealPath = safeRealpathSync(left);
|
||||
const rightRealPath = safeRealpathSync(right);
|
||||
const leftRealPath = safeRealpathSync(left, realpathCache);
|
||||
const rightRealPath = safeRealpathSync(right, realpathCache);
|
||||
if (leftRealPath && rightRealPath) {
|
||||
return leftRealPath === rightRealPath;
|
||||
}
|
||||
@@ -403,11 +412,16 @@ function getCachedDiscoveryResult(params: {
|
||||
return result;
|
||||
}
|
||||
|
||||
function readPackageManifest(dir: string, rejectHardlinks = true): PackageManifest | null {
|
||||
function readPackageManifest(
|
||||
dir: string,
|
||||
rejectHardlinks = true,
|
||||
rootRealPath?: string,
|
||||
): PackageManifest | null {
|
||||
const manifestPath = path.join(dir, "package.json");
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: dir,
|
||||
...(rootRealPath !== undefined ? { rootRealPath } : {}),
|
||||
boundaryLabel: "plugin package directory",
|
||||
rejectHardlinks,
|
||||
});
|
||||
@@ -456,8 +470,12 @@ function deriveIdHint(params: {
|
||||
return `${normalizedPackageId}/${base}`;
|
||||
}
|
||||
|
||||
function resolveIdHintManifestId(rootDir: string, rejectHardlinks: boolean): string | undefined {
|
||||
const manifest = loadPluginManifest(rootDir, rejectHardlinks);
|
||||
function resolveIdHintManifestId(
|
||||
rootDir: string,
|
||||
rejectHardlinks: boolean,
|
||||
rootRealPath?: string,
|
||||
): string | undefined {
|
||||
const manifest = loadPluginManifest(rootDir, rejectHardlinks, rootRealPath);
|
||||
return manifest.ok ? manifest.manifest.id : undefined;
|
||||
}
|
||||
|
||||
@@ -478,12 +496,14 @@ function addCandidate(params: {
|
||||
packageDir?: string;
|
||||
bundledManifest?: PluginManifest;
|
||||
bundledManifestPath?: string;
|
||||
realpathCache: Map<string, string>;
|
||||
}) {
|
||||
const resolved = path.resolve(params.source);
|
||||
if (params.seen.has(resolved)) {
|
||||
return;
|
||||
}
|
||||
const resolvedRoot = safeRealpathSync(params.rootDir) ?? path.resolve(params.rootDir);
|
||||
const resolvedRoot =
|
||||
safeRealpathSync(params.rootDir, params.realpathCache) ?? path.resolve(params.rootDir);
|
||||
if (
|
||||
isUnsafePluginCandidate({
|
||||
source: resolved,
|
||||
@@ -491,6 +511,7 @@ function addCandidate(params: {
|
||||
origin: params.origin,
|
||||
diagnostics: params.diagnostics,
|
||||
ownershipUid: params.ownershipUid,
|
||||
realpathCache: params.realpathCache,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
@@ -524,13 +545,16 @@ function discoverBundleInRoot(params: {
|
||||
candidates: PluginCandidate[];
|
||||
diagnostics: PluginDiagnostic[];
|
||||
seen: Set<string>;
|
||||
realpathCache: Map<string, string>;
|
||||
}): "added" | "invalid" | "none" {
|
||||
const bundleFormat = detectBundleManifestFormat(params.rootDir);
|
||||
if (!bundleFormat) {
|
||||
return "none";
|
||||
}
|
||||
const rootRealPath = safeRealpathSync(params.rootDir, params.realpathCache) ?? undefined;
|
||||
const bundleManifest = loadBundleManifest({
|
||||
rootDir: params.rootDir,
|
||||
...(rootRealPath !== undefined ? { rootRealPath } : {}),
|
||||
bundleFormat,
|
||||
rejectHardlinks: params.origin !== "bundled",
|
||||
});
|
||||
@@ -554,6 +578,7 @@ function discoverBundleInRoot(params: {
|
||||
bundleFormat,
|
||||
ownershipUid: params.ownershipUid,
|
||||
workspaceDir: params.workspaceDir,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
return "added";
|
||||
}
|
||||
@@ -566,6 +591,7 @@ function discoverInDirectory(params: {
|
||||
candidates: PluginCandidate[];
|
||||
diagnostics: PluginDiagnostic[];
|
||||
seen: Set<string>;
|
||||
realpathCache: Map<string, string>;
|
||||
recurseDirectories?: boolean;
|
||||
skipDirectories?: Set<string>;
|
||||
visitedDirectories?: Set<string>;
|
||||
@@ -573,7 +599,8 @@ function discoverInDirectory(params: {
|
||||
if (!fs.existsSync(params.dir)) {
|
||||
return;
|
||||
}
|
||||
const resolvedDir = safeRealpathSync(params.dir) ?? path.resolve(params.dir);
|
||||
const resolvedDir =
|
||||
safeRealpathSync(params.dir, params.realpathCache) ?? path.resolve(params.dir);
|
||||
if (params.recurseDirectories) {
|
||||
if (params.visitedDirectories?.has(resolvedDir)) {
|
||||
return;
|
||||
@@ -608,6 +635,7 @@ function discoverInDirectory(params: {
|
||||
origin: params.origin,
|
||||
ownershipUid: params.ownershipUid,
|
||||
workspaceDir: params.workspaceDir,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
}
|
||||
if (!entry.isDirectory()) {
|
||||
@@ -621,12 +649,14 @@ function discoverInDirectory(params: {
|
||||
}
|
||||
|
||||
const rejectHardlinks = params.origin !== "bundled";
|
||||
const manifest = readPackageManifest(fullPath, rejectHardlinks);
|
||||
const fullPathRealPath = safeRealpathSync(fullPath, params.realpathCache) ?? undefined;
|
||||
const manifest = readPackageManifest(fullPath, rejectHardlinks, fullPathRealPath);
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
const manifestId = resolveIdHintManifestId(fullPath, rejectHardlinks);
|
||||
const manifestId = resolveIdHintManifestId(fullPath, rejectHardlinks, fullPathRealPath);
|
||||
const setupSource = resolvePackageSetupSource({
|
||||
packageDir: fullPath,
|
||||
...(fullPathRealPath !== undefined ? { packageRootRealPath: fullPathRealPath } : {}),
|
||||
manifest,
|
||||
origin: params.origin,
|
||||
sourceLabel: fullPath,
|
||||
@@ -637,6 +667,7 @@ function discoverInDirectory(params: {
|
||||
if (extensions.length > 0) {
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionSources({
|
||||
packageDir: fullPath,
|
||||
...(fullPathRealPath !== undefined ? { packageRootRealPath: fullPathRealPath } : {}),
|
||||
manifest,
|
||||
extensions,
|
||||
origin: params.origin,
|
||||
@@ -663,6 +694,7 @@ function discoverInDirectory(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
manifest,
|
||||
packageDir: fullPath,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
@@ -676,6 +708,7 @@ function discoverInDirectory(params: {
|
||||
candidates: params.candidates,
|
||||
diagnostics: params.diagnostics,
|
||||
seen: params.seen,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
if (bundleDiscovery === "added") {
|
||||
continue;
|
||||
@@ -698,6 +731,7 @@ function discoverInDirectory(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
manifest,
|
||||
packageDir: fullPath,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -720,6 +754,7 @@ function discoverFromPath(params: {
|
||||
candidates: PluginCandidate[];
|
||||
diagnostics: PluginDiagnostic[];
|
||||
seen: Set<string>;
|
||||
realpathCache: Map<string, string>;
|
||||
}) {
|
||||
const resolved = resolveUserPath(params.rawPath, params.env);
|
||||
if (!fs.existsSync(resolved)) {
|
||||
@@ -751,18 +786,21 @@ function discoverFromPath(params: {
|
||||
origin: params.origin,
|
||||
ownershipUid: params.ownershipUid,
|
||||
workspaceDir: params.workspaceDir,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const rejectHardlinks = params.origin !== "bundled";
|
||||
const manifest = readPackageManifest(resolved, rejectHardlinks);
|
||||
const resolvedRealPath = safeRealpathSync(resolved, params.realpathCache) ?? undefined;
|
||||
const manifest = readPackageManifest(resolved, rejectHardlinks, resolvedRealPath);
|
||||
const extensionResolution = resolvePackageExtensionEntries(manifest ?? undefined);
|
||||
const extensions = extensionResolution.status === "ok" ? extensionResolution.entries : [];
|
||||
const manifestId = resolveIdHintManifestId(resolved, rejectHardlinks);
|
||||
const manifestId = resolveIdHintManifestId(resolved, rejectHardlinks, resolvedRealPath);
|
||||
const setupSource = resolvePackageSetupSource({
|
||||
packageDir: resolved,
|
||||
...(resolvedRealPath !== undefined ? { packageRootRealPath: resolvedRealPath } : {}),
|
||||
manifest,
|
||||
origin: params.origin,
|
||||
sourceLabel: resolved,
|
||||
@@ -773,6 +811,7 @@ function discoverFromPath(params: {
|
||||
if (extensions.length > 0) {
|
||||
const resolvedRuntimeSources = resolvePackageRuntimeExtensionSources({
|
||||
packageDir: resolved,
|
||||
...(resolvedRealPath !== undefined ? { packageRootRealPath: resolvedRealPath } : {}),
|
||||
manifest,
|
||||
extensions,
|
||||
origin: params.origin,
|
||||
@@ -799,6 +838,7 @@ function discoverFromPath(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
manifest,
|
||||
packageDir: resolved,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
}
|
||||
return;
|
||||
@@ -812,6 +852,7 @@ function discoverFromPath(params: {
|
||||
candidates: params.candidates,
|
||||
diagnostics: params.diagnostics,
|
||||
seen: params.seen,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
if (bundleDiscovery === "added") {
|
||||
return;
|
||||
@@ -835,6 +876,7 @@ function discoverFromPath(params: {
|
||||
workspaceDir: params.workspaceDir,
|
||||
manifest,
|
||||
packageDir: resolved,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -847,6 +889,7 @@ function discoverFromPath(params: {
|
||||
candidates: params.candidates,
|
||||
diagnostics: params.diagnostics,
|
||||
seen: params.seen,
|
||||
realpathCache: params.realpathCache,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -876,6 +919,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
load: () => {
|
||||
const result = createDiscoveryResult();
|
||||
const seen = new Set<string>();
|
||||
const realpathCache = new Map<string, string>();
|
||||
const extra = params.extraPaths ?? [];
|
||||
for (const extraPath of extra) {
|
||||
if (typeof extraPath !== "string") {
|
||||
@@ -906,9 +950,14 @@ export function discoverOpenClawPlugins(params: {
|
||||
candidates: result.candidates,
|
||||
diagnostics: result.diagnostics,
|
||||
seen,
|
||||
realpathCache,
|
||||
});
|
||||
}
|
||||
const workspaceMatchesBundledRoot = resolvesToSameDirectory(workspaceRoot, roots.stock);
|
||||
const workspaceMatchesBundledRoot = resolvesToSameDirectory(
|
||||
workspaceRoot,
|
||||
roots.stock,
|
||||
realpathCache,
|
||||
);
|
||||
if (roots.workspace && workspaceRoot && !workspaceMatchesBundledRoot) {
|
||||
// Keep workspace auto-discovery constrained to the OpenClaw extensions root.
|
||||
// Recursively scanning the full workspace treats arbitrary project folders as
|
||||
@@ -921,6 +970,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
candidates: result.candidates,
|
||||
diagnostics: result.diagnostics,
|
||||
seen,
|
||||
realpathCache,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -936,6 +986,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
load: () => {
|
||||
const result = createDiscoveryResult();
|
||||
const seen = new Set<string>();
|
||||
const realpathCache = new Map<string, string>();
|
||||
for (const sourceOverlayDir of listBundledSourceOverlayDirs({
|
||||
bundledRoot: roots.stock,
|
||||
env,
|
||||
@@ -949,6 +1000,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
candidates: result.candidates,
|
||||
diagnostics: result.diagnostics,
|
||||
seen,
|
||||
realpathCache,
|
||||
});
|
||||
result.diagnostics.push({
|
||||
level: "warn",
|
||||
@@ -965,6 +1017,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
candidates: result.candidates,
|
||||
diagnostics: result.diagnostics,
|
||||
seen,
|
||||
realpathCache,
|
||||
});
|
||||
}
|
||||
// Keep auto-discovered global extensions behind bundled plugins.
|
||||
@@ -976,6 +1029,7 @@ export function discoverOpenClawPlugins(params: {
|
||||
candidates: result.candidates,
|
||||
diagnostics: result.diagnostics,
|
||||
seen,
|
||||
realpathCache,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -1151,11 +1151,13 @@ function parsePluginKind(raw: unknown): PluginKind | PluginKind[] | undefined {
|
||||
export function loadPluginManifest(
|
||||
rootDir: string,
|
||||
rejectHardlinks = true,
|
||||
rootRealPath?: string,
|
||||
): PluginManifestLoadResult {
|
||||
const manifestPath = resolvePluginManifestPath(rootDir);
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: rootDir,
|
||||
...(rootRealPath !== undefined ? { rootRealPath } : {}),
|
||||
boundaryLabel: "plugin root",
|
||||
maxBytes: MAX_PLUGIN_MANIFEST_BYTES,
|
||||
rejectHardlinks,
|
||||
|
||||
@@ -173,6 +173,7 @@ export async function validatePackageExtensionEntriesForInstall(params: {
|
||||
|
||||
function resolvePackageEntrySource(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
@@ -185,6 +186,9 @@ function resolvePackageEntrySource(params: {
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { rootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
boundaryLabel: "plugin package directory",
|
||||
rejectHardlinks,
|
||||
});
|
||||
@@ -236,6 +240,7 @@ function shouldInferBuiltRuntimeEntry(origin: PluginOrigin): boolean {
|
||||
|
||||
function resolveSafePackageEntry(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
@@ -245,6 +250,9 @@ function resolveSafePackageEntry(params: {
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
const existingSource = resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
@@ -263,6 +271,9 @@ function resolveSafePackageEntry(params: {
|
||||
resolveBoundaryPathSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { rootCanonicalPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
boundaryLabel: "plugin package directory",
|
||||
});
|
||||
} catch {
|
||||
@@ -278,6 +289,7 @@ function resolveSafePackageEntry(params: {
|
||||
|
||||
function resolveExistingPackageEntrySource(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
entryPath: string;
|
||||
sourceLabel: string;
|
||||
diagnostics: PluginDiagnostic[];
|
||||
@@ -292,6 +304,7 @@ function resolveExistingPackageEntrySource(params: {
|
||||
|
||||
function resolvePackageRuntimeEntrySource(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
entryPath: string;
|
||||
runtimeEntryPath?: string;
|
||||
origin: PluginOrigin;
|
||||
@@ -301,6 +314,9 @@ function resolvePackageRuntimeEntrySource(params: {
|
||||
}): string | null {
|
||||
const safeEntry = resolveSafePackageEntry({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
@@ -313,6 +329,9 @@ function resolvePackageRuntimeEntrySource(params: {
|
||||
if (params.runtimeEntryPath) {
|
||||
const runtimeSource = resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: params.runtimeEntryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
@@ -327,6 +346,9 @@ function resolvePackageRuntimeEntrySource(params: {
|
||||
for (const candidate of listBuiltRuntimeEntryCandidates(safeEntry.relativePath)) {
|
||||
const runtimeSource = resolveExistingPackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: candidate,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
@@ -344,6 +366,9 @@ function resolvePackageRuntimeEntrySource(params: {
|
||||
|
||||
return resolvePackageEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: params.entryPath,
|
||||
sourceLabel: params.sourceLabel,
|
||||
diagnostics: params.diagnostics,
|
||||
@@ -353,6 +378,7 @@ function resolvePackageRuntimeEntrySource(params: {
|
||||
|
||||
export function resolvePackageSetupSource(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
manifest: PackageManifest | null;
|
||||
origin: PluginOrigin;
|
||||
sourceLabel: string;
|
||||
@@ -366,6 +392,9 @@ export function resolvePackageSetupSource(params: {
|
||||
}
|
||||
return resolvePackageRuntimeEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath: setupEntryPath,
|
||||
runtimeEntryPath: normalizeOptionalString(packageManifest?.runtimeSetupEntry),
|
||||
origin: params.origin,
|
||||
@@ -377,6 +406,7 @@ export function resolvePackageSetupSource(params: {
|
||||
|
||||
export function resolvePackageRuntimeExtensionSources(params: {
|
||||
packageDir: string;
|
||||
packageRootRealPath?: string;
|
||||
manifest: PackageManifest | null;
|
||||
extensions: readonly string[];
|
||||
origin: PluginOrigin;
|
||||
@@ -400,6 +430,9 @@ export function resolvePackageRuntimeExtensionSources(params: {
|
||||
return params.extensions.flatMap((entryPath, index) => {
|
||||
const source = resolvePackageRuntimeEntrySource({
|
||||
packageDir: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
? { packageRootRealPath: params.packageRootRealPath }
|
||||
: {}),
|
||||
entryPath,
|
||||
runtimeEntryPath: runtimeResolution.runtimeExtensions[index],
|
||||
origin: params.origin,
|
||||
|
||||
@@ -13,6 +13,7 @@ export function safeRealpathSync(targetPath: string, cache?: Map<string, string>
|
||||
try {
|
||||
const resolved = fs.realpathSync(targetPath);
|
||||
cache?.set(targetPath, resolved);
|
||||
cache?.set(resolved, resolved);
|
||||
return resolved;
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user