feat: expand skill command registration
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
- Repo: ignore local identity files to avoid accidental commits. (#1001) — thanks @gerardward2007.
|
- Repo: ignore local identity files to avoid accidental commits. (#1001) — thanks @gerardward2007.
|
||||||
- Sessions/Security: add `session.dmScope` for multi-user DM isolation and audit warnings. (#948) — thanks @Alphonse-arianee.
|
- Sessions/Security: add `session.dmScope` for multi-user DM isolation and audit warnings. (#948) — thanks @Alphonse-arianee.
|
||||||
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows.
|
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows.
|
||||||
- Skills: add user-invocable skill commands with sanitized names and an opt-out for model invocation.
|
- Skills: add user-invocable skill commands (sanitized + unique), native skill registration gating, and an opt-out for model invocation.
|
||||||
- Onboarding: switch channels setup to a single-select loop with per-channel actions and disabled hints in the picker.
|
- Onboarding: switch channels setup to a single-select loop with per-channel actions and disabled hints in the picker.
|
||||||
- TUI: show provider/model labels for the active session and default model.
|
- TUI: show provider/model labels for the active session and default model.
|
||||||
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
|
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ They run immediately, are stripped before the model sees the message, and the re
|
|||||||
{
|
{
|
||||||
commands: {
|
commands: {
|
||||||
native: "auto",
|
native: "auto",
|
||||||
|
nativeSkills: "auto",
|
||||||
text: true,
|
text: true,
|
||||||
bash: false,
|
bash: false,
|
||||||
bashForegroundMs: 2000,
|
bashForegroundMs: 2000,
|
||||||
@@ -43,6 +44,9 @@ They run immediately, are stripped before the model sees the message, and the re
|
|||||||
- Auto: on for Discord/Telegram; off for Slack (until you add slash commands); ignored for providers without native support.
|
- Auto: on for Discord/Telegram; off for Slack (until you add slash commands); ignored for providers without native support.
|
||||||
- Set `channels.discord.commands.native`, `channels.telegram.commands.native`, or `channels.slack.commands.native` to override per provider (bool or `"auto"`).
|
- Set `channels.discord.commands.native`, `channels.telegram.commands.native`, or `channels.slack.commands.native` to override per provider (bool or `"auto"`).
|
||||||
- `false` clears previously registered commands on Discord/Telegram at startup. Slack commands are managed in the Slack app and are not removed automatically.
|
- `false` clears previously registered commands on Discord/Telegram at startup. Slack commands are managed in the Slack app and are not removed automatically.
|
||||||
|
- `commands.nativeSkills` (default `"auto"`) registers **skill** commands natively when supported.
|
||||||
|
- Auto: on for Discord/Telegram; off for Slack (Slack requires creating a slash command per skill).
|
||||||
|
- Set `channels.discord.commands.nativeSkills`, `channels.telegram.commands.nativeSkills`, or `channels.slack.commands.nativeSkills` to override per provider (bool or `"auto"`).
|
||||||
- `commands.bash` (default `false`) enables `! <cmd>` to run host shell commands (`/bash <cmd>` is an alias; requires `tools.elevated` allowlists).
|
- `commands.bash` (default `false`) enables `! <cmd>` to run host shell commands (`/bash <cmd>` is an alias; requires `tools.elevated` allowlists).
|
||||||
- `commands.bashForegroundMs` (default `2000`) controls how long bash waits before switching to background mode (`0` backgrounds immediately).
|
- `commands.bashForegroundMs` (default `2000`) controls how long bash waits before switching to background mode (`0` backgrounds immediately).
|
||||||
- `commands.config` (default `false`) enables `/config` (reads/writes `clawdbot.json`).
|
- `commands.config` (default `false`) enables `/config` (reads/writes `clawdbot.json`).
|
||||||
|
|||||||
@@ -38,6 +38,20 @@ function listAgents(cfg: ClawdbotConfig): AgentEntry[] {
|
|||||||
return list.filter((entry): entry is AgentEntry => Boolean(entry && typeof entry === "object"));
|
return list.filter((entry): entry is AgentEntry => Boolean(entry && typeof entry === "object"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listAgentIds(cfg: ClawdbotConfig): string[] {
|
||||||
|
const agents = listAgents(cfg);
|
||||||
|
if (agents.length === 0) return [DEFAULT_AGENT_ID];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const ids: string[] = [];
|
||||||
|
for (const entry of agents) {
|
||||||
|
const id = normalizeAgentId(entry?.id);
|
||||||
|
if (seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
return ids.length > 0 ? ids : [DEFAULT_AGENT_ID];
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveDefaultAgentId(cfg: ClawdbotConfig): string {
|
export function resolveDefaultAgentId(cfg: ClawdbotConfig): string {
|
||||||
const agents = listAgents(cfg);
|
const agents = listAgents(cfg);
|
||||||
if (agents.length === 0) return DEFAULT_AGENT_ID;
|
if (agents.length === 0) return DEFAULT_AGENT_ID;
|
||||||
|
|||||||
@@ -321,17 +321,26 @@ export function buildWorkspaceSkillCommandSpecs(
|
|||||||
|
|
||||||
const specs: SkillCommandSpec[] = [];
|
const specs: SkillCommandSpec[] = [];
|
||||||
for (const entry of userInvocable) {
|
for (const entry of userInvocable) {
|
||||||
const base = sanitizeSkillCommandName(entry.skill.name);
|
const rawName = entry.skill.name;
|
||||||
|
const base = sanitizeSkillCommandName(rawName);
|
||||||
|
if (base !== rawName) {
|
||||||
|
console.warn(`[skills] Sanitized skill command name "${rawName}" to "/${base}".`);
|
||||||
|
}
|
||||||
const unique = resolveUniqueSkillCommandName(base, used);
|
const unique = resolveUniqueSkillCommandName(base, used);
|
||||||
|
if (unique !== base) {
|
||||||
|
console.warn(
|
||||||
|
`[skills] De-duplicated skill command name for "${rawName}" to "/${unique}".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
used.add(unique.toLowerCase());
|
used.add(unique.toLowerCase());
|
||||||
const rawDescription = entry.skill.description?.trim() || entry.skill.name;
|
const rawDescription = entry.skill.description?.trim() || rawName;
|
||||||
const description =
|
const description =
|
||||||
rawDescription.length > SKILL_COMMAND_DESCRIPTION_MAX_LENGTH
|
rawDescription.length > SKILL_COMMAND_DESCRIPTION_MAX_LENGTH
|
||||||
? rawDescription.slice(0, SKILL_COMMAND_DESCRIPTION_MAX_LENGTH - 1) + "…"
|
? rawDescription.slice(0, SKILL_COMMAND_DESCRIPTION_MAX_LENGTH - 1) + "…"
|
||||||
: rawDescription;
|
: rawDescription;
|
||||||
specs.push({
|
specs.push({
|
||||||
name: unique,
|
name: unique,
|
||||||
skillName: entry.skill.name,
|
skillName: rawName,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
@@ -8,6 +9,17 @@ import { getReplyFromConfig } from "./reply.js";
|
|||||||
|
|
||||||
const MAIN_SESSION_KEY = "agent:main:main";
|
const MAIN_SESSION_KEY = "agent:main:main";
|
||||||
|
|
||||||
|
async function writeSkill(params: { workspaceDir: string; name: string; description: string }) {
|
||||||
|
const { workspaceDir, name, description } = params;
|
||||||
|
const skillDir = path.join(workspaceDir, "skills", name);
|
||||||
|
await fs.mkdir(skillDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(skillDir, "SKILL.md"),
|
||||||
|
`---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
vi.mock("../agents/pi-embedded.js", () => ({
|
vi.mock("../agents/pi-embedded.js", () => ({
|
||||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||||
runEmbeddedPiAgent: vi.fn(),
|
runEmbeddedPiAgent: vi.fn(),
|
||||||
@@ -174,6 +186,43 @@ describe("directive behavior", () => {
|
|||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("treats skill commands as reserved for model aliases", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
|
const workspace = path.join(home, "clawd");
|
||||||
|
await writeSkill({
|
||||||
|
workspaceDir: workspace,
|
||||||
|
name: "demo-skill",
|
||||||
|
description: "Demo skill",
|
||||||
|
});
|
||||||
|
|
||||||
|
await getReplyFromConfig(
|
||||||
|
{
|
||||||
|
Body: "/demo_skill",
|
||||||
|
From: "+1222",
|
||||||
|
To: "+1222",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
model: "anthropic/claude-opus-4-5",
|
||||||
|
workspace,
|
||||||
|
models: {
|
||||||
|
"anthropic/claude-opus-4-5": { alias: "demo_skill" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
|
session: { store: path.join(home, "sessions.json") },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runEmbeddedPiAgent).toHaveBeenCalled();
|
||||||
|
const prompt = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0]?.prompt ?? "";
|
||||||
|
expect(prompt).toContain('Use the "demo-skill" skill');
|
||||||
|
});
|
||||||
|
});
|
||||||
it("errors on invalid queue options", async () => {
|
it("errors on invalid queue options", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import type { ModelAliasIndex } from "../../agents/model-selection.js";
|
import type { ModelAliasIndex } from "../../agents/model-selection.js";
|
||||||
|
import type { SkillCommandSpec } from "../../agents/skills.js";
|
||||||
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import type { SessionEntry } from "../../config/sessions.js";
|
import type { SessionEntry } from "../../config/sessions.js";
|
||||||
import { listChatCommands, shouldHandleTextCommands } from "../commands-registry.js";
|
import { listChatCommands, shouldHandleTextCommands } from "../commands-registry.js";
|
||||||
|
import { listSkillCommandsForWorkspace } from "../skill-commands.js";
|
||||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||||
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
|
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
|
||||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||||
@@ -24,6 +26,7 @@ export type ReplyDirectiveContinuation = {
|
|||||||
commandSource: string;
|
commandSource: string;
|
||||||
command: ReturnType<typeof buildCommandContext>;
|
command: ReturnType<typeof buildCommandContext>;
|
||||||
allowTextCommands: boolean;
|
allowTextCommands: boolean;
|
||||||
|
skillCommands?: SkillCommandSpec[];
|
||||||
directives: InlineDirectives;
|
directives: InlineDirectives;
|
||||||
cleanedBody: string;
|
cleanedBody: string;
|
||||||
messageProviderKey: string;
|
messageProviderKey: string;
|
||||||
@@ -65,6 +68,7 @@ export async function resolveReplyDirectives(params: {
|
|||||||
cfg: ClawdbotConfig;
|
cfg: ClawdbotConfig;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
agentDir: string;
|
agentDir: string;
|
||||||
|
workspaceDir: string;
|
||||||
agentCfg: AgentDefaults;
|
agentCfg: AgentDefaults;
|
||||||
sessionCtx: TemplateContext;
|
sessionCtx: TemplateContext;
|
||||||
sessionEntry?: SessionEntry;
|
sessionEntry?: SessionEntry;
|
||||||
@@ -83,6 +87,7 @@ export async function resolveReplyDirectives(params: {
|
|||||||
model: string;
|
model: string;
|
||||||
typing: TypingController;
|
typing: TypingController;
|
||||||
opts?: GetReplyOptions;
|
opts?: GetReplyOptions;
|
||||||
|
skillFilter?: string[];
|
||||||
}): Promise<ReplyDirectiveResult> {
|
}): Promise<ReplyDirectiveResult> {
|
||||||
const {
|
const {
|
||||||
ctx,
|
ctx,
|
||||||
@@ -90,6 +95,7 @@ export async function resolveReplyDirectives(params: {
|
|||||||
agentId,
|
agentId,
|
||||||
agentCfg,
|
agentCfg,
|
||||||
agentDir,
|
agentDir,
|
||||||
|
workspaceDir,
|
||||||
sessionCtx,
|
sessionCtx,
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
sessionStore,
|
sessionStore,
|
||||||
@@ -106,6 +112,7 @@ export async function resolveReplyDirectives(params: {
|
|||||||
model: initialModel,
|
model: initialModel,
|
||||||
typing,
|
typing,
|
||||||
opts,
|
opts,
|
||||||
|
skillFilter,
|
||||||
} = params;
|
} = params;
|
||||||
let provider = initialProvider;
|
let provider = initialProvider;
|
||||||
let model = initialModel;
|
let model = initialModel;
|
||||||
@@ -132,11 +139,23 @@ export async function resolveReplyDirectives(params: {
|
|||||||
surface: command.surface,
|
surface: command.surface,
|
||||||
commandSource: ctx.CommandSource,
|
commandSource: ctx.CommandSource,
|
||||||
});
|
});
|
||||||
|
const shouldResolveSkillCommands =
|
||||||
|
allowTextCommands && command.commandBodyNormalized.includes("/");
|
||||||
|
const skillCommands = shouldResolveSkillCommands
|
||||||
|
? listSkillCommandsForWorkspace({
|
||||||
|
workspaceDir,
|
||||||
|
cfg,
|
||||||
|
skillFilter,
|
||||||
|
})
|
||||||
|
: [];
|
||||||
const reservedCommands = new Set(
|
const reservedCommands = new Set(
|
||||||
listChatCommands().flatMap((cmd) =>
|
listChatCommands().flatMap((cmd) =>
|
||||||
cmd.textAliases.map((a) => a.replace(/^\//, "").toLowerCase()),
|
cmd.textAliases.map((a) => a.replace(/^\//, "").toLowerCase()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
for (const command of skillCommands) {
|
||||||
|
reservedCommands.add(command.name.toLowerCase());
|
||||||
|
}
|
||||||
const configuredAliases = Object.values(cfg.agents?.defaults?.models ?? {})
|
const configuredAliases = Object.values(cfg.agents?.defaults?.models ?? {})
|
||||||
.map((entry) => entry.alias?.trim())
|
.map((entry) => entry.alias?.trim())
|
||||||
.filter((alias): alias is string => Boolean(alias))
|
.filter((alias): alias is string => Boolean(alias))
|
||||||
@@ -385,6 +404,7 @@ export async function resolveReplyDirectives(params: {
|
|||||||
commandSource,
|
commandSource,
|
||||||
command,
|
command,
|
||||||
allowTextCommands,
|
allowTextCommands,
|
||||||
|
skillCommands,
|
||||||
directives,
|
directives,
|
||||||
cleanedBody,
|
cleanedBody,
|
||||||
messageProviderKey,
|
messageProviderKey,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getChannelDock } from "../../channels/dock.js";
|
import { getChannelDock } from "../../channels/dock.js";
|
||||||
|
import type { SkillCommandSpec } from "../../agents/skills.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import type { SessionEntry } from "../../config/sessions.js";
|
import type { SessionEntry } from "../../config/sessions.js";
|
||||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||||
@@ -39,6 +40,7 @@ export async function handleInlineActions(params: {
|
|||||||
allowTextCommands: boolean;
|
allowTextCommands: boolean;
|
||||||
inlineStatusRequested: boolean;
|
inlineStatusRequested: boolean;
|
||||||
command: Parameters<typeof handleCommands>[0]["command"];
|
command: Parameters<typeof handleCommands>[0]["command"];
|
||||||
|
skillCommands?: SkillCommandSpec[];
|
||||||
directives: InlineDirectives;
|
directives: InlineDirectives;
|
||||||
cleanedBody: string;
|
cleanedBody: string;
|
||||||
elevatedEnabled: boolean;
|
elevatedEnabled: boolean;
|
||||||
@@ -99,13 +101,16 @@ export async function handleInlineActions(params: {
|
|||||||
let cleanedBody = initialCleanedBody;
|
let cleanedBody = initialCleanedBody;
|
||||||
|
|
||||||
const shouldLoadSkillCommands = command.commandBodyNormalized.startsWith("/");
|
const shouldLoadSkillCommands = command.commandBodyNormalized.startsWith("/");
|
||||||
const skillCommands = shouldLoadSkillCommands
|
const skillCommands =
|
||||||
? listSkillCommandsForWorkspace({
|
shouldLoadSkillCommands && params.skillCommands
|
||||||
workspaceDir,
|
? params.skillCommands
|
||||||
cfg,
|
: shouldLoadSkillCommands
|
||||||
skillFilter,
|
? listSkillCommandsForWorkspace({
|
||||||
})
|
workspaceDir,
|
||||||
: [];
|
cfg,
|
||||||
|
skillFilter,
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const skillInvocation =
|
const skillInvocation =
|
||||||
allowTextCommands && skillCommands.length > 0
|
allowTextCommands && skillCommands.length > 0
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export async function getReplyFromConfig(
|
|||||||
cfg,
|
cfg,
|
||||||
agentId,
|
agentId,
|
||||||
agentDir,
|
agentDir,
|
||||||
|
workspaceDir,
|
||||||
agentCfg,
|
agentCfg,
|
||||||
sessionCtx,
|
sessionCtx,
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
@@ -136,6 +137,7 @@ export async function getReplyFromConfig(
|
|||||||
model,
|
model,
|
||||||
typing,
|
typing,
|
||||||
opts,
|
opts,
|
||||||
|
skillFilter: opts?.skillFilter,
|
||||||
});
|
});
|
||||||
if (directiveResult.kind === "reply") {
|
if (directiveResult.kind === "reply") {
|
||||||
return directiveResult.reply;
|
return directiveResult.reply;
|
||||||
@@ -145,6 +147,7 @@ export async function getReplyFromConfig(
|
|||||||
commandSource,
|
commandSource,
|
||||||
command,
|
command,
|
||||||
allowTextCommands,
|
allowTextCommands,
|
||||||
|
skillCommands,
|
||||||
directives,
|
directives,
|
||||||
cleanedBody,
|
cleanedBody,
|
||||||
elevatedEnabled,
|
elevatedEnabled,
|
||||||
@@ -187,6 +190,7 @@ export async function getReplyFromConfig(
|
|||||||
allowTextCommands,
|
allowTextCommands,
|
||||||
inlineStatusRequested,
|
inlineStatusRequested,
|
||||||
command,
|
command,
|
||||||
|
skillCommands,
|
||||||
directives,
|
directives,
|
||||||
cleanedBody,
|
cleanedBody,
|
||||||
elevatedEnabled,
|
elevatedEnabled,
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { resolveSkillCommandInvocation } from "./skill-commands.js";
|
import { listSkillCommandsForAgents, resolveSkillCommandInvocation } from "./skill-commands.js";
|
||||||
|
|
||||||
|
async function writeSkill(params: {
|
||||||
|
workspaceDir: string;
|
||||||
|
dirName: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}) {
|
||||||
|
const { workspaceDir, dirName, name, description } = params;
|
||||||
|
const skillDir = path.join(workspaceDir, "skills", dirName);
|
||||||
|
await fs.mkdir(skillDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(skillDir, "SKILL.md"),
|
||||||
|
`---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
describe("resolveSkillCommandInvocation", () => {
|
describe("resolveSkillCommandInvocation", () => {
|
||||||
it("matches skill commands and parses args", () => {
|
it("matches skill commands and parses args", () => {
|
||||||
@@ -19,3 +38,44 @@ describe("resolveSkillCommandInvocation", () => {
|
|||||||
expect(invocation).toBeNull();
|
expect(invocation).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("listSkillCommandsForAgents", () => {
|
||||||
|
it("merges command names across agents and de-duplicates", async () => {
|
||||||
|
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-skills-"));
|
||||||
|
const mainWorkspace = path.join(baseDir, "main");
|
||||||
|
const researchWorkspace = path.join(baseDir, "research");
|
||||||
|
await writeSkill({
|
||||||
|
workspaceDir: mainWorkspace,
|
||||||
|
dirName: "demo",
|
||||||
|
name: "demo-skill",
|
||||||
|
description: "Demo skill",
|
||||||
|
});
|
||||||
|
await writeSkill({
|
||||||
|
workspaceDir: researchWorkspace,
|
||||||
|
dirName: "demo2",
|
||||||
|
name: "demo-skill",
|
||||||
|
description: "Demo skill 2",
|
||||||
|
});
|
||||||
|
await writeSkill({
|
||||||
|
workspaceDir: researchWorkspace,
|
||||||
|
dirName: "extra",
|
||||||
|
name: "extra-skill",
|
||||||
|
description: "Extra skill",
|
||||||
|
});
|
||||||
|
|
||||||
|
const commands = listSkillCommandsForAgents({
|
||||||
|
cfg: {
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{ id: "main", workspace: mainWorkspace },
|
||||||
|
{ id: "research", workspace: researchWorkspace },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const names = commands.map((entry) => entry.name);
|
||||||
|
expect(names).toContain("demo_skill");
|
||||||
|
expect(names).toContain("demo_skill_2");
|
||||||
|
expect(names).toContain("extra_skill");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { listAgentIds, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
||||||
import { getRemoteSkillEligibility } from "../infra/skills-remote.js";
|
import { getRemoteSkillEligibility } from "../infra/skills-remote.js";
|
||||||
import { buildWorkspaceSkillCommandSpecs, type SkillCommandSpec } from "../agents/skills.js";
|
import { buildWorkspaceSkillCommandSpecs, type SkillCommandSpec } from "../agents/skills.js";
|
||||||
import { listChatCommands } from "./commands-registry.js";
|
import { listChatCommands } from "./commands-registry.js";
|
||||||
@@ -29,6 +32,29 @@ export function listSkillCommandsForWorkspace(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listSkillCommandsForAgents(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
agentIds?: string[];
|
||||||
|
}): SkillCommandSpec[] {
|
||||||
|
const used = resolveReservedCommandNames();
|
||||||
|
const entries: SkillCommandSpec[] = [];
|
||||||
|
const agentIds = params.agentIds ?? listAgentIds(params.cfg);
|
||||||
|
for (const agentId of agentIds) {
|
||||||
|
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, agentId);
|
||||||
|
if (!fs.existsSync(workspaceDir)) continue;
|
||||||
|
const commands = buildWorkspaceSkillCommandSpecs(workspaceDir, {
|
||||||
|
config: params.cfg,
|
||||||
|
eligibility: { remote: getRemoteSkillEligibility() },
|
||||||
|
reservedNames: used,
|
||||||
|
});
|
||||||
|
for (const command of commands) {
|
||||||
|
used.add(command.name.toLowerCase());
|
||||||
|
entries.push(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveSkillCommandInvocation(params: {
|
export function resolveSkillCommandInvocation(params: {
|
||||||
commandBodyNormalized: string;
|
commandBodyNormalized: string;
|
||||||
skillCommands: SkillCommandSpec[];
|
skillCommands: SkillCommandSpec[];
|
||||||
|
|||||||
48
src/config/commands.test.ts
Normal file
48
src/config/commands.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveNativeSkillsEnabled } from "./commands.js";
|
||||||
|
|
||||||
|
describe("resolveNativeSkillsEnabled", () => {
|
||||||
|
it("uses provider defaults for auto", () => {
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "discord",
|
||||||
|
globalSetting: "auto",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "telegram",
|
||||||
|
globalSetting: "auto",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "slack",
|
||||||
|
globalSetting: "auto",
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "whatsapp",
|
||||||
|
globalSetting: "auto",
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("honors explicit provider settings", () => {
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "slack",
|
||||||
|
providerSetting: true,
|
||||||
|
globalSetting: "auto",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
resolveNativeSkillsEnabled({
|
||||||
|
providerId: "discord",
|
||||||
|
providerSetting: false,
|
||||||
|
globalSetting: true,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,18 @@ function resolveAutoDefault(providerId?: ChannelId): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveNativeSkillsEnabled(params: {
|
||||||
|
providerId: ChannelId;
|
||||||
|
providerSetting?: NativeCommandsSetting;
|
||||||
|
globalSetting?: NativeCommandsSetting;
|
||||||
|
}): boolean {
|
||||||
|
const { providerId, providerSetting, globalSetting } = params;
|
||||||
|
const setting = providerSetting === undefined ? globalSetting : providerSetting;
|
||||||
|
if (setting === true) return true;
|
||||||
|
if (setting === false) return false;
|
||||||
|
return resolveAutoDefault(providerId);
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveNativeCommandsEnabled(params: {
|
export function resolveNativeCommandsEnabled(params: {
|
||||||
providerId: ChannelId;
|
providerId: ChannelId;
|
||||||
providerSetting?: NativeCommandsSetting;
|
providerSetting?: NativeCommandsSetting;
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"agents.defaults.humanDelay.maxMs": "Human Delay Max (ms)",
|
"agents.defaults.humanDelay.maxMs": "Human Delay Max (ms)",
|
||||||
"agents.defaults.cliBackends": "CLI Backends",
|
"agents.defaults.cliBackends": "CLI Backends",
|
||||||
"commands.native": "Native Commands",
|
"commands.native": "Native Commands",
|
||||||
|
"commands.nativeSkills": "Native Skill Commands",
|
||||||
"commands.text": "Text Commands",
|
"commands.text": "Text Commands",
|
||||||
"commands.bash": "Allow Bash Chat Command",
|
"commands.bash": "Allow Bash Chat Command",
|
||||||
"commands.bashForegroundMs": "Bash Foreground Window (ms)",
|
"commands.bashForegroundMs": "Bash Foreground Window (ms)",
|
||||||
@@ -323,6 +324,8 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"agents.defaults.humanDelay.maxMs": "Maximum delay in ms for custom humanDelay (default: 2500).",
|
"agents.defaults.humanDelay.maxMs": "Maximum delay in ms for custom humanDelay (default: 2500).",
|
||||||
"commands.native":
|
"commands.native":
|
||||||
"Register native commands with channels that support it (Discord/Slack/Telegram).",
|
"Register native commands with channels that support it (Discord/Slack/Telegram).",
|
||||||
|
"commands.nativeSkills":
|
||||||
|
"Register native skill commands (user-invocable skills) with channels that support it.",
|
||||||
"commands.text": "Allow text command parsing (slash commands only).",
|
"commands.text": "Allow text command parsing (slash commands only).",
|
||||||
"commands.bash":
|
"commands.bash":
|
||||||
"Allow bash chat command (`!`; `/bash` alias) to run host shell commands (default: false; requires tools.elevated).",
|
"Allow bash chat command (`!`; `/bash` alias) to run host shell commands (default: false; requires tools.elevated).",
|
||||||
@@ -349,8 +352,14 @@ const FIELD_HELP: Record<string, string> = {
|
|||||||
"channels.msteams.configWrites":
|
"channels.msteams.configWrites":
|
||||||
"Allow Microsoft Teams to write config in response to channel events/commands (default: true).",
|
"Allow Microsoft Teams to write config in response to channel events/commands (default: true).",
|
||||||
"channels.discord.commands.native": 'Override native commands for Discord (bool or "auto").',
|
"channels.discord.commands.native": 'Override native commands for Discord (bool or "auto").',
|
||||||
|
"channels.discord.commands.nativeSkills":
|
||||||
|
'Override native skill commands for Discord (bool or "auto").',
|
||||||
"channels.telegram.commands.native": 'Override native commands for Telegram (bool or "auto").',
|
"channels.telegram.commands.native": 'Override native commands for Telegram (bool or "auto").',
|
||||||
|
"channels.telegram.commands.nativeSkills":
|
||||||
|
'Override native skill commands for Telegram (bool or "auto").',
|
||||||
"channels.slack.commands.native": 'Override native commands for Slack (bool or "auto").',
|
"channels.slack.commands.native": 'Override native commands for Slack (bool or "auto").',
|
||||||
|
"channels.slack.commands.nativeSkills":
|
||||||
|
'Override native skill commands for Slack (bool or "auto").',
|
||||||
"session.agentToAgent.maxPingPongTurns":
|
"session.agentToAgent.maxPingPongTurns":
|
||||||
"Max reply-back turns between requester and target (0–5).",
|
"Max reply-back turns between requester and target (0–5).",
|
||||||
"channels.telegram.customCommands":
|
"channels.telegram.customCommands":
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ export type NativeCommandsSetting = boolean | "auto";
|
|||||||
export type CommandsConfig = {
|
export type CommandsConfig = {
|
||||||
/** Enable native command registration when supported (default: "auto"). */
|
/** Enable native command registration when supported (default: "auto"). */
|
||||||
native?: NativeCommandsSetting;
|
native?: NativeCommandsSetting;
|
||||||
|
/** Enable native skill command registration when supported (default: "auto"). */
|
||||||
|
nativeSkills?: NativeCommandsSetting;
|
||||||
/** Enable text command parsing (default: true). */
|
/** Enable text command parsing (default: true). */
|
||||||
text?: boolean;
|
text?: boolean;
|
||||||
/** Allow bash chat command (`!`; `/bash` alias) (default: false). */
|
/** Allow bash chat command (`!`; `/bash` alias) (default: false). */
|
||||||
@@ -114,4 +116,6 @@ export type CommandsConfig = {
|
|||||||
export type ProviderCommandsConfig = {
|
export type ProviderCommandsConfig = {
|
||||||
/** Override native command registration for this provider (bool or "auto"). */
|
/** Override native command registration for this provider (bool or "auto"). */
|
||||||
native?: NativeCommandsSetting;
|
native?: NativeCommandsSetting;
|
||||||
|
/** Override native skill command registration for this provider (bool or "auto"). */
|
||||||
|
nativeSkills?: NativeCommandsSetting;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -252,5 +252,6 @@ export const NativeCommandsSettingSchema = z.union([z.boolean(), z.literal("auto
|
|||||||
export const ProviderCommandsSchema = z
|
export const ProviderCommandsSchema = z
|
||||||
.object({
|
.object({
|
||||||
native: NativeCommandsSettingSchema.optional(),
|
native: NativeCommandsSettingSchema.optional(),
|
||||||
|
nativeSkills: NativeCommandsSettingSchema.optional(),
|
||||||
})
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export const MessagesSchema = z
|
|||||||
export const CommandsSchema = z
|
export const CommandsSchema = z
|
||||||
.object({
|
.object({
|
||||||
native: NativeCommandsSettingSchema.optional().default("auto"),
|
native: NativeCommandsSettingSchema.optional().default("auto"),
|
||||||
|
nativeSkills: NativeCommandsSettingSchema.optional().default("auto"),
|
||||||
text: z.boolean().optional(),
|
text: z.boolean().optional(),
|
||||||
bash: z.boolean().optional(),
|
bash: z.boolean().optional(),
|
||||||
bashForegroundMs: z.number().int().min(0).max(30_000).optional(),
|
bashForegroundMs: z.number().int().min(0).max(30_000).optional(),
|
||||||
@@ -81,4 +82,4 @@ export const CommandsSchema = z
|
|||||||
useAccessGroups: z.boolean().optional(),
|
useAccessGroups: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({ native: "auto" });
|
.default({ native: "auto", nativeSkills: "auto" });
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
|
|||||||
import { Routes } from "discord-api-types/v10";
|
import { Routes } from "discord-api-types/v10";
|
||||||
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||||
import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js";
|
import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js";
|
||||||
import { listSkillCommandsForWorkspace } from "../../auto-reply/skill-commands.js";
|
import { listSkillCommandsForAgents } from "../../auto-reply/skill-commands.js";
|
||||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
|
||||||
import type { HistoryEntry } from "../../auto-reply/reply/history.js";
|
import type { HistoryEntry } from "../../auto-reply/reply/history.js";
|
||||||
import {
|
import {
|
||||||
isNativeCommandsExplicitlyDisabled,
|
isNativeCommandsExplicitlyDisabled,
|
||||||
resolveNativeCommandsEnabled,
|
resolveNativeCommandsEnabled,
|
||||||
|
resolveNativeSkillsEnabled,
|
||||||
} from "../../config/commands.js";
|
} from "../../config/commands.js";
|
||||||
import type { ClawdbotConfig, ReplyToMode } from "../../config/config.js";
|
import type { ClawdbotConfig, ReplyToMode } from "../../config/config.js";
|
||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
@@ -99,6 +99,11 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
providerSetting: discordCfg.commands?.native,
|
providerSetting: discordCfg.commands?.native,
|
||||||
globalSetting: cfg.commands?.native,
|
globalSetting: cfg.commands?.native,
|
||||||
});
|
});
|
||||||
|
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
||||||
|
providerId: "discord",
|
||||||
|
providerSetting: discordCfg.commands?.nativeSkills,
|
||||||
|
globalSetting: cfg.commands?.nativeSkills,
|
||||||
|
});
|
||||||
const nativeDisabledExplicit = isNativeCommandsExplicitlyDisabled({
|
const nativeDisabledExplicit = isNativeCommandsExplicitlyDisabled({
|
||||||
providerSetting: discordCfg.commands?.native,
|
providerSetting: discordCfg.commands?.native,
|
||||||
globalSetting: cfg.commands?.native,
|
globalSetting: cfg.commands?.native,
|
||||||
@@ -109,7 +114,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
|
|
||||||
if (shouldLogVerbose()) {
|
if (shouldLogVerbose()) {
|
||||||
logVerbose(
|
logVerbose(
|
||||||
`discord: config dm=${dmEnabled ? "on" : "off"} dmPolicy=${dmPolicy} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} groupPolicy=${groupPolicy} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))} native=${nativeEnabled ? "on" : "off"} accessGroups=${useAccessGroups ? "on" : "off"}`,
|
`discord: config dm=${dmEnabled ? "on" : "off"} dmPolicy=${dmPolicy} allowFrom=${summarizeAllowList(allowFrom)} groupDm=${groupDmEnabled ? "on" : "off"} groupDmChannels=${summarizeAllowList(groupDmChannels)} groupPolicy=${groupPolicy} guilds=${summarizeGuilds(guildEntries)} historyLimit=${historyLimit} mediaMaxMb=${Math.round(mediaMaxBytes / (1024 * 1024))} native=${nativeEnabled ? "on" : "off"} nativeSkills=${nativeSkillsEnabled ? "on" : "off"} accessGroups=${useAccessGroups ? "on" : "off"}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +123,8 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
throw new Error("Failed to resolve Discord application id");
|
throw new Error("Failed to resolve Discord application id");
|
||||||
}
|
}
|
||||||
|
|
||||||
const skillCommands = nativeEnabled
|
const skillCommands =
|
||||||
? listSkillCommandsForWorkspace({
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
workspaceDir: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)),
|
|
||||||
cfg,
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
const commandSpecs = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands }) : [];
|
const commandSpecs = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands }) : [];
|
||||||
const commands = commandSpecs.map((spec) =>
|
const commands = commandSpecs.map((spec) =>
|
||||||
createDiscordNativeCommand({
|
createDiscordNativeCommand({
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import {
|
|||||||
parseCommandArgs,
|
parseCommandArgs,
|
||||||
resolveCommandArgMenu,
|
resolveCommandArgMenu,
|
||||||
} from "../../auto-reply/commands-registry.js";
|
} from "../../auto-reply/commands-registry.js";
|
||||||
import { listSkillCommandsForWorkspace } from "../../auto-reply/skill-commands.js";
|
import { listSkillCommandsForAgents } from "../../auto-reply/skill-commands.js";
|
||||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
|
||||||
import { dispatchReplyWithDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
import { dispatchReplyWithDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
||||||
import { resolveNativeCommandsEnabled } from "../../config/commands.js";
|
import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "../../config/commands.js";
|
||||||
import { danger, logVerbose } from "../../globals.js";
|
import { danger, logVerbose } from "../../globals.js";
|
||||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||||
import {
|
import {
|
||||||
@@ -405,12 +404,13 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
providerSetting: account.config.commands?.native,
|
providerSetting: account.config.commands?.native,
|
||||||
globalSetting: cfg.commands?.native,
|
globalSetting: cfg.commands?.native,
|
||||||
});
|
});
|
||||||
const skillCommands = nativeEnabled
|
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
||||||
? listSkillCommandsForWorkspace({
|
providerId: "slack",
|
||||||
workspaceDir: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)),
|
providerSetting: account.config.commands?.nativeSkills,
|
||||||
cfg,
|
globalSetting: cfg.commands?.nativeSkills,
|
||||||
})
|
});
|
||||||
: [];
|
const skillCommands =
|
||||||
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
const nativeCommands = nativeEnabled
|
const nativeCommands = nativeEnabled
|
||||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import {
|
|||||||
parseCommandArgs,
|
parseCommandArgs,
|
||||||
resolveCommandArgMenu,
|
resolveCommandArgMenu,
|
||||||
} from "../auto-reply/commands-registry.js";
|
} from "../auto-reply/commands-registry.js";
|
||||||
import { listSkillCommandsForWorkspace } from "../auto-reply/skill-commands.js";
|
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
||||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
||||||
import type { CommandArgs } from "../auto-reply/commands-registry.js";
|
import type { CommandArgs } from "../auto-reply/commands-registry.js";
|
||||||
import { resolveTelegramCustomCommands } from "../config/telegram-custom-commands.js";
|
import { resolveTelegramCustomCommands } from "../config/telegram-custom-commands.js";
|
||||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
|
||||||
@@ -39,18 +38,15 @@ export const registerTelegramNativeCommands = ({
|
|||||||
textLimit,
|
textLimit,
|
||||||
useAccessGroups,
|
useAccessGroups,
|
||||||
nativeEnabled,
|
nativeEnabled,
|
||||||
|
nativeSkillsEnabled,
|
||||||
nativeDisabledExplicit,
|
nativeDisabledExplicit,
|
||||||
resolveGroupPolicy,
|
resolveGroupPolicy,
|
||||||
resolveTelegramGroupConfig,
|
resolveTelegramGroupConfig,
|
||||||
shouldSkipUpdate,
|
shouldSkipUpdate,
|
||||||
opts,
|
opts,
|
||||||
}) => {
|
}) => {
|
||||||
const skillCommands = nativeEnabled
|
const skillCommands =
|
||||||
? listSkillCommandsForWorkspace({
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
workspaceDir: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)),
|
|
||||||
cfg,
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
const nativeCommands = nativeEnabled
|
const nativeCommands = nativeEnabled
|
||||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import {
|
|||||||
listNativeCommandSpecs,
|
listNativeCommandSpecs,
|
||||||
listNativeCommandSpecsForConfig,
|
listNativeCommandSpecsForConfig,
|
||||||
} from "../auto-reply/commands-registry.js";
|
} from "../auto-reply/commands-registry.js";
|
||||||
import { listSkillCommandsForWorkspace } from "../auto-reply/skill-commands.js";
|
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
||||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
||||||
import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js";
|
import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js";
|
||||||
import * as replyModule from "../auto-reply/reply.js";
|
import * as replyModule from "../auto-reply/reply.js";
|
||||||
import { createTelegramBot, getTelegramSequentialKey } from "./bot.js";
|
import { createTelegramBot, getTelegramSequentialKey } from "./bot.js";
|
||||||
import { resolveTelegramFetch } from "./fetch.js";
|
import { resolveTelegramFetch } from "./fetch.js";
|
||||||
|
|
||||||
function resolveSkillCommands(config: Parameters<typeof listNativeCommandSpecsForConfig>[0]) {
|
function resolveSkillCommands(config: Parameters<typeof listNativeCommandSpecsForConfig>[0]) {
|
||||||
return listSkillCommandsForWorkspace({
|
return listSkillCommandsForAgents({ cfg: config });
|
||||||
workspaceDir: resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)),
|
|
||||||
cfg: config,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { loadWebMedia } = vi.hoisted(() => ({
|
const { loadWebMedia } = vi.hoisted(() => ({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "../auto-reply/re
|
|||||||
import {
|
import {
|
||||||
isNativeCommandsExplicitlyDisabled,
|
isNativeCommandsExplicitlyDisabled,
|
||||||
resolveNativeCommandsEnabled,
|
resolveNativeCommandsEnabled,
|
||||||
|
resolveNativeSkillsEnabled,
|
||||||
} from "../config/commands.js";
|
} from "../config/commands.js";
|
||||||
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
|
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
@@ -190,6 +191,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
providerSetting: telegramCfg.commands?.native,
|
providerSetting: telegramCfg.commands?.native,
|
||||||
globalSetting: cfg.commands?.native,
|
globalSetting: cfg.commands?.native,
|
||||||
});
|
});
|
||||||
|
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
||||||
|
providerId: "telegram",
|
||||||
|
providerSetting: telegramCfg.commands?.nativeSkills,
|
||||||
|
globalSetting: cfg.commands?.nativeSkills,
|
||||||
|
});
|
||||||
const nativeDisabledExplicit = isNativeCommandsExplicitlyDisabled({
|
const nativeDisabledExplicit = isNativeCommandsExplicitlyDisabled({
|
||||||
providerSetting: telegramCfg.commands?.native,
|
providerSetting: telegramCfg.commands?.native,
|
||||||
globalSetting: cfg.commands?.native,
|
globalSetting: cfg.commands?.native,
|
||||||
@@ -297,6 +303,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
textLimit,
|
textLimit,
|
||||||
useAccessGroups,
|
useAccessGroups,
|
||||||
nativeEnabled,
|
nativeEnabled,
|
||||||
|
nativeSkillsEnabled,
|
||||||
nativeDisabledExplicit,
|
nativeDisabledExplicit,
|
||||||
resolveGroupPolicy,
|
resolveGroupPolicy,
|
||||||
resolveTelegramGroupConfig,
|
resolveTelegramGroupConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user