refactor: rename skill workshop agent tool

This commit is contained in:
Shakker
2026-05-30 15:41:22 +01:00
committed by Shakker
parent c09e1efe99
commit 2383cfd303
22 changed files with 87 additions and 87 deletions

View File

@@ -185,9 +185,9 @@ scans them, verifies their hashes before apply, and writes them beside the
active `SKILL.md` only after the proposal is applied.
Agents can create, revise, list, and inspect pending proposals through the
`skill_research` tool when they identify reusable work. If the user explicitly
`skill_workshop` tool when they identify reusable work. If the user explicitly
asks to approve/use/apply, reject, or quarantine a specific proposal,
`skill_research` can also perform that proposal lifecycle action through the
`skill_workshop` can also perform that proposal lifecycle action through the
same Skill Workshop safeguards.
## Related

View File

@@ -96,8 +96,8 @@ For how skills are loaded and prioritized, see [Skills](/tools/skills).
## Propose before applying
For agent-generated or research-derived procedures, use a Skill Workshop
proposal instead of writing `SKILL.md` directly:
For agent-generated procedures, use a Skill Workshop proposal instead of
writing `SKILL.md` directly:
```bash
openclaw skills workshop propose-create \

View File

@@ -169,7 +169,7 @@ openclaw skills workshop reject <proposal-id>
openclaw skills workshop quarantine <proposal-id>
```
Agents can draft proposals through the `skill_research` tool when they identify
Agents can draft proposals through the `skill_workshop` tool when they identify
work worth reusing and can revise pending proposals during review. When the
user explicitly asks to approve/use/apply, reject, or quarantine a specific
proposal, the tool can perform that lifecycle action through Skill Workshop

View File

@@ -74,7 +74,7 @@ describe("SkillsProposalInspectResultSchema", () => {
schema: "openclaw.skill-workshop.proposal.v1",
createdAt: "2026-05-30T00:00:00.000Z",
updatedAt: "2026-05-30T00:00:00.000Z",
createdBy: "skill-research",
createdBy: "skill-workshop",
proposedVersion: "v1",
draftFile: "PROPOSAL.md",
target: {

View File

@@ -496,7 +496,6 @@ const SkillProposalScanStateSchema = Type.Union([
Type.Literal("quarantined"),
]);
const SkillProposalSourceSchema = Type.Union([
Type.Literal("skill-research"),
Type.Literal("skill-workshop"),
Type.Literal("cli"),
Type.Literal("gateway"),

View File

@@ -313,15 +313,15 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
expect(runBeforeToolCallMock).not.toHaveBeenCalled();
});
it("requires approval before skill_research applies a proposal", async () => {
it("requires approval before skill_workshop applies a proposal", async () => {
(hookRunner.hasHooks as ReturnType<typeof vi.fn>).mockReturnValue(false);
mockCallGatewayTool.mockResolvedValueOnce({
id: "skill-research-approval",
id: "skill-workshop-approval",
decision: PluginApprovalResolutions.ALLOW_ONCE,
});
const result = await runBeforeToolCallHook({
toolName: "skill_research",
toolName: "skill_workshop",
params: { action: "apply", proposal_id: "weather-20260530-a1b2c3d4e5" },
toolCallId: "call-skill-apply",
ctx: {
@@ -342,7 +342,7 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
params: { action: "apply", proposal_id: "weather-20260530-a1b2c3d4e5" },
approvalResolution: PluginApprovalResolutions.ALLOW_ONCE,
});
const approvalCall = requireApprovalRequestCall("skill_research approval request");
const approvalCall = requireApprovalRequestCall("skill_workshop approval request");
expect(approvalCall.request.pluginId).toBeUndefined();
expect(approvalCall.request.title).toBe("Apply workspace skill proposal");
expect(approvalCall.request.description).toBe(
@@ -350,16 +350,16 @@ describe("runBeforeToolCallHook — embedded mode approvals", () => {
);
expect(approvalCall.request.severity).toBe("warning");
expect(approvalCall.request.allowedDecisions).toEqual(["allow-once", "deny"]);
expect(approvalCall.request.toolName).toBe("skill_research");
expect(approvalCall.request.toolName).toBe("skill_workshop");
expect(approvalCall.request.toolCallId).toBe("call-skill-apply");
expect(runBeforeToolCallMock).not.toHaveBeenCalled();
});
it("does not require skill_research lifecycle approval in auto mode", async () => {
it("does not require skill_workshop lifecycle approval in auto mode", async () => {
(hookRunner.hasHooks as ReturnType<typeof vi.fn>).mockReturnValue(false);
const result = await runBeforeToolCallHook({
toolName: "skill_research",
toolName: "skill_workshop",
params: { action: "reject", proposal_id: "weather-20260530-a1b2c3d4e5" },
ctx: {
config: {

View File

@@ -41,7 +41,7 @@ import {
resolveSkillTelemetrySourceValue,
} from "../skills/loading/source.js";
import type { SkillSnapshot, SkillTelemetrySource } from "../skills/types.js";
import { resolveSkillResearchToolApproval } from "../skills/workshop/policy.js";
import { resolveSkillWorkshopToolApproval } from "../skills/workshop/policy.js";
import { isPlainObject } from "../utils.js";
import { adjustedParamsByToolCallId } from "./agent-tools.before-tool-call.state.js";
import { copyChannelAgentToolMeta, getChannelAgentToolMeta } from "./channel-tools.js";
@@ -839,7 +839,7 @@ export async function runBeforeToolCallHook(args: {
const hasBeforeToolCallHooks = hookRunner?.hasHooks("before_tool_call") === true;
const shouldRunTrustedPolicies = hasTrustedToolPolicies();
const normalizedParams = isPlainObject(params) ? params : {};
const corePolicyResult = resolveSkillResearchToolApproval({
const corePolicyResult = resolveSkillWorkshopToolApproval({
toolName,
toolParams: normalizedParams,
...(args.ctx?.config ? { config: args.ctx.config } : {}),

View File

@@ -1050,12 +1050,12 @@ describe("createOpenClawCodingTools", () => {
expect(toolNameList(tools)).toContain("heartbeat_respond");
});
it("keeps skill_research available under the coding profile", () => {
it("keeps skill_workshop available under the coding profile", () => {
const tools = createOpenClawCodingTools({
config: { tools: { profile: "coding" } },
});
expect(toolNameList(tools)).toContain("skill_research");
expect(toolNameList(tools)).toContain("skill_workshop");
});
it("can keep message available when a cron route needs it under a provider coding profile", () => {

View File

@@ -302,11 +302,11 @@ describe("resolveEmbeddedAttemptToolConstructionPlan", () => {
},
);
expectConstructionPlan(
resolveEmbeddedAttemptToolConstructionPlan({ toolsAllow: ["skill_research"] }),
resolveEmbeddedAttemptToolConstructionPlan({ toolsAllow: ["skill_workshop"] }),
{
constructTools: true,
includeCoreTools: true,
runtimeToolAllowlist: ["skill_research"],
runtimeToolAllowlist: ["skill_workshop"],
coding: {
includeBaseCodingTools: false,
includeShellTools: false,

View File

@@ -36,7 +36,7 @@ const OPENCLAW_TOOL_FACTORY_NAMES = new Set([
"sessions_send",
"sessions_spawn",
"sessions_yield",
"skill_research",
"skill_workshop",
"create_goal",
"subagents",
"tts",

View File

@@ -56,7 +56,7 @@ import { createSessionsListTool } from "./tools/sessions-list-tool.js";
import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js";
import { createSessionsYieldTool } from "./tools/sessions-yield-tool.js";
import { createSkillResearchTool } from "./tools/skill-research-tool.js";
import { createSkillWorkshopTool } from "./tools/skill-workshop-tool.js";
import { createSubagentsTool } from "./tools/subagents-tool.js";
import { createTranscriptsTool } from "./tools/transcripts-tool.js";
import { createTtsTool } from "./tools/tts-tool.js";
@@ -445,7 +445,7 @@ export function createOpenClawTools(
...(options?.sandboxed
? []
: [
createSkillResearchTool({
createSkillWorkshopTool({
workspaceDir,
config: resolvedConfig,
agentId: sessionAgentId,

View File

@@ -683,24 +683,24 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain("If several apply, choose the most specific.");
});
it("instructs models to use skill_research only when the tool is available", () => {
it("instructs models to use skill_workshop only when the tool is available", () => {
const withoutTool = buildAgentSystemPrompt({
workspaceDir: "/tmp/openclaw",
toolNames: ["read"],
});
expect(withoutTool).not.toContain("## Skill Research");
expect(withoutTool).not.toContain("use `skill_research`");
expect(withoutTool).not.toContain("## Skill Workshop");
expect(withoutTool).not.toContain("use `skill_workshop`");
const withTool = buildAgentSystemPrompt({
workspaceDir: "/tmp/openclaw",
toolNames: ["read", "skill_research"],
toolNames: ["read", "skill_workshop"],
});
expect(withTool).toContain(
"- skill_research: Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals",
"- skill_workshop: Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals",
);
expect(withTool).toContain("## Skill Research");
expect(withTool).toContain("## Skill Workshop");
expect(withTool).toContain(
"Use `skill_research` when the user wants to capture, create, draft, formalize, improve, update, or revise a reusable skill, playbook, workflow, procedure, or durable instruction.",
"Use `skill_workshop` when the user wants to create, update, revise, list, inspect, apply, reject, or quarantine a reusable skill, Skill Workshop proposal, playbook, workflow, procedure, or durable instruction.",
);
expect(withTool).toContain(
"Treat a request as durable when it should be saved, repeated, proposed, installed later, shared as a skill, or used as a standing workflow instead of answered once in chat.",
@@ -716,11 +716,12 @@ describe("buildAgentSystemPrompt", () => {
expect(withTool).toContain(
"Use `action=apply`, `action=reject`, or `action=quarantine` only after the user explicitly asks to approve/use/apply, reject, or quarantine a specific proposal.",
);
expect(withTool).toContain("Generated skills are pending proposals by default.");
expect(withTool).toContain(
"Do not apply, reject, or quarantine proposals manually with filesystem operations or shell commands.",
);
expect(withTool).toContain(
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_research`.",
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_workshop`.",
);
});

View File

@@ -750,7 +750,7 @@ export function buildAgentSystemPrompt(params: {
"On-demand list/status visibility for sub-agent runs in this requester session; do not use for wait loops",
session_status:
"Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (📊 session_status); optional per-session model override",
skill_research:
skill_workshop:
"Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals",
image: "Analyze an image with the configured image model",
image_generate: "Generate images with the configured image-generation model",
@@ -782,7 +782,7 @@ export function buildAgentSystemPrompt(params: {
"sessions_yield",
"subagents",
"session_status",
"skill_research",
"skill_workshop",
"image",
"image_generate",
];
@@ -923,20 +923,20 @@ export function buildAgentSystemPrompt(params: {
skillsPrompt,
readToolName,
});
const skillResearchSection = availableTools.has("skill_research")
const skillWorkshopSection = availableTools.has("skill_workshop")
? [
"## Skill Research",
"Use `skill_research` when the user wants to capture, create, draft, formalize, improve, update, or revise a reusable skill, playbook, workflow, procedure, or durable instruction.",
"## Skill Workshop",
"Use `skill_workshop` when the user wants to create, update, revise, list, inspect, apply, reject, or quarantine a reusable skill, Skill Workshop proposal, playbook, workflow, procedure, or durable instruction.",
"Treat a request as durable when it should be saved, repeated, proposed, installed later, shared as a skill, or used as a standing workflow instead of answered once in chat.",
"Do not create or change skill proposal files manually with `write`, `edit`, `exec`, shell commands, or direct filesystem operations. The final proposal artifact must go through `skill_research`.",
"Do not create or change skill proposal files manually with `write`, `edit`, `exec`, shell commands, or direct filesystem operations. The final proposal artifact must go through `skill_workshop`.",
"Use `action=create` for a new skill, `action=update` for an existing approved/live skill, and `action=revise` for an existing pending proposal.",
"For `action=revise`, pass `proposal_id` when known. If it is not known, pass the proposal or skill name in `name` so `skill_research` can resolve the pending proposal or return candidates.",
"For `action=revise`, pass `proposal_id` when known. If it is not known, pass the proposal or skill name in `name` so `skill_workshop` can resolve the pending proposal or return candidates.",
"Use `action=list` or `action=inspect` only when you need to find or read pending proposals before revising. Do not use filesystem search for proposal discovery.",
"If the user names an existing skill, proposal, or workflow, inspect it first when available.",
"Generated or researched skills are pending proposals by default. Do not apply, install, approve, enable, or write into live skills unless the user explicitly asks for that separate action.",
"Generated skills are pending proposals by default. Do not apply, install, approve, enable, or write into live skills unless the user explicitly asks for that separate action.",
"Use `action=apply`, `action=reject`, or `action=quarantine` only after the user explicitly asks to approve/use/apply, reject, or quarantine a specific proposal. Pass `proposal_id`; if it is not known, use `action=list` or `action=inspect` first.",
"Do not apply, reject, or quarantine proposals manually with filesystem operations or shell commands. Proposal lifecycle changes must use `skill_research`.",
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_research`.",
"Do not apply, reject, or quarantine proposals manually with filesystem operations or shell commands. Proposal lifecycle changes must use `skill_workshop`.",
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_workshop`.",
"",
]
: [];
@@ -1102,7 +1102,7 @@ export function buildAgentSystemPrompt(params: {
"`restart`, not stop+start.",
"",
...skillsSection,
...skillResearchSection,
...skillWorkshopSection,
...memorySection,
hasGateway && !isMinimal ? "## OpenClaw Self-Update" : "",
hasGateway && !isMinimal

View File

@@ -38,7 +38,7 @@ const coreTools = [
stubActionTool("sessions_spawn", ["spawn", "handoff"]),
stubActionTool("subagents", ["list", "show"]),
stubActionTool("session_status", ["get", "show"]),
stubTool("skill_research"),
stubTool("skill_workshop"),
stubActionTool("browser", ["status", "snapshot"]),
stubTool("tts"),
stubTool("image_generate"),

View File

@@ -45,7 +45,7 @@ describe("tool-catalog", () => {
"create_goal",
"update_goal",
"update_plan",
"skill_research",
"skill_workshop",
"image",
"image_generate",
"music_generate",

View File

@@ -293,8 +293,8 @@ const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
includeInOpenClawGroup: true,
},
{
id: "skill_research",
label: "skill_research",
id: "skill_workshop",
label: "skill_workshop",
description:
"Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals",
sectionId: "agents",

View File

@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { captureEnv } from "../../test-utils/env.js";
import { createTrackedTempDirs } from "../../test-utils/tracked-temp-dirs.js";
import { createOpenClawTools } from "../openclaw-tools.js";
import { createSkillResearchTool } from "./skill-research-tool.js";
import { createSkillWorkshopTool } from "./skill-workshop-tool.js";
const tempDirs = createTrackedTempDirs();
let envSnapshot: ReturnType<typeof captureEnv>;
@@ -12,7 +12,7 @@ let stateDir = "";
beforeEach(async () => {
envSnapshot = captureEnv(["OPENCLAW_STATE_DIR"]);
stateDir = await tempDirs.make("openclaw-skill-research-state-");
stateDir = await tempDirs.make("openclaw-skill-workshop-state-");
process.env.OPENCLAW_STATE_DIR = stateDir;
});
@@ -21,19 +21,19 @@ afterEach(async () => {
await tempDirs.cleanup();
});
describe("skill_research tool", () => {
describe("skill_workshop tool", () => {
it("is exposed in the OpenClaw tool set", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-research-tool-");
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tools = createOpenClawTools({
workspaceDir,
config: {},
disablePluginTools: true,
});
expect(tools.some((tool) => tool.name === "skill_research")).toBe(true);
expect(tools.some((tool) => tool.name === "skill_workshop")).toBe(true);
});
it("stays exposed when autonomous proposal capture is disabled", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-research-tool-");
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tools = createOpenClawTools({
workspaceDir,
config: {
@@ -47,11 +47,11 @@ describe("skill_research tool", () => {
},
disablePluginTools: true,
});
expect(tools.some((tool) => tool.name === "skill_research")).toBe(true);
expect(tools.some((tool) => tool.name === "skill_workshop")).toBe(true);
});
it("is not exposed from sandboxed OpenClaw tool sets", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-research-tool-");
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tools = createOpenClawTools({
workspaceDir,
config: {},
@@ -59,12 +59,12 @@ describe("skill_research tool", () => {
sandboxed: true,
});
expect(tools.some((tool) => tool.name === "skill_research")).toBe(false);
expect(tools.some((tool) => tool.name === "skill_workshop")).toBe(false);
});
it("creates pending skill proposals without applying them", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-research-tool-");
const tool = createSkillResearchTool({ workspaceDir, config: {}, agentId: "main" });
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tool = createSkillWorkshopTool({ workspaceDir, config: {}, agentId: "main" });
const result = await tool.execute("call-1", {
action: "create",
@@ -206,8 +206,8 @@ describe("skill_research tool", () => {
});
it("applies, rejects, and quarantines proposals through the workshop service", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-research-tool-");
const tool = createSkillResearchTool({ workspaceDir, config: {}, agentId: "main" });
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tool = createSkillWorkshopTool({ workspaceDir, config: {}, agentId: "main" });
const created = await tool.execute("call-1", {
action: "create",
@@ -307,14 +307,14 @@ describe("skill_research tool", () => {
});
it("scopes proposal discovery to the tool workspace", async () => {
const firstWorkspaceDir = await tempDirs.make("openclaw-skill-research-tool-first-");
const secondWorkspaceDir = await tempDirs.make("openclaw-skill-research-tool-second-");
const firstTool = createSkillResearchTool({
const firstWorkspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-first-");
const secondWorkspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-second-");
const firstTool = createSkillWorkshopTool({
workspaceDir: firstWorkspaceDir,
config: {},
agentId: "main",
});
const secondTool = createSkillResearchTool({
const secondTool = createSkillWorkshopTool({
workspaceDir: secondWorkspaceDir,
config: {},
agentId: "main",

View File

@@ -27,7 +27,7 @@ import {
type AnyAgentTool,
} from "./common.js";
const SKILL_RESEARCH_ACTIONS = [
const SKILL_WORKSHOP_ACTIONS = [
"create",
"update",
"revise",
@@ -45,9 +45,9 @@ const SKILL_PROPOSAL_STATUSES = [
"stale",
] as const satisfies readonly SkillProposalStatus[];
const SkillResearchToolSchema = Type.Object(
const SkillWorkshopToolSchema = Type.Object(
{
action: stringEnum(SKILL_RESEARCH_ACTIONS, {
action: stringEnum(SKILL_WORKSHOP_ACTIONS, {
description:
"create for a new skill proposal, update for an existing skill, revise for a pending proposal, list or inspect proposals for proposal discovery, apply/reject/quarantine for explicit proposal lifecycle actions.",
}),
@@ -103,7 +103,7 @@ const SkillResearchToolSchema = Type.Object(
{ description: "Optional support files to store with the proposal." },
),
),
goal: Type.Optional(Type.String({ description: "Research or improvement goal." })),
goal: Type.Optional(Type.String({ description: "Proposal or improvement goal." })),
evidence: Type.Optional(Type.String({ description: "Short evidence or notes." })),
reason: Type.Optional(
Type.String({
@@ -114,20 +114,20 @@ const SkillResearchToolSchema = Type.Object(
{ additionalProperties: false },
);
export type SkillResearchToolOptions = {
export type SkillWorkshopToolOptions = {
workspaceDir: string;
config?: OpenClawConfig;
agentId?: string;
};
export function createSkillResearchTool(options: SkillResearchToolOptions): AnyAgentTool {
export function createSkillWorkshopTool(options: SkillWorkshopToolOptions): AnyAgentTool {
return {
label: "Skill Research",
name: "skill_research",
label: "Skill Workshop",
name: "skill_workshop",
displaySummary: "Propose a reusable skill",
description:
"Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals when reusable procedures should be captured, improved, or explicitly approved.",
parameters: SkillResearchToolSchema,
parameters: SkillWorkshopToolSchema,
execute: async (_toolCallId, args) => {
const params = asToolParamsRecord(args);
const action = readStringParam(params, "action", { required: true });
@@ -205,7 +205,7 @@ export function createSkillResearchTool(options: SkillResearchToolOptions): AnyA
description: readStringParam(params, "description", { required: true }),
content: proposalContent,
supportFiles,
createdBy: "skill-research",
createdBy: "skill-workshop",
goal,
evidence,
});
@@ -220,7 +220,7 @@ export function createSkillResearchTool(options: SkillResearchToolOptions): AnyA
}),
content: proposalContent,
supportFiles,
createdBy: "skill-research",
createdBy: "skill-workshop",
goal,
evidence,
});
@@ -242,7 +242,7 @@ export function createSkillResearchTool(options: SkillResearchToolOptions): AnyA
evidence,
});
} else {
throw new ToolInputError(`action must be one of ${SKILL_RESEARCH_ACTIONS.join(", ")}`);
throw new ToolInputError(`action must be one of ${SKILL_WORKSHOP_ACTIONS.join(", ")}`);
}
return proposalResult(proposal);

View File

@@ -552,7 +552,7 @@ export function registerSkillsCli(program: Command) {
"--proposal-dir <path>",
"Path to proposal directory with PROPOSAL.md and UTF-8 text support files",
)
.option("--goal <text>", "Research or improvement goal")
.option("--goal <text>", "Proposal or improvement goal")
.option("--evidence <text>", "Evidence or notes for the proposal")
.option("--json", "Output as JSON", false)
.action(
@@ -603,7 +603,7 @@ export function registerSkillsCli(program: Command) {
"--proposal-dir <path>",
"Path to proposal directory with PROPOSAL.md and UTF-8 text support files",
)
.option("--goal <text>", "Research or improvement goal")
.option("--goal <text>", "Proposal or improvement goal")
.option("--evidence <text>", "Evidence or notes for the proposal")
.option("--json", "Output as JSON", false)
.action(

View File

@@ -3,19 +3,19 @@ import type { PluginHookBeforeToolCallResult } from "../../plugins/types.js";
import { asNullableRecord } from "../../shared/record-coerce.js";
import { resolveSkillWorkshopConfig } from "./config.js";
const SKILL_RESEARCH_LIFECYCLE_ACTIONS = new Set(["apply", "reject", "quarantine"]);
const SKILL_WORKSHOP_LIFECYCLE_ACTIONS = new Set(["apply", "reject", "quarantine"]);
type SkillResearchLifecycleAction = "apply" | "reject" | "quarantine";
type SkillWorkshopLifecycleAction = "apply" | "reject" | "quarantine";
function readLifecycleAction(params: unknown): SkillResearchLifecycleAction | undefined {
function readLifecycleAction(params: unknown): SkillWorkshopLifecycleAction | undefined {
const action = asNullableRecord(params)?.action;
if (typeof action !== "string" || !SKILL_RESEARCH_LIFECYCLE_ACTIONS.has(action)) {
if (typeof action !== "string" || !SKILL_WORKSHOP_LIFECYCLE_ACTIONS.has(action)) {
return undefined;
}
return action as SkillResearchLifecycleAction;
return action as SkillWorkshopLifecycleAction;
}
function lifecycleApprovalText(action: SkillResearchLifecycleAction): {
function lifecycleApprovalText(action: SkillWorkshopLifecycleAction): {
title: string;
description: string;
severity: "info" | "warning";
@@ -41,12 +41,12 @@ function lifecycleApprovalText(action: SkillResearchLifecycleAction): {
};
}
export function resolveSkillResearchToolApproval(params: {
export function resolveSkillWorkshopToolApproval(params: {
toolName: string;
toolParams: unknown;
config?: OpenClawConfig;
}): PluginHookBeforeToolCallResult | undefined {
if (params.toolName !== "skill_research") {
if (params.toolName !== "skill_workshop") {
return undefined;
}
const action = readLifecycleAction(params.toolParams);

View File

@@ -60,7 +60,7 @@ describe("skill workshop proposals", () => {
content: "export function parseWeather(value) { return value; }\n",
},
],
createdBy: "skill-research",
createdBy: "skill-workshop",
goal: "Reuse weather lookup steps",
});

View File

@@ -8,7 +8,7 @@ export const SKILL_WORKSHOP_ROLLBACK_SCHEMA = "openclaw.skill-workshop.rollback.
export type SkillProposalKind = "create" | "update";
export type SkillProposalStatus = "pending" | "applied" | "rejected" | "quarantined" | "stale";
export type SkillProposalScannerState = "pending" | "clean" | "failed" | "quarantined";
export type SkillProposalSource = "skill-research" | "skill-workshop" | "cli" | "gateway";
export type SkillProposalSource = "skill-workshop" | "cli" | "gateway";
export type SkillProposalScan = {
state: SkillProposalScannerState;