mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 00:01:24 +00:00
Gateway/UI: data-driven agents tools catalog with provenance (openclaw#24199) thanks @Takhoffman
Verified: - pnpm install --frozen-lockfile - pnpm build - gh pr checks 24199 --watch --fail-fast Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
322
src/agents/tool-catalog.ts
Normal file
322
src/agents/tool-catalog.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
export type ToolProfileId = "minimal" | "coding" | "messaging" | "full";
|
||||
|
||||
type ToolProfilePolicy = {
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
};
|
||||
|
||||
export type CoreToolSection = {
|
||||
id: string;
|
||||
label: string;
|
||||
tools: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type CoreToolDefinition = {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
sectionId: string;
|
||||
profiles: ToolProfileId[];
|
||||
includeInOpenClawGroup?: boolean;
|
||||
};
|
||||
|
||||
const CORE_TOOL_SECTION_ORDER: Array<{ id: string; label: string }> = [
|
||||
{ id: "fs", label: "Files" },
|
||||
{ id: "runtime", label: "Runtime" },
|
||||
{ id: "web", label: "Web" },
|
||||
{ id: "memory", label: "Memory" },
|
||||
{ id: "sessions", label: "Sessions" },
|
||||
{ id: "ui", label: "UI" },
|
||||
{ id: "messaging", label: "Messaging" },
|
||||
{ id: "automation", label: "Automation" },
|
||||
{ id: "nodes", label: "Nodes" },
|
||||
{ id: "agents", label: "Agents" },
|
||||
{ id: "media", label: "Media" },
|
||||
];
|
||||
|
||||
const CORE_TOOL_DEFINITIONS: CoreToolDefinition[] = [
|
||||
{
|
||||
id: "read",
|
||||
label: "read",
|
||||
description: "Read file contents",
|
||||
sectionId: "fs",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "write",
|
||||
label: "write",
|
||||
description: "Create or overwrite files",
|
||||
sectionId: "fs",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "edit",
|
||||
label: "edit",
|
||||
description: "Make precise edits",
|
||||
sectionId: "fs",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "apply_patch",
|
||||
label: "apply_patch",
|
||||
description: "Patch files (OpenAI)",
|
||||
sectionId: "fs",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "exec",
|
||||
label: "exec",
|
||||
description: "Run shell commands",
|
||||
sectionId: "runtime",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "process",
|
||||
label: "process",
|
||||
description: "Manage background processes",
|
||||
sectionId: "runtime",
|
||||
profiles: ["coding"],
|
||||
},
|
||||
{
|
||||
id: "web_search",
|
||||
label: "web_search",
|
||||
description: "Search the web",
|
||||
sectionId: "web",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "web_fetch",
|
||||
label: "web_fetch",
|
||||
description: "Fetch web content",
|
||||
sectionId: "web",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "memory_search",
|
||||
label: "memory_search",
|
||||
description: "Semantic search",
|
||||
sectionId: "memory",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "memory_get",
|
||||
label: "memory_get",
|
||||
description: "Read memory files",
|
||||
sectionId: "memory",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "sessions_list",
|
||||
label: "sessions_list",
|
||||
description: "List sessions",
|
||||
sectionId: "sessions",
|
||||
profiles: ["coding", "messaging"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "sessions_history",
|
||||
label: "sessions_history",
|
||||
description: "Session history",
|
||||
sectionId: "sessions",
|
||||
profiles: ["coding", "messaging"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "sessions_send",
|
||||
label: "sessions_send",
|
||||
description: "Send to session",
|
||||
sectionId: "sessions",
|
||||
profiles: ["coding", "messaging"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "sessions_spawn",
|
||||
label: "sessions_spawn",
|
||||
description: "Spawn sub-agent",
|
||||
sectionId: "sessions",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "subagents",
|
||||
label: "subagents",
|
||||
description: "Manage sub-agents",
|
||||
sectionId: "sessions",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "session_status",
|
||||
label: "session_status",
|
||||
description: "Session status",
|
||||
sectionId: "sessions",
|
||||
profiles: ["minimal", "coding", "messaging"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "browser",
|
||||
label: "browser",
|
||||
description: "Control web browser",
|
||||
sectionId: "ui",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "canvas",
|
||||
label: "canvas",
|
||||
description: "Control canvases",
|
||||
sectionId: "ui",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "message",
|
||||
label: "message",
|
||||
description: "Send messages",
|
||||
sectionId: "messaging",
|
||||
profiles: ["messaging"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "cron",
|
||||
label: "cron",
|
||||
description: "Schedule tasks",
|
||||
sectionId: "automation",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "gateway",
|
||||
label: "gateway",
|
||||
description: "Gateway control",
|
||||
sectionId: "automation",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "nodes",
|
||||
label: "nodes",
|
||||
description: "Nodes + devices",
|
||||
sectionId: "nodes",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "agents_list",
|
||||
label: "agents_list",
|
||||
description: "List agents",
|
||||
sectionId: "agents",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "image",
|
||||
label: "image",
|
||||
description: "Image understanding",
|
||||
sectionId: "media",
|
||||
profiles: ["coding"],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
{
|
||||
id: "tts",
|
||||
label: "tts",
|
||||
description: "Text-to-speech conversion",
|
||||
sectionId: "media",
|
||||
profiles: [],
|
||||
includeInOpenClawGroup: true,
|
||||
},
|
||||
];
|
||||
|
||||
const CORE_TOOL_BY_ID = new Map<string, CoreToolDefinition>(
|
||||
CORE_TOOL_DEFINITIONS.map((tool) => [tool.id, tool]),
|
||||
);
|
||||
|
||||
function listCoreToolIdsForProfile(profile: ToolProfileId): string[] {
|
||||
return CORE_TOOL_DEFINITIONS.filter((tool) => tool.profiles.includes(profile)).map(
|
||||
(tool) => tool.id,
|
||||
);
|
||||
}
|
||||
|
||||
const CORE_TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
|
||||
minimal: {
|
||||
allow: listCoreToolIdsForProfile("minimal"),
|
||||
},
|
||||
coding: {
|
||||
allow: listCoreToolIdsForProfile("coding"),
|
||||
},
|
||||
messaging: {
|
||||
allow: listCoreToolIdsForProfile("messaging"),
|
||||
},
|
||||
full: {},
|
||||
};
|
||||
|
||||
function buildCoreToolGroupMap() {
|
||||
const sectionToolMap = new Map<string, string[]>();
|
||||
for (const tool of CORE_TOOL_DEFINITIONS) {
|
||||
const groupId = `group:${tool.sectionId}`;
|
||||
const list = sectionToolMap.get(groupId) ?? [];
|
||||
list.push(tool.id);
|
||||
sectionToolMap.set(groupId, list);
|
||||
}
|
||||
const openclawTools = CORE_TOOL_DEFINITIONS.filter((tool) => tool.includeInOpenClawGroup).map(
|
||||
(tool) => tool.id,
|
||||
);
|
||||
return {
|
||||
"group:openclaw": openclawTools,
|
||||
...Object.fromEntries(sectionToolMap.entries()),
|
||||
};
|
||||
}
|
||||
|
||||
export const CORE_TOOL_GROUPS = buildCoreToolGroupMap();
|
||||
|
||||
export const PROFILE_OPTIONS = [
|
||||
{ id: "minimal", label: "Minimal" },
|
||||
{ id: "coding", label: "Coding" },
|
||||
{ id: "messaging", label: "Messaging" },
|
||||
{ id: "full", label: "Full" },
|
||||
] as const;
|
||||
|
||||
export function resolveCoreToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined {
|
||||
if (!profile) {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = CORE_TOOL_PROFILES[profile as ToolProfileId];
|
||||
if (!resolved) {
|
||||
return undefined;
|
||||
}
|
||||
if (!resolved.allow && !resolved.deny) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
allow: resolved.allow ? [...resolved.allow] : undefined,
|
||||
deny: resolved.deny ? [...resolved.deny] : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function listCoreToolSections(): CoreToolSection[] {
|
||||
return CORE_TOOL_SECTION_ORDER.map((section) => ({
|
||||
id: section.id,
|
||||
label: section.label,
|
||||
tools: CORE_TOOL_DEFINITIONS.filter((tool) => tool.sectionId === section.id).map((tool) => ({
|
||||
id: tool.id,
|
||||
label: tool.label,
|
||||
description: tool.description,
|
||||
})),
|
||||
})).filter((section) => section.tools.length > 0);
|
||||
}
|
||||
|
||||
export function resolveCoreToolProfiles(toolId: string): ToolProfileId[] {
|
||||
const tool = CORE_TOOL_BY_ID.get(toolId);
|
||||
if (!tool) {
|
||||
return [];
|
||||
}
|
||||
return [...tool.profiles];
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
export type ToolProfileId = "minimal" | "coding" | "messaging" | "full";
|
||||
import {
|
||||
CORE_TOOL_GROUPS,
|
||||
resolveCoreToolProfilePolicy,
|
||||
type ToolProfileId,
|
||||
} from "./tool-catalog.js";
|
||||
|
||||
type ToolProfilePolicy = {
|
||||
allow?: string[];
|
||||
@@ -10,72 +14,7 @@ const TOOL_NAME_ALIASES: Record<string, string> = {
|
||||
"apply-patch": "apply_patch",
|
||||
};
|
||||
|
||||
export const TOOL_GROUPS: Record<string, string[]> = {
|
||||
// NOTE: Keep canonical (lowercase) tool names here.
|
||||
"group:memory": ["memory_search", "memory_get"],
|
||||
"group:web": ["web_search", "web_fetch"],
|
||||
// Basic workspace/file tools
|
||||
"group:fs": ["read", "write", "edit", "apply_patch"],
|
||||
// Host/runtime execution tools
|
||||
"group:runtime": ["exec", "process"],
|
||||
// Session management tools
|
||||
"group:sessions": [
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"sessions_spawn",
|
||||
"subagents",
|
||||
"session_status",
|
||||
],
|
||||
// UI helpers
|
||||
"group:ui": ["browser", "canvas"],
|
||||
// Automation + infra
|
||||
"group:automation": ["cron", "gateway"],
|
||||
// Messaging surface
|
||||
"group:messaging": ["message"],
|
||||
// Nodes + device tools
|
||||
"group:nodes": ["nodes"],
|
||||
// All OpenClaw native tools (excludes provider plugins).
|
||||
"group:openclaw": [
|
||||
"browser",
|
||||
"canvas",
|
||||
"nodes",
|
||||
"cron",
|
||||
"message",
|
||||
"gateway",
|
||||
"agents_list",
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"sessions_spawn",
|
||||
"subagents",
|
||||
"session_status",
|
||||
"memory_search",
|
||||
"memory_get",
|
||||
"web_search",
|
||||
"web_fetch",
|
||||
"image",
|
||||
],
|
||||
};
|
||||
|
||||
const TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
|
||||
minimal: {
|
||||
allow: ["session_status"],
|
||||
},
|
||||
coding: {
|
||||
allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"],
|
||||
},
|
||||
messaging: {
|
||||
allow: [
|
||||
"group:messaging",
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"session_status",
|
||||
],
|
||||
},
|
||||
full: {},
|
||||
};
|
||||
export const TOOL_GROUPS: Record<string, string[]> = { ...CORE_TOOL_GROUPS };
|
||||
|
||||
export function normalizeToolName(name: string) {
|
||||
const normalized = name.trim().toLowerCase();
|
||||
@@ -104,18 +43,7 @@ export function expandToolGroups(list?: string[]) {
|
||||
}
|
||||
|
||||
export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined {
|
||||
if (!profile) {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = TOOL_PROFILES[profile as ToolProfileId];
|
||||
if (!resolved) {
|
||||
return undefined;
|
||||
}
|
||||
if (!resolved.allow && !resolved.deny) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
allow: resolved.allow ? [...resolved.allow] : undefined,
|
||||
deny: resolved.deny ? [...resolved.deny] : undefined,
|
||||
};
|
||||
return resolveCoreToolProfilePolicy(profile);
|
||||
}
|
||||
|
||||
export type { ToolProfileId };
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("tool-policy", () => {
|
||||
|
||||
it("resolves known profiles and ignores unknown ones", () => {
|
||||
const coding = resolveToolProfilePolicy("coding");
|
||||
expect(coding?.allow).toContain("group:fs");
|
||||
expect(coding?.allow).toContain("read");
|
||||
expect(resolveToolProfilePolicy("nope")).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -65,6 +65,7 @@ describe("tool-policy", () => {
|
||||
expect(group).toContain("message");
|
||||
expect(group).toContain("subagents");
|
||||
expect(group).toContain("session_status");
|
||||
expect(group).toContain("tts");
|
||||
});
|
||||
|
||||
it("normalizes tool names and aliases", () => {
|
||||
|
||||
Reference in New Issue
Block a user