fix(artifacts): update export artifacts module

This commit is contained in:
Haitao Pan 2026-06-17 21:01:47 +08:00
parent 0682fbf7cf
commit df3aab97be
2 changed files with 66 additions and 1 deletions

View File

@ -177,6 +177,30 @@ describe("exportXWorkmateArtifacts", () => {
expect(result.missingRequiredExtensions).toEqual(["pdf"]);
});
it("reports missing required artifact file counts", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({
params: { openclawSessionKey: "thread-main", runId: "run-missing-count" },
pluginConfig: { workspaceDir: root },
});
await fs.mkdir(path.join(prepared.artifactDirectory, "assets", "images"), { recursive: true });
await fs.writeFile(path.join(prepared.artifactDirectory, "assets", "images", "001.png"), "png");
const result = await exportXWorkmateArtifacts({
params: {
openclawSessionKey: "thread-main",
runId: "run-missing-count",
requiredArtifactExtensions: ["png"],
expectedFileCountByExtension: { png: 7 },
},
pluginConfig: { workspaceDir: root },
});
expect(result.constraintSatisfied).toBe(false);
expect(result.missingRequiredExtensions).toEqual([]);
expect(result.missingRequiredFileCounts).toEqual({ png: { expected: 7, actual: 1 } });
});
it("treats an empty required artifact extension list as satisfied", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "tmp-openclaw-multi-session-plugins-"));
const prepared = await prepareXWorkmateArtifacts({

View File

@ -49,6 +49,7 @@ type XWorkmateArtifactExport = {
expectedArtifactDirStatus: XWorkmateExpectedArtifactDirStatus[];
constraintSatisfied: boolean;
missingRequiredExtensions: string[];
missingRequiredFileCounts: Record<string, { expected: number; actual: number }>;
};
type XWorkmateArtifactPrepare = {
@ -238,6 +239,7 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise<XWor
const sinceUnixMs = nonNegativeNumber(params.sinceUnixMs, 0);
const includeContent = optionalBoolean(params.includeContent, true);
const requiredArtifactExtensions = normalizeRequiredExtensions(params.requiredArtifactExtensions);
const expectedFileCountByExtension = normalizeExpectedFileCountByExtension(params.expectedFileCountByExtension);
const workspaceDir = resolveWorkspaceDir({
config: input.config,
pluginConfig,
@ -361,6 +363,7 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise<XWor
artifacts.push(artifact);
}
const missingRequiredExtensions = missingRequiredArtifactExtensions(artifacts, requiredArtifactExtensions);
const missingRequiredFileCounts = missingRequiredArtifactFileCounts(artifacts, expectedFileCountByExtension);
const result = {
runId,
@ -373,8 +376,9 @@ export async function exportXWorkmateArtifacts(input: ExportInput): Promise<XWor
warnings,
expectedArtifactDirs: expectedDirs,
expectedArtifactDirStatus: await expectedArtifactDirStatuses(workspaceRoot, expectedDirs),
constraintSatisfied: missingRequiredExtensions.length === 0,
constraintSatisfied: missingRequiredExtensions.length === 0 && Object.keys(missingRequiredFileCounts).length === 0,
missingRequiredExtensions,
missingRequiredFileCounts,
};
return result;
}
@ -488,6 +492,7 @@ export async function readXWorkmateArtifact(input: ReadInput): Promise<XWorkmate
expectedArtifactDirStatus: [],
constraintSatisfied: true,
missingRequiredExtensions: [],
missingRequiredFileCounts: {},
};
return result;
}
@ -534,6 +539,42 @@ function missingRequiredArtifactExtensions(
);
}
function normalizeExpectedFileCountByExtension(value: unknown): Record<string, number> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return {};
}
const result: Record<string, number> = {};
for (const [rawExtension, rawCount] of Object.entries(value as Record<string, unknown>)) {
const extension = rawExtension
.toLowerCase()
.trim()
.replace(/^\.+/u, "");
if (!extension || extension.includes("/") || extension.includes("\\") || extension.includes("\0")) {
continue;
}
const count = typeof rawCount === "number" ? rawCount : Number.parseInt(optionalString(rawCount), 10);
if (!Number.isFinite(count) || count <= 0) {
continue;
}
result[extension] = Math.floor(count);
}
return result;
}
function missingRequiredArtifactFileCounts(
artifacts: XWorkmateArtifact[],
expectedFileCountByExtension: Record<string, number>,
): Record<string, { expected: number; actual: number }> {
const missing: Record<string, { expected: number; actual: number }> = {};
for (const [extension, expected] of Object.entries(expectedFileCountByExtension)) {
const actual = artifacts.filter((artifact) => artifact.relativePath.toLowerCase().endsWith(`.${extension}`)).length;
if (actual < expected) {
missing[extension] = { expected, actual };
}
}
return missing;
}
async function expectedArtifactDirStatuses(
workspaceRoot: string,
expectedArtifactDirs: string[],