mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 02:50:23 +00:00
fix(agents): prefer runtime snapshot for skill secrets
This commit is contained in:
@@ -1,28 +1,21 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
clearRuntimeConfigSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "../../config/config.js";
|
||||
import * as skillsModule from "../skills.js";
|
||||
import type { SkillSnapshot } from "../skills.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
loadWorkspaceSkillEntries: vi.fn(
|
||||
(_workspaceDir: string, _options?: { config?: OpenClawConfig }) => [],
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("../skills.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../skills.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadWorkspaceSkillEntries: (workspaceDir: string, options?: { config?: OpenClawConfig }) =>
|
||||
hoisted.loadWorkspaceSkillEntries(workspaceDir, options),
|
||||
};
|
||||
});
|
||||
|
||||
const { resolveEmbeddedRunSkillEntries } = await import("./skills-runtime.js");
|
||||
|
||||
describe("resolveEmbeddedRunSkillEntries", () => {
|
||||
const loadWorkspaceSkillEntriesSpy = vi.spyOn(skillsModule, "loadWorkspaceSkillEntries");
|
||||
|
||||
beforeEach(() => {
|
||||
hoisted.loadWorkspaceSkillEntries.mockReset();
|
||||
hoisted.loadWorkspaceSkillEntries.mockReturnValue([]);
|
||||
clearRuntimeConfigSnapshot();
|
||||
loadWorkspaceSkillEntriesSpy.mockReset();
|
||||
loadWorkspaceSkillEntriesSpy.mockReturnValue([]);
|
||||
});
|
||||
|
||||
it("loads skill entries with config when no resolved snapshot skills exist", () => {
|
||||
@@ -44,8 +37,47 @@ describe("resolveEmbeddedRunSkillEntries", () => {
|
||||
});
|
||||
|
||||
expect(result.shouldLoadSkillEntries).toBe(true);
|
||||
expect(hoisted.loadWorkspaceSkillEntries).toHaveBeenCalledTimes(1);
|
||||
expect(hoisted.loadWorkspaceSkillEntries).toHaveBeenCalledWith("/tmp/workspace", { config });
|
||||
expect(loadWorkspaceSkillEntriesSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadWorkspaceSkillEntriesSpy).toHaveBeenCalledWith("/tmp/workspace", { config });
|
||||
});
|
||||
|
||||
it("prefers the active runtime snapshot when caller config still contains SecretRefs", () => {
|
||||
const sourceConfig: OpenClawConfig = {
|
||||
skills: {
|
||||
entries: {
|
||||
diffs: {
|
||||
apiKey: {
|
||||
source: "file",
|
||||
provider: "default",
|
||||
id: "/skills/entries/diffs/apiKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const runtimeConfig: OpenClawConfig = {
|
||||
skills: {
|
||||
entries: {
|
||||
diffs: {
|
||||
apiKey: "resolved-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
|
||||
resolveEmbeddedRunSkillEntries({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
config: sourceConfig,
|
||||
skillsSnapshot: {
|
||||
prompt: "skills prompt",
|
||||
skills: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(loadWorkspaceSkillEntriesSpy).toHaveBeenCalledWith("/tmp/workspace", {
|
||||
config: runtimeConfig,
|
||||
});
|
||||
});
|
||||
|
||||
it("skips skill entry loading when resolved snapshot skills are present", () => {
|
||||
@@ -65,6 +97,6 @@ describe("resolveEmbeddedRunSkillEntries", () => {
|
||||
shouldLoadSkillEntries: false,
|
||||
skillEntries: [],
|
||||
});
|
||||
expect(hoisted.loadWorkspaceSkillEntries).not.toHaveBeenCalled();
|
||||
expect(loadWorkspaceSkillEntriesSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { loadWorkspaceSkillEntries, type SkillEntry, type SkillSnapshot } from "../skills.js";
|
||||
import { resolveSkillRuntimeConfig } from "../skills/runtime-config.js";
|
||||
|
||||
export function resolveEmbeddedRunSkillEntries(params: {
|
||||
workspaceDir: string;
|
||||
@@ -10,10 +11,11 @@ export function resolveEmbeddedRunSkillEntries(params: {
|
||||
skillEntries: SkillEntry[];
|
||||
} {
|
||||
const shouldLoadSkillEntries = !params.skillsSnapshot || !params.skillsSnapshot.resolvedSkills;
|
||||
const config = resolveSkillRuntimeConfig(params.config);
|
||||
return {
|
||||
shouldLoadSkillEntries,
|
||||
skillEntries: shouldLoadSkillEntries
|
||||
? loadWorkspaceSkillEntries(params.workspaceDir, { config: params.config })
|
||||
? loadWorkspaceSkillEntries(params.workspaceDir, { config })
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearRuntimeConfigSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
|
||||
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
|
||||
import { writeSkill } from "./skills.e2e-test-helpers.js";
|
||||
@@ -75,6 +80,10 @@ afterAll(async () => {
|
||||
await fixtureSuite.cleanup();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
});
|
||||
|
||||
describe("buildWorkspaceSkillCommandSpecs", () => {
|
||||
it("sanitizes and de-duplicates command names", async () => {
|
||||
const workspaceDir = await makeWorkspace();
|
||||
@@ -370,6 +379,50 @@ describe("applySkillEnvOverrides", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the active runtime snapshot over raw SecretRef skill config", async () => {
|
||||
const workspaceDir = await makeWorkspace();
|
||||
await writeEnvSkill(workspaceDir);
|
||||
|
||||
const entries = loadWorkspaceSkillEntries(workspaceDir, resolveTestSkillDirs(workspaceDir));
|
||||
const sourceConfig: OpenClawConfig = {
|
||||
skills: {
|
||||
entries: {
|
||||
"env-skill": {
|
||||
apiKey: {
|
||||
source: "file",
|
||||
provider: "default",
|
||||
id: "/skills/entries/env-skill/apiKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const runtimeConfig: OpenClawConfig = {
|
||||
skills: {
|
||||
entries: {
|
||||
"env-skill": {
|
||||
apiKey: "resolved-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
|
||||
withClearedEnv(["ENV_KEY"], () => {
|
||||
const restore = applySkillEnvOverrides({
|
||||
skills: entries,
|
||||
config: sourceConfig,
|
||||
});
|
||||
|
||||
try {
|
||||
expect(process.env.ENV_KEY).toBe("resolved-key");
|
||||
} finally {
|
||||
restore();
|
||||
expect(process.env.ENV_KEY).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks unsafe env overrides but allows declared secrets", async () => {
|
||||
const workspaceDir = await makeWorkspace();
|
||||
const skillDir = path.join(workspaceDir, "skills", "unsafe-env-skill");
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { sanitizeEnvVars, validateEnvVarValue } from "../sandbox/sanitize-env-vars.js";
|
||||
import { resolveSkillConfig } from "./config.js";
|
||||
import { resolveSkillKey } from "./frontmatter.js";
|
||||
import { resolveSkillRuntimeConfig } from "./runtime-config.js";
|
||||
import type { SkillEntry, SkillSnapshot } from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("env-overrides");
|
||||
@@ -211,7 +212,8 @@ function createEnvReverter(updates: EnvUpdate[]) {
|
||||
}
|
||||
|
||||
export function applySkillEnvOverrides(params: { skills: SkillEntry[]; config?: OpenClawConfig }) {
|
||||
const { skills, config } = params;
|
||||
const { skills } = params;
|
||||
const config = resolveSkillRuntimeConfig(params.config);
|
||||
const updates: EnvUpdate[] = [];
|
||||
|
||||
for (const entry of skills) {
|
||||
@@ -237,7 +239,8 @@ export function applySkillEnvOverridesFromSnapshot(params: {
|
||||
snapshot?: SkillSnapshot;
|
||||
config?: OpenClawConfig;
|
||||
}) {
|
||||
const { snapshot, config } = params;
|
||||
const { snapshot } = params;
|
||||
const config = resolveSkillRuntimeConfig(params.config);
|
||||
if (!snapshot) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
5
src/agents/skills/runtime-config.ts
Normal file
5
src/agents/skills/runtime-config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { getRuntimeConfigSnapshot, type OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
export function resolveSkillRuntimeConfig(config?: OpenClawConfig): OpenClawConfig | undefined {
|
||||
return getRuntimeConfigSnapshot() ?? config;
|
||||
}
|
||||
Reference in New Issue
Block a user