mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
// Handles /learn by turning the command into a Skill Workshop authoring turn.
|
|
import { resolveCliBackendConfig } from "../../agents/cli-backends.js";
|
|
import { resolveConversationCapabilityProfile } from "../../agents/conversation-capability-profile.js";
|
|
import {
|
|
agentHarnessExposesOpenClawTools,
|
|
selectAgentHarness,
|
|
} from "../../agents/harness/selection.js";
|
|
import {
|
|
isCliRuntimeAliasForProvider,
|
|
resolveCliRuntimeExecutionProvider,
|
|
} from "../../agents/model-runtime-aliases.js";
|
|
import { supportsModelTools } from "../../agents/model-tool-support.js";
|
|
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
|
import { isToolAllowedByPolicyName } from "../../agents/tool-policy-match.js";
|
|
import { resolveConfiguredModelCompat } from "../../agents/tools-effective-inventory.js";
|
|
import { buildLearnPrompt, DEFAULT_LEARN_REQUEST } from "../../skills/workshop/learn-prompt.js";
|
|
import { resolveSkillWorkshopToolPolicyAvailability } from "../../skills/workshop/tool-policy-diagnostic.js";
|
|
import { applyCommandTextToParams } from "./command-context-rewrite.js";
|
|
import { commandReply, defineAuthorizedTextCommand } from "./command-gates.js";
|
|
import type { CommandHandler, HandleCommandsParams } from "./commands-types.js";
|
|
import { resolveRuntimePolicySessionKey } from "./runtime-policy-session-key.js";
|
|
|
|
const LEARN_COMMAND_PREFIX = "/learn";
|
|
const SKILL_WORKSHOP_TOOL_NAME = "skill_workshop";
|
|
const SKILL_WORKSHOP_UNAVAILABLE_REPLY =
|
|
"Skill workshop is not available on this agent. Use a non-sandboxed agent where the skill_workshop tool is available, or use the openclaw skills workshop CLI.";
|
|
|
|
function parseLearnRequest(raw: string): string | null {
|
|
const trimmed = raw.trim();
|
|
const commandEnd = trimmed.search(/\s/);
|
|
const commandToken = commandEnd === -1 ? trimmed : trimmed.slice(0, commandEnd);
|
|
if (commandToken.toLowerCase() !== LEARN_COMMAND_PREFIX) {
|
|
return null;
|
|
}
|
|
const request = commandEnd === -1 ? "" : trimmed.slice(commandEnd).trim();
|
|
return request || DEFAULT_LEARN_REQUEST;
|
|
}
|
|
|
|
function workshopIsAvailable(params: HandleCommandsParams): boolean {
|
|
if (params.opts?.disableTools) {
|
|
return false;
|
|
}
|
|
if (params.opts?.toolsAllow?.length === 0) {
|
|
return false;
|
|
}
|
|
if (
|
|
params.opts?.toolsAllow !== undefined &&
|
|
!isToolAllowedByPolicyName(SKILL_WORKSHOP_TOOL_NAME, { allow: params.opts.toolsAllow })
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const policySessionKey = resolveRuntimePolicySessionKey({
|
|
cfg: params.cfg,
|
|
ctx: params.ctx,
|
|
sessionKey: params.sessionKey,
|
|
});
|
|
if (
|
|
resolveSandboxRuntimeStatus({
|
|
cfg: params.cfg,
|
|
sessionKey: policySessionKey,
|
|
}).sandboxed
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const targetSessionEntry = params.sessionStore?.[params.sessionKey] ?? params.sessionEntry;
|
|
const runtimeOverride = targetSessionEntry?.agentRuntimeOverride;
|
|
const cliProvider = isCliRuntimeAliasForProvider({
|
|
provider: params.provider,
|
|
runtime: runtimeOverride,
|
|
cfg: params.cfg,
|
|
})
|
|
? runtimeOverride
|
|
: resolveCliRuntimeExecutionProvider({
|
|
provider: params.provider,
|
|
cfg: params.cfg,
|
|
agentId: params.agentId,
|
|
modelId: params.model,
|
|
authProfileId: targetSessionEntry?.authProfileOverride,
|
|
});
|
|
if (cliProvider) {
|
|
const cliBackend = resolveCliBackendConfig(cliProvider, params.cfg, {
|
|
agentId: params.agentId,
|
|
});
|
|
if (!cliBackend?.bundleMcp) {
|
|
return false;
|
|
}
|
|
} else {
|
|
const harness = selectAgentHarness({
|
|
provider: params.provider,
|
|
modelId: params.model,
|
|
config: params.cfg,
|
|
agentId: params.agentId,
|
|
sessionKey: policySessionKey,
|
|
});
|
|
if (!agentHarnessExposesOpenClawTools(harness.id)) {
|
|
return false;
|
|
}
|
|
}
|
|
const modelCompat = resolveConfiguredModelCompat({
|
|
cfg: params.cfg,
|
|
modelProvider: params.provider,
|
|
modelId: params.model,
|
|
});
|
|
if (modelCompat && !supportsModelTools({ compat: modelCompat })) {
|
|
return false;
|
|
}
|
|
const capabilityProfile = resolveConversationCapabilityProfile({
|
|
config: params.cfg,
|
|
agentId: params.agentId,
|
|
sessionKey: policySessionKey ?? params.sessionKey,
|
|
workspaceDir: params.workspaceDir,
|
|
agentDir: params.agentDir,
|
|
runtimeToolAllowlist: params.opts?.toolsAllow,
|
|
messageProvider: params.command.channel,
|
|
senderId: params.command.senderId,
|
|
senderName: params.ctx.SenderName,
|
|
senderUsername: params.ctx.SenderUsername,
|
|
senderE164: params.ctx.SenderE164,
|
|
senderIsOwner: params.command.senderIsOwner,
|
|
agentAccountId: params.command.accountId ?? params.ctx.AccountId,
|
|
modelProvider: params.provider,
|
|
modelId: params.model,
|
|
groupId: params.sessionEntry?.groupId,
|
|
groupChannel: params.sessionEntry?.groupChannel ?? params.ctx.GroupChannel,
|
|
groupSpace: params.sessionEntry?.space ?? params.ctx.GroupSpace,
|
|
});
|
|
return resolveSkillWorkshopToolPolicyAvailability({
|
|
config: params.cfg,
|
|
conversationCapabilityProfile: capabilityProfile,
|
|
}).available;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Command handler for /learn skill-draft requests. */
|
|
export const handleLearnCommand: CommandHandler = defineAuthorizedTextCommand(
|
|
{ label: LEARN_COMMAND_PREFIX, match: parseLearnRequest },
|
|
(params, request) => {
|
|
if (!workshopIsAvailable(params)) {
|
|
return commandReply(SKILL_WORKSHOP_UNAVAILABLE_REPLY);
|
|
}
|
|
|
|
applyCommandTextToParams(params, buildLearnPrompt(request));
|
|
return { shouldContinue: true };
|
|
},
|
|
);
|