

























@@ -1,8 +1,15 @@
11import { afterEach, describe, expect, it, vi } from "vitest";
2+import type { SkillSnapshot } from "../agents/skills.js";
23import { REDACTED_SENTINEL } from "../config/redact-snapshot.js";
4+import {
5+redactPathForSupport,
6+type SupportRedactionContext,
7+} from "../logging/diagnostic-support-redaction.js";
38import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
49import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";
51011+type ResolvedSkillEntry = NonNullable<SkillSnapshot["resolvedSkills"]>[number];
12+613const loadPluginManifestRegistry = vi.hoisted(() => vi.fn(() => ({ plugins: [] })));
714815vi.mock("../infra/git-commit.js", () => ({
@@ -174,6 +181,81 @@ describe("trajectory metadata", () => {
174181expect(skills.entries?.[0]?.filePath).toBe("/tmp/workspace/skills/weather/SKILL.md");
175182});
176183184+it("tolerates skill snapshot entries with missing name/paths (symlink-escape rejects)", () => {
185+const metadata = buildTrajectoryRunMetadata({
186+workspaceDir: "/tmp/workspace",
187+sessionFile: "/tmp/workspace/session.jsonl",
188+timeoutMs: 30_000,
189+skillsSnapshot: {
190+prompt: "skill prompt",
191+version: 1,
192+skills: [],
193+resolvedSkills: [
194+{
195+name: "alpha",
196+description: "valid entry",
197+filePath: "/tmp/workspace/skills/alpha/SKILL.md",
198+baseDir: "/tmp/workspace/skills/alpha",
199+source: "workspace",
200+sourceInfo: {
201+path: "/tmp/workspace/skills/alpha/SKILL.md",
202+source: "workspace",
203+scope: "project",
204+origin: "top-level",
205+baseDir: "/tmp/workspace/skills/alpha",
206+},
207+disableModelInvocation: false,
208+},
209+{
210+name: undefined,
211+description: undefined,
212+filePath: undefined,
213+baseDir: undefined,
214+source: "workspace",
215+sourceInfo: undefined,
216+disableModelInvocation: false,
217+} as unknown as ResolvedSkillEntry,
218+],
219+},
220+});
221+222+const skills = metadata.skills as { entries?: Array<{ name?: string }> };
223+expect(skills.entries?.map((e) => e.name)).toEqual(["alpha"]);
224+});
225+226+it("falls back to skills list when every resolvedSkills entry is partial", () => {
227+const metadata = buildTrajectoryRunMetadata({
228+workspaceDir: "/tmp/workspace",
229+sessionFile: "/tmp/workspace/session.jsonl",
230+timeoutMs: 30_000,
231+skillsSnapshot: {
232+prompt: "skill prompt",
233+version: 1,
234+skills: [{ name: "fallback-skill" }],
235+resolvedSkills: [
236+{
237+name: undefined,
238+description: undefined,
239+filePath: undefined,
240+baseDir: undefined,
241+source: "workspace",
242+sourceInfo: undefined,
243+disableModelInvocation: false,
244+} as unknown as ResolvedSkillEntry,
245+],
246+},
247+});
248+249+const skills = metadata.skills as { entries?: Array<{ name?: string }> };
250+expect(skills.entries?.map((e) => e.name)).toEqual(["fallback-skill"]);
251+});
252+253+it("redactPathForSupport returns empty string for null/undefined input", () => {
254+const ctx: SupportRedactionContext = { env: {}, stateDir: "/tmp/.openclaw" };
255+expect(redactPathForSupport(undefined, ctx)).toBe("");
256+expect(redactPathForSupport(null, ctx)).toBe("");
257+});
258+177259it("captures final artifact summaries for export sidecars", () => {
178260const artifacts = buildTrajectoryArtifacts({
179261status: "success",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。