fix: keep inspector target cache in workspace (#3347)

This commit is contained in:
Vincent Koc
2026-07-31 17:41:54 +08:00
committed by GitHub
parent e9316c1c7d
commit c762d8ec6d
4 changed files with 76 additions and 12 deletions
+1
View File
@@ -9,6 +9,7 @@
### Fixes
- API: keep publish-time Plugin Inspector target preparation inside its disposable workspace when hosted runtimes expose an unusable home directory.
- API: keep older code-plugin and Claw backports from replacing the highest-semver `latest` release while preserving custom distribution tags.
- Integrations: truncate publisher-controlled Discord webhook titles to the platform's 256-character embed limit.
- Security: recover scheduled temporal publisher-abuse scans from strict Convex payload validation failures without leaving zombie running runs (thanks @jesse-merhi).
+6
View File
@@ -26,9 +26,15 @@ declare module "@openclaw/plugin-inspector" {
allowExecution?: boolean;
configPath?: string;
generatedAt?: string;
targetOpenClaw?: unknown;
}): Promise<{ report: PluginInspectorReport; paths: PluginInspectorPaths }>;
};
export const openClawTargets: {
resolveVersion(requestedVersion: string): Promise<unknown>;
prepare(resolvedTarget: unknown, options?: { cacheDir?: string }): Promise<unknown>;
};
export const reports: {
renderTextSummary(report: PluginInspectorReport, options?: Record<string, unknown>): string;
sanitizeArtifact(report: PluginInspectorReport): unknown;
+30 -4
View File
@@ -1,11 +1,12 @@
/* @vitest-environment node */
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
buildPublishInspectorRunCheckOptions,
createPackageInspectorWorkspace,
normalizeInspectorReportForPublish,
preparePublishInspectorOpenClawTarget,
} from "./packageInspectorNode";
const originalPlatform = process.platform;
@@ -60,12 +61,37 @@ describe("package inspector publish normalization", () => {
);
});
it("targets latest stable OpenClaw for publish-time inspection", () => {
expect(buildPublishInspectorRunCheckOptions("/tmp/plugin", "2026-07-30T00:00:00.000Z")).toEqual(
it("prepares latest stable OpenClaw with a cache outside the inspected package", async () => {
const resolvedTarget = { version: "2026.7.0" };
const preparedTarget = { status: "ok", version: "2026.7.0" };
const resolveVersion = vi.fn(async () => resolvedTarget);
const prepare = vi.fn(async () => preparedTarget);
await expect(
preparePublishInspectorOpenClawTarget("/tmp/plugin", { resolveVersion, prepare }),
).resolves.toBe(preparedTarget);
expect(resolveVersion).toHaveBeenCalledWith("latest");
expect(prepare).toHaveBeenCalledWith(resolvedTarget, {
cacheDir: path.join("/tmp/plugin", ".plugin-inspector-cache"),
});
expect(path.join("/tmp/plugin", ".plugin-inspector-cache")).not.toContain(
path.join("/tmp/plugin", "package") + path.sep,
);
});
it("uses the prepared OpenClaw target for publish-time inspection", () => {
const targetOpenClaw = { status: "ok", version: "2026.7.0" };
expect(
buildPublishInspectorRunCheckOptions(
"/tmp/plugin",
"2026-07-30T00:00:00.000Z",
targetOpenClaw,
),
).toEqual(
expect.objectContaining({
pluginRoot: "/tmp/plugin",
openclawPath: false,
openclawVersion: "latest",
targetOpenClaw,
authorFacing: true,
}),
);
+39 -8
View File
@@ -130,11 +130,32 @@ const inspectorMetadataValidator = v.object({
targetOpenClawVersion: v.optional(v.string()),
});
export function buildPublishInspectorRunCheckOptions(root: string, generatedAt: string) {
export async function preparePublishInspectorOpenClawTarget<ResolvedTarget, PreparedTarget>(
root: string,
targets: {
resolveVersion: (requestedVersion: string) => Promise<ResolvedTarget>;
prepare: (
resolvedTarget: ResolvedTarget,
options: { cacheDir: string },
) => Promise<PreparedTarget>;
},
) {
const resolvedTarget = await targets.resolveVersion("latest");
return await targets.prepare(resolvedTarget, {
// The dependency defaults to os.homedir(), which can be unusable in serverless runtimes.
cacheDir: path.join(root, ".plugin-inspector-cache"),
});
}
export function buildPublishInspectorRunCheckOptions(
root: string,
generatedAt: string,
targetOpenClaw: unknown,
) {
return {
pluginRoot: root,
openclawPath: false,
openclawVersion: "latest",
targetOpenClaw,
outDir: "reports",
capture: false,
mockSdk: true,
@@ -187,9 +208,11 @@ export const runPackageInspectorForPublishInternal = internalAction({
metadata: inspectorMetadataValidator,
}),
handler: async (ctx, args) => {
let root: string | undefined;
let workspaceRoot: string | undefined;
try {
root = await createPackageInspectorWorkspace();
workspaceRoot = await createPackageInspectorWorkspace();
const root = path.join(workspaceRoot, "package");
await mkdir(root, { recursive: true });
for (const file of args.files) {
const blob = await ctx.storage.get(file.storageId);
if (!blob) {
@@ -201,8 +224,16 @@ export const runPackageInspectorForPublishInternal = internalAction({
}
await writeSyntheticInspectorConfigIfNeeded(root, args.files, args.packageName);
const { pluginRoot } = await import("@openclaw/plugin-inspector");
const runCheckOptions = buildPublishInspectorRunCheckOptions(root, new Date().toISOString());
const { openClawTargets, pluginRoot } = await import("@openclaw/plugin-inspector");
const targetOpenClaw = await preparePublishInspectorOpenClawTarget(
workspaceRoot,
openClawTargets,
);
const runCheckOptions = buildPublishInspectorRunCheckOptions(
root,
new Date().toISOString(),
targetOpenClaw,
);
const { report } = await pluginRoot.runCheck(runCheckOptions);
return normalizeInspectorReportForPublish(report);
@@ -230,8 +261,8 @@ export const runPackageInspectorForPublishInternal = internalAction({
},
};
} finally {
if (root) {
await rm(root, { recursive: true, force: true });
if (workspaceRoot) {
await rm(workspaceRoot, { recursive: true, force: true });
}
}
},