mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-22 03:51:18 +00:00
fix(hooks): respect live skill workshop config
Some checks are pending
CI / preflight (push) Waiting to run
CI / security-scm-fast (push) Waiting to run
CI / security-dependency-audit (push) Waiting to run
CI / security-fast (push) Blocked by required conditions
CI / build-artifacts (push) Blocked by required conditions
CI / (push) Blocked by required conditions
CI / -1 (push) Blocked by required conditions
CI / checks-fast-contracts-channels (push) Blocked by required conditions
CI / checks-fast-protocol (push) Blocked by required conditions
CI / -2 (push) Blocked by required conditions
CI / checks-node-extensions (push) Blocked by required conditions
CI / -3 (push) Blocked by required conditions
CI / checks-node-compat-node22 (push) Blocked by required conditions
CI / -4 (push) Blocked by required conditions
CI / -5 (push) Blocked by required conditions
CI / checks-node-core (push) Blocked by required conditions
CI / extension-fast (push) Blocked by required conditions
CI / check-lint (push) Blocked by required conditions
CI / check-policy-guards (push) Blocked by required conditions
CI / check-preflight-guards (push) Blocked by required conditions
CI / check-prod-types (push) Blocked by required conditions
CI / check-strict-smoke (push) Blocked by required conditions
CI / check-test-types (push) Blocked by required conditions
CI / check (push) Blocked by required conditions
CI / check-additional-boundaries (push) Blocked by required conditions
CI / check-additional-extension-bundled (push) Blocked by required conditions
CI / check-additional-extension-channels (push) Blocked by required conditions
CI / check-additional-extension-package-boundary (push) Blocked by required conditions
CI / check-additional-runtime-topology-architecture (push) Blocked by required conditions
CI / check-additional-runtime-topology-gateway (push) Blocked by required conditions
CI / check-additional (push) Blocked by required conditions
CI / build-smoke (push) Blocked by required conditions
CI / check-docs (push) Blocked by required conditions
CI / skills-python (push) Blocked by required conditions
CI / -6 (push) Blocked by required conditions
CI / -7 (push) Blocked by required conditions
CI / macos-swift (push) Blocked by required conditions
CI / -8 (push) Blocked by required conditions
Docs Sync Publish Repo / sync-publish-repo (push) Waiting to run
Install Smoke / preflight (push) Waiting to run
Install Smoke / install-smoke (push) Blocked by required conditions
Plugin NPM Release / preview_plugins_npm (push) Waiting to run
Plugin NPM Release / preview_plugin_pack (push) Blocked by required conditions
Plugin NPM Release / publish_plugins_npm (push) Blocked by required conditions
Workflow Sanity / no-tabs (push) Waiting to run
Workflow Sanity / actionlint (push) Waiting to run
Workflow Sanity / generated-doc-baselines (push) Waiting to run
Some checks are pending
CI / preflight (push) Waiting to run
CI / security-scm-fast (push) Waiting to run
CI / security-dependency-audit (push) Waiting to run
CI / security-fast (push) Blocked by required conditions
CI / build-artifacts (push) Blocked by required conditions
CI / (push) Blocked by required conditions
CI / -1 (push) Blocked by required conditions
CI / checks-fast-contracts-channels (push) Blocked by required conditions
CI / checks-fast-protocol (push) Blocked by required conditions
CI / -2 (push) Blocked by required conditions
CI / checks-node-extensions (push) Blocked by required conditions
CI / -3 (push) Blocked by required conditions
CI / checks-node-compat-node22 (push) Blocked by required conditions
CI / -4 (push) Blocked by required conditions
CI / -5 (push) Blocked by required conditions
CI / checks-node-core (push) Blocked by required conditions
CI / extension-fast (push) Blocked by required conditions
CI / check-lint (push) Blocked by required conditions
CI / check-policy-guards (push) Blocked by required conditions
CI / check-preflight-guards (push) Blocked by required conditions
CI / check-prod-types (push) Blocked by required conditions
CI / check-strict-smoke (push) Blocked by required conditions
CI / check-test-types (push) Blocked by required conditions
CI / check (push) Blocked by required conditions
CI / check-additional-boundaries (push) Blocked by required conditions
CI / check-additional-extension-bundled (push) Blocked by required conditions
CI / check-additional-extension-channels (push) Blocked by required conditions
CI / check-additional-extension-package-boundary (push) Blocked by required conditions
CI / check-additional-runtime-topology-architecture (push) Blocked by required conditions
CI / check-additional-runtime-topology-gateway (push) Blocked by required conditions
CI / check-additional (push) Blocked by required conditions
CI / build-smoke (push) Blocked by required conditions
CI / check-docs (push) Blocked by required conditions
CI / skills-python (push) Blocked by required conditions
CI / -6 (push) Blocked by required conditions
CI / -7 (push) Blocked by required conditions
CI / macos-swift (push) Blocked by required conditions
CI / -8 (push) Blocked by required conditions
Docs Sync Publish Repo / sync-publish-repo (push) Waiting to run
Install Smoke / preflight (push) Waiting to run
Install Smoke / install-smoke (push) Blocked by required conditions
Plugin NPM Release / preview_plugins_npm (push) Waiting to run
Plugin NPM Release / preview_plugin_pack (push) Blocked by required conditions
Plugin NPM Release / publish_plugins_npm (push) Blocked by required conditions
Workflow Sanity / no-tabs (push) Waiting to run
Workflow Sanity / actionlint (push) Waiting to run
Workflow Sanity / generated-doc-baselines (push) Waiting to run
This commit is contained in:
parent
9ea5484fa1
commit
fbf554397f
2 changed files with 224 additions and 9 deletions
|
|
@ -213,6 +213,117 @@ describe("skill-workshop", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("uses live runtime config for prompt-build guidance enablement", async () => {
|
||||
let configFile: Record<string, unknown> = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
approvalPolicy: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const on = vi.fn();
|
||||
const api = createTestPluginApi({
|
||||
pluginConfig: { approvalPolicy: "auto" },
|
||||
runtime: {
|
||||
config: {
|
||||
loadConfig: () => configFile,
|
||||
},
|
||||
} as never,
|
||||
on,
|
||||
});
|
||||
|
||||
plugin.register(api);
|
||||
|
||||
const hook = on.mock.calls.find((call) => call[0] === "before_prompt_build")?.[1];
|
||||
expect(hook).toBeTypeOf("function");
|
||||
|
||||
configFile = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await expect(hook?.({}, {})).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses live runtime config for tool approval policy", async () => {
|
||||
const workspaceDir = await makeTempDir();
|
||||
const stateDir = await makeTempDir();
|
||||
let configFile: Record<string, unknown> = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
approvalPolicy: "pending",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let tool: AnyAgentTool | undefined;
|
||||
let toolFactory:
|
||||
| ((ctx: { workspaceDir?: string }) => AnyAgentTool | AnyAgentTool[] | null | undefined)
|
||||
| undefined;
|
||||
const api = createTestPluginApi({
|
||||
pluginConfig: { approvalPolicy: "pending" },
|
||||
runtime: {
|
||||
agent: {
|
||||
resolveAgentWorkspaceDir: () => workspaceDir,
|
||||
},
|
||||
state: {
|
||||
resolveStateDir: () => stateDir,
|
||||
},
|
||||
config: {
|
||||
loadConfig: () => configFile,
|
||||
},
|
||||
} as never,
|
||||
registerTool(registered) {
|
||||
toolFactory = typeof registered === "function" ? registered : undefined;
|
||||
const resolved =
|
||||
typeof registered === "function" ? registered({ workspaceDir }) : registered;
|
||||
tool = Array.isArray(resolved) ? resolved[0] : (resolved ?? undefined);
|
||||
},
|
||||
});
|
||||
|
||||
plugin.register(api);
|
||||
|
||||
configFile = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
approvalPolicy: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const refreshedTool = toolFactory?.({ workspaceDir });
|
||||
tool = Array.isArray(refreshedTool) ? refreshedTool[0] : (refreshedTool ?? undefined);
|
||||
|
||||
const result = await tool?.execute?.("call-1", {
|
||||
action: "suggest",
|
||||
skillName: "screenshot-asset-workflow",
|
||||
description: "Screenshot asset workflow",
|
||||
body: "Verify dimensions, optimize the PNG, and run the relevant gate.",
|
||||
});
|
||||
|
||||
expect(result?.details).toMatchObject({ status: "applied" });
|
||||
await expect(
|
||||
fs.access(path.join(workspaceDir, "skills", "screenshot-asset-workflow", "SKILL.md")),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips agent_end hook wiring when auto-capture is disabled", () => {
|
||||
const on = vi.fn();
|
||||
const api = createTestPluginApi({
|
||||
|
|
@ -226,6 +337,77 @@ describe("skill-workshop", () => {
|
|||
expect(on).not.toHaveBeenCalledWith("agent_end", expect.any(Function));
|
||||
});
|
||||
|
||||
it("uses live runtime config to skip capture when review mode turns off", async () => {
|
||||
const workspaceDir = await makeTempDir();
|
||||
const stateDir = await makeTempDir();
|
||||
let configFile: Record<string, unknown> = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
approvalPolicy: "auto",
|
||||
reviewMode: "hybrid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
||||
const on = vi.fn();
|
||||
const api = createTestPluginApi({
|
||||
pluginConfig: { approvalPolicy: "auto", reviewMode: "hybrid" },
|
||||
logger,
|
||||
runtime: {
|
||||
agent: {
|
||||
resolveAgentWorkspaceDir: () => workspaceDir,
|
||||
},
|
||||
state: {
|
||||
resolveStateDir: () => stateDir,
|
||||
},
|
||||
config: {
|
||||
loadConfig: () => configFile,
|
||||
},
|
||||
} as never,
|
||||
on,
|
||||
});
|
||||
|
||||
plugin.register(api);
|
||||
|
||||
configFile = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"skill-workshop": {
|
||||
config: {
|
||||
approvalPolicy: "auto",
|
||||
reviewMode: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const handler = on.mock.calls.find((call) => call[0] === "agent_end")?.[1];
|
||||
expect(handler).toBeTypeOf("function");
|
||||
await handler?.(
|
||||
{
|
||||
success: true,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"From now on when asked for animated GIFs, verify the file is actually animated.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ workspaceDir },
|
||||
);
|
||||
|
||||
await expect(
|
||||
fs.access(path.join(workspaceDir, "skills", "animated-gif-workflow", "SKILL.md")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
expect(logger.info).not.toHaveBeenCalledWith("skill-workshop: applied animated-gif-workflow");
|
||||
});
|
||||
|
||||
it("skips agent_end hook wiring when review mode is off", () => {
|
||||
const on = vi.fn();
|
||||
const api = createTestPluginApi({
|
||||
|
|
|
|||
|
|
@ -6,27 +6,60 @@ import { createProposalFromMessages } from "./src/signals.js";
|
|||
import { createSkillWorkshopTool } from "./src/tool.js";
|
||||
import { applyOrStoreProposal, createStoreForContext } from "./src/workshop.js";
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "skill-workshop",
|
||||
name: "Skill Workshop",
|
||||
description:
|
||||
"Captures repeatable workflows as workspace skills, with pending review and safe writes.",
|
||||
register(api) {
|
||||
const config = resolveConfig(api.pluginConfig);
|
||||
if (!config.enabled) {
|
||||
const startupConfig = resolveConfig(api.pluginConfig);
|
||||
if (!startupConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
const resolveCurrentConfig = () => {
|
||||
const runtimeConfig = api.runtime.config?.loadConfig?.();
|
||||
const runtimePlugins = asRecord(asRecord(runtimeConfig)?.plugins);
|
||||
const runtimeEntries = asRecord(runtimePlugins?.entries);
|
||||
const runtimePluginConfig =
|
||||
asRecord(runtimeEntries?.["skill-workshop"])?.config ?? api.pluginConfig;
|
||||
return resolveConfig(runtimePluginConfig);
|
||||
};
|
||||
|
||||
api.registerTool((ctx) => createSkillWorkshopTool({ api, config, ctx }), {
|
||||
name: "skill_workshop",
|
||||
api.registerTool(
|
||||
(ctx) => {
|
||||
const config = resolveCurrentConfig();
|
||||
if (!config.enabled) {
|
||||
return null;
|
||||
}
|
||||
return createSkillWorkshopTool({ api, config, ctx });
|
||||
},
|
||||
{
|
||||
name: "skill_workshop",
|
||||
},
|
||||
);
|
||||
|
||||
api.on("before_prompt_build", async () => {
|
||||
const config = resolveCurrentConfig();
|
||||
if (!config.enabled) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
prependSystemContext: buildWorkshopGuidance(config),
|
||||
};
|
||||
});
|
||||
|
||||
api.on("before_prompt_build", async () => ({
|
||||
prependSystemContext: buildWorkshopGuidance(config),
|
||||
}));
|
||||
|
||||
if (config.autoCapture && config.reviewMode !== "off") {
|
||||
if (startupConfig.autoCapture && startupConfig.reviewMode !== "off") {
|
||||
api.on("agent_end", async (event, ctx) => {
|
||||
const config = resolveCurrentConfig();
|
||||
if (!config.enabled || !config.autoCapture || config.reviewMode === "off") {
|
||||
return;
|
||||
}
|
||||
if (!event.success) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue