From 2383cfd3039db754dea8e5baa933e405c12fb73c Mon Sep 17 00:00:00 2001 From: Shakker Date: Sat, 30 May 2026 15:41:22 +0100 Subject: [PATCH] refactor: rename skill workshop agent tool --- docs/cli/skills.md | 4 +-- docs/tools/creating-skills.md | 4 +-- docs/tools/skills.md | 2 +- .../src/schema/agents-models-skills.test.ts | 2 +- .../src/schema/agents-models-skills.ts | 1 - ...ols.before-tool-call.embedded-mode.test.ts | 14 ++++---- src/agents/agent-tools.before-tool-call.ts | 4 +-- ...tools.create-openclaw-coding-tools.test.ts | 4 +-- .../attempt-tool-construction-plan.test.ts | 4 +-- .../run/attempt-tool-construction-plan.ts | 2 +- src/agents/openclaw-tools.ts | 4 +-- src/agents/system-prompt.test.ts | 17 +++++----- src/agents/system-prompt.ts | 22 ++++++------ .../test-helpers/fast-openclaw-tools.ts | 2 +- src/agents/tool-catalog.test.ts | 2 +- src/agents/tool-catalog.ts | 4 +-- ...ol.test.ts => skill-workshop-tool.test.ts} | 34 +++++++++---------- ...esearch-tool.ts => skill-workshop-tool.ts} | 24 ++++++------- src/cli/skills-cli.ts | 4 +-- src/skills/workshop/policy.ts | 16 ++++----- src/skills/workshop/service.test.ts | 2 +- src/skills/workshop/types.ts | 2 +- 22 files changed, 87 insertions(+), 87 deletions(-) rename src/agents/tools/{skill-research-tool.test.ts => skill-workshop-tool.test.ts} (90%) rename src/agents/tools/{skill-research-tool.ts => skill-workshop-tool.ts} (96%) diff --git a/docs/cli/skills.md b/docs/cli/skills.md index 20d516e2667..f3e99c78b0c 100644 --- a/docs/cli/skills.md +++ b/docs/cli/skills.md @@ -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 diff --git a/docs/tools/creating-skills.md b/docs/tools/creating-skills.md index d3da3026ac6..c5ae7eac67c 100644 --- a/docs/tools/creating-skills.md +++ b/docs/tools/creating-skills.md @@ -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 \ diff --git a/docs/tools/skills.md b/docs/tools/skills.md index 60994138769..b034996daf4 100644 --- a/docs/tools/skills.md +++ b/docs/tools/skills.md @@ -169,7 +169,7 @@ openclaw skills workshop reject openclaw skills workshop quarantine ``` -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 diff --git a/packages/gateway-protocol/src/schema/agents-models-skills.test.ts b/packages/gateway-protocol/src/schema/agents-models-skills.test.ts index e59c353b085..69e4ac52e6e 100644 --- a/packages/gateway-protocol/src/schema/agents-models-skills.test.ts +++ b/packages/gateway-protocol/src/schema/agents-models-skills.test.ts @@ -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: { diff --git a/packages/gateway-protocol/src/schema/agents-models-skills.ts b/packages/gateway-protocol/src/schema/agents-models-skills.ts index 5344e595be4..a4af8cd7469 100644 --- a/packages/gateway-protocol/src/schema/agents-models-skills.ts +++ b/packages/gateway-protocol/src/schema/agents-models-skills.ts @@ -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"), diff --git a/src/agents/agent-tools.before-tool-call.embedded-mode.test.ts b/src/agents/agent-tools.before-tool-call.embedded-mode.test.ts index 84c6f8ec447..64405c6c1e5 100644 --- a/src/agents/agent-tools.before-tool-call.embedded-mode.test.ts +++ b/src/agents/agent-tools.before-tool-call.embedded-mode.test.ts @@ -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).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).mockReturnValue(false); const result = await runBeforeToolCallHook({ - toolName: "skill_research", + toolName: "skill_workshop", params: { action: "reject", proposal_id: "weather-20260530-a1b2c3d4e5" }, ctx: { config: { diff --git a/src/agents/agent-tools.before-tool-call.ts b/src/agents/agent-tools.before-tool-call.ts index 72b53dc37bb..20e3ddc8f90 100644 --- a/src/agents/agent-tools.before-tool-call.ts +++ b/src/agents/agent-tools.before-tool-call.ts @@ -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 } : {}), diff --git a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts index b5ea445de62..3ecbba9a935 100644 --- a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts @@ -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", () => { diff --git a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.test.ts b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.test.ts index 2fb0643786d..6fccc89400b 100644 --- a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.test.ts @@ -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, diff --git a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts index ad5c2d5f060..624691edc95 100644 --- a/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts +++ b/src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts @@ -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", diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index 01d4cb5aa12..89b9794ab13 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -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, diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index 58701e631d5..01a24922b83 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -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`.", ); }); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 816d9a18562..b226a280f18 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -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 diff --git a/src/agents/test-helpers/fast-openclaw-tools.ts b/src/agents/test-helpers/fast-openclaw-tools.ts index 8fc1ff586bd..2395172c16e 100644 --- a/src/agents/test-helpers/fast-openclaw-tools.ts +++ b/src/agents/test-helpers/fast-openclaw-tools.ts @@ -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"), diff --git a/src/agents/tool-catalog.test.ts b/src/agents/tool-catalog.test.ts index e0431093fc7..10395fc3be9 100644 --- a/src/agents/tool-catalog.test.ts +++ b/src/agents/tool-catalog.test.ts @@ -45,7 +45,7 @@ describe("tool-catalog", () => { "create_goal", "update_goal", "update_plan", - "skill_research", + "skill_workshop", "image", "image_generate", "music_generate", diff --git a/src/agents/tool-catalog.ts b/src/agents/tool-catalog.ts index c9a8771f8bf..d20e1360774 100644 --- a/src/agents/tool-catalog.ts +++ b/src/agents/tool-catalog.ts @@ -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", diff --git a/src/agents/tools/skill-research-tool.test.ts b/src/agents/tools/skill-workshop-tool.test.ts similarity index 90% rename from src/agents/tools/skill-research-tool.test.ts rename to src/agents/tools/skill-workshop-tool.test.ts index ff11104f5a1..10222f7eeca 100644 --- a/src/agents/tools/skill-research-tool.test.ts +++ b/src/agents/tools/skill-workshop-tool.test.ts @@ -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; @@ -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", diff --git a/src/agents/tools/skill-research-tool.ts b/src/agents/tools/skill-workshop-tool.ts similarity index 96% rename from src/agents/tools/skill-research-tool.ts rename to src/agents/tools/skill-workshop-tool.ts index f39e5cc269c..51e4940b53a 100644 --- a/src/agents/tools/skill-research-tool.ts +++ b/src/agents/tools/skill-workshop-tool.ts @@ -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); diff --git a/src/cli/skills-cli.ts b/src/cli/skills-cli.ts index e91e65c3288..d1f9d998ed4 100644 --- a/src/cli/skills-cli.ts +++ b/src/cli/skills-cli.ts @@ -552,7 +552,7 @@ export function registerSkillsCli(program: Command) { "--proposal-dir ", "Path to proposal directory with PROPOSAL.md and UTF-8 text support files", ) - .option("--goal ", "Research or improvement goal") + .option("--goal ", "Proposal or improvement goal") .option("--evidence ", "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 to proposal directory with PROPOSAL.md and UTF-8 text support files", ) - .option("--goal ", "Research or improvement goal") + .option("--goal ", "Proposal or improvement goal") .option("--evidence ", "Evidence or notes for the proposal") .option("--json", "Output as JSON", false) .action( diff --git a/src/skills/workshop/policy.ts b/src/skills/workshop/policy.ts index f0dc9384867..1b7e1c6ab22 100644 --- a/src/skills/workshop/policy.ts +++ b/src/skills/workshop/policy.ts @@ -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); diff --git a/src/skills/workshop/service.test.ts b/src/skills/workshop/service.test.ts index a817a0d9d40..3831529765d 100644 --- a/src/skills/workshop/service.test.ts +++ b/src/skills/workshop/service.test.ts @@ -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", }); diff --git a/src/skills/workshop/types.ts b/src/skills/workshop/types.ts index 16292f310a3..3f7d4c76fd3 100644 --- a/src/skills/workshop/types.ts +++ b/src/skills/workshop/types.ts @@ -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;