Files
openclaw/src/auto-reply/reply/commands-learn.ts
T

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 };
},
);