mirror of
https://github.com/ValueCell-ai/ClawX.git
synced 2026-08-14 08:53:09 +00:00
Revert "feat(auth): deny subagent tools for ClawX desktop" (#1164)
This commit is contained in:
@@ -2862,13 +2862,6 @@ export async function updateSingleAgentModelProvider(
|
||||
const SKILL_WORKSHOP_TOOL_DENY_ENTRY = 'skill_workshop';
|
||||
const SKILL_CREATOR_SKILL_KEY = 'skill-creator';
|
||||
|
||||
// ClawX desktop does not expose the subagent workflow. Deny the spawn entry
|
||||
// point and its companion tools so the model cannot spawn child agent
|
||||
// sessions (runtime="subagent") even under tools.profile="full".
|
||||
// `sessions_spawn` is the only tool that creates a subagent; `sessions_yield`
|
||||
// and `subagents` are the receive/list companions and are useless without it.
|
||||
const SUBAGENT_TOOL_DENY_ENTRIES = ['sessions_spawn', 'sessions_yield', 'subagents'] as const;
|
||||
|
||||
function normalizeToolDenyList(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === 'string')
|
||||
@@ -2885,22 +2878,6 @@ function ensureToolDenyIncludes(
|
||||
return { deny: [...deny, entry], modified: true };
|
||||
}
|
||||
|
||||
function ensureToolDenyIncludesAll(
|
||||
deny: string[],
|
||||
entries: readonly string[],
|
||||
): { deny: string[]; modified: boolean } {
|
||||
let current = deny;
|
||||
let modified = false;
|
||||
for (const entry of entries) {
|
||||
const result = ensureToolDenyIncludes(current, entry);
|
||||
if (result.modified) {
|
||||
current = result.deny;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return { deny: current, modified };
|
||||
}
|
||||
|
||||
export async function sanitizeOpenClawConfig(): Promise<void> {
|
||||
return withConfigLock(async () => {
|
||||
// Skip sanitization if the config file does not exist yet.
|
||||
@@ -3100,22 +3077,6 @@ export async function sanitizeOpenClawConfig(): Promise<void> {
|
||||
toolsModified = true;
|
||||
}
|
||||
|
||||
// ClawX desktop does not expose the subagent workflow. Deny the spawn
|
||||
// entry point and its companions even under tools.profile="full" so the
|
||||
// model cannot spawn child agent sessions (runtime="subagent").
|
||||
const subagentDenyResult = ensureToolDenyIncludesAll(
|
||||
normalizeToolDenyList(toolsConfig.deny),
|
||||
SUBAGENT_TOOL_DENY_ENTRIES,
|
||||
);
|
||||
if (subagentDenyResult.modified) {
|
||||
toolsConfig.deny = subagentDenyResult.deny;
|
||||
toolsModified = true;
|
||||
console.log(`[sanitize] Added subagent tool(s) to tools.deny for ClawX desktop: ${SUBAGENT_TOOL_DENY_ENTRIES.join(', ')}`);
|
||||
} else if (!Array.isArray(toolsConfig.deny) || toolsConfig.deny.length !== subagentDenyResult.deny.length) {
|
||||
toolsConfig.deny = subagentDenyResult.deny;
|
||||
toolsModified = true;
|
||||
}
|
||||
|
||||
// ── tools.exec approvals (OpenClaw 3.28+) ──────────────────────
|
||||
// ClawX is a local desktop app where the user is the trusted operator.
|
||||
// Exec approval prompts add unnecessary friction in this context, so we
|
||||
@@ -3177,21 +3138,6 @@ export async function sanitizeOpenClawConfig(): Promise<void> {
|
||||
gatewayTools.deny = gatewayDenyResult.deny;
|
||||
gatewayModified = true;
|
||||
}
|
||||
|
||||
// Mirror the subagent tool deny into gateway.tools.deny so gateway-side
|
||||
// sessions cannot spawn subagents either.
|
||||
const gatewaySubagentDenyResult = ensureToolDenyIncludesAll(
|
||||
normalizeToolDenyList(gatewayTools.deny),
|
||||
SUBAGENT_TOOL_DENY_ENTRIES,
|
||||
);
|
||||
if (gatewaySubagentDenyResult.modified) {
|
||||
gatewayTools.deny = gatewaySubagentDenyResult.deny;
|
||||
gatewayModified = true;
|
||||
console.log(`[sanitize] Added subagent tool(s) to gateway.tools.deny for ClawX desktop: ${SUBAGENT_TOOL_DENY_ENTRIES.join(', ')}`);
|
||||
} else if (!Array.isArray(gatewayTools.deny) || gatewayTools.deny.length !== gatewaySubagentDenyResult.deny.length) {
|
||||
gatewayTools.deny = gatewaySubagentDenyResult.deny;
|
||||
gatewayModified = true;
|
||||
}
|
||||
if (gatewayModified) {
|
||||
gateway.tools = gatewayTools;
|
||||
config.gateway = gateway;
|
||||
|
||||
@@ -47,17 +47,6 @@ vi.mock('@electron/utils/paths', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
// ClawX desktop denies these tools in tools.deny / gateway.tools.deny during
|
||||
// sanitizeOpenClawConfig(): skill_workshop (ClawX keeps direct skill-creator
|
||||
// authoring) plus the subagent workflow (sessions_spawn / sessions_yield /
|
||||
// subagents) so the model cannot spawn child agent sessions.
|
||||
const CLAWX_DESKTOP_TOOL_DENY = [
|
||||
'skill_workshop',
|
||||
'sessions_spawn',
|
||||
'sessions_yield',
|
||||
'subagents',
|
||||
];
|
||||
|
||||
async function writeOpenClawJson(config: unknown): Promise<void> {
|
||||
const openclawDir = join(testHome, '.openclaw');
|
||||
await mkdir(openclawDir, { recursive: true });
|
||||
@@ -390,10 +379,10 @@ describe('sanitizeOpenClawConfig', () => {
|
||||
// Fresh install should get tools settings enforced
|
||||
const tools = result.tools as Record<string, unknown>;
|
||||
expect(tools.profile).toBe('full');
|
||||
expect(tools.deny).toEqual(CLAWX_DESKTOP_TOOL_DENY);
|
||||
expect(tools.deny).toEqual(['skill_workshop']);
|
||||
const gateway = result.gateway as Record<string, unknown>;
|
||||
const gatewayTools = gateway.tools as Record<string, unknown>;
|
||||
expect(gatewayTools.deny).toEqual(CLAWX_DESKTOP_TOOL_DENY);
|
||||
expect(gatewayTools.deny).toEqual(['skill_workshop']);
|
||||
const skills = result.skills as Record<string, unknown>;
|
||||
const workshop = skills.workshop as Record<string, unknown>;
|
||||
const autonomous = workshop.autonomous as Record<string, unknown>;
|
||||
@@ -428,9 +417,9 @@ describe('sanitizeOpenClawConfig', () => {
|
||||
// tools settings should now be enforced
|
||||
const tools = result.tools as Record<string, unknown>;
|
||||
expect(tools.profile).toBe('full');
|
||||
expect(tools.deny).toEqual(CLAWX_DESKTOP_TOOL_DENY);
|
||||
expect(tools.deny).toEqual(['skill_workshop']);
|
||||
const gateway = result.gateway as Record<string, unknown>;
|
||||
expect((gateway.tools as Record<string, unknown>).deny).toEqual(CLAWX_DESKTOP_TOOL_DENY);
|
||||
expect((gateway.tools as Record<string, unknown>).deny).toEqual(['skill_workshop']);
|
||||
const skills = result.skills as Record<string, unknown>;
|
||||
expect(((skills.workshop as Record<string, unknown>).autonomous as Record<string, unknown>).enabled).toBe(false);
|
||||
expect((skills.entries as Record<string, Record<string, unknown>>)['skill-creator'].enabled).toBe(true);
|
||||
@@ -450,9 +439,9 @@ describe('sanitizeOpenClawConfig', () => {
|
||||
|
||||
const result = await readOpenClawJson();
|
||||
const tools = result.tools as Record<string, unknown>;
|
||||
expect(tools.deny).toEqual(['browser', ...CLAWX_DESKTOP_TOOL_DENY]);
|
||||
expect(tools.deny).toEqual(['browser', 'skill_workshop']);
|
||||
const gateway = result.gateway as Record<string, unknown>;
|
||||
expect((gateway.tools as Record<string, unknown>).deny).toEqual(CLAWX_DESKTOP_TOOL_DENY);
|
||||
expect((gateway.tools as Record<string, unknown>).deny).toEqual(['skill_workshop']);
|
||||
});
|
||||
|
||||
it('migrates legacy tools.web.search.kimi into moonshot plugin config', async () => {
|
||||
|
||||
@@ -51,12 +51,6 @@ function withClawXToolDefaults<T extends Record<string, unknown>>(config: T): T
|
||||
tools.sessions = sessions;
|
||||
tools.exec = exec;
|
||||
tools.deny = deny.includes('skill_workshop') ? deny : [...deny, 'skill_workshop'];
|
||||
// Mirror production: also deny the subagent workflow tools.
|
||||
for (const entry of ['sessions_spawn', 'sessions_yield', 'subagents']) {
|
||||
if (!(tools.deny as string[]).includes(entry)) {
|
||||
(tools.deny as string[]).push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
const gateway = (config.gateway && typeof config.gateway === 'object' && !Array.isArray(config.gateway))
|
||||
? { ...(config.gateway as Record<string, unknown>) }
|
||||
@@ -68,12 +62,6 @@ function withClawXToolDefaults<T extends Record<string, unknown>>(config: T): T
|
||||
? (gatewayTools.deny as unknown[]).filter((value): value is string => typeof value === 'string')
|
||||
: [];
|
||||
gatewayTools.deny = gatewayDeny.includes('skill_workshop') ? gatewayDeny : [...gatewayDeny, 'skill_workshop'];
|
||||
// Mirror production: also deny the subagent workflow tools (gateway side).
|
||||
for (const entry of ['sessions_spawn', 'sessions_yield', 'subagents']) {
|
||||
if (!(gatewayTools.deny as string[]).includes(entry)) {
|
||||
(gatewayTools.deny as string[]).push(entry);
|
||||
}
|
||||
}
|
||||
gateway.tools = gatewayTools;
|
||||
|
||||
const skills = (config.skills && typeof config.skills === 'object' && !Array.isArray(config.skills))
|
||||
@@ -426,27 +414,6 @@ async function sanitizeConfig(
|
||||
toolsModified = true;
|
||||
}
|
||||
|
||||
// Mirror production: deny the subagent workflow tools (sessions_spawn /
|
||||
// sessions_yield / subagents) so the model cannot spawn child agent sessions.
|
||||
const subagentEntries = ['sessions_spawn', 'sessions_yield', 'subagents'];
|
||||
let subagentDeny = Array.isArray(toolsConfig.deny)
|
||||
? toolsConfig.deny.filter((value): value is string => typeof value === 'string')
|
||||
: [];
|
||||
let subagentDenyChanged = false;
|
||||
for (const entry of subagentEntries) {
|
||||
if (!subagentDeny.includes(entry)) {
|
||||
subagentDeny = [...subagentDeny, entry];
|
||||
subagentDenyChanged = true;
|
||||
}
|
||||
}
|
||||
if (subagentDenyChanged) {
|
||||
toolsConfig.deny = subagentDeny;
|
||||
toolsModified = true;
|
||||
} else if (!Array.isArray(toolsConfig.deny) || toolsConfig.deny.length !== subagentDeny.length) {
|
||||
toolsConfig.deny = subagentDeny;
|
||||
toolsModified = true;
|
||||
}
|
||||
|
||||
const execConfig = (toolsConfig.exec as Record<string, unknown> | undefined) || {};
|
||||
if (execConfig.security !== 'full' || execConfig.ask !== 'off') {
|
||||
execConfig.security = 'full';
|
||||
@@ -492,29 +459,6 @@ async function sanitizeConfig(
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Mirror production: deny the subagent workflow tools on the gateway side too.
|
||||
let gatewaySubagentDeny = Array.isArray(gatewayTools.deny)
|
||||
? gatewayTools.deny.filter((value): value is string => typeof value === 'string')
|
||||
: [];
|
||||
let gatewaySubagentChanged = false;
|
||||
for (const entry of ['sessions_spawn', 'sessions_yield', 'subagents']) {
|
||||
if (!gatewaySubagentDeny.includes(entry)) {
|
||||
gatewaySubagentDeny = [...gatewaySubagentDeny, entry];
|
||||
gatewaySubagentChanged = true;
|
||||
}
|
||||
}
|
||||
if (gatewaySubagentChanged) {
|
||||
gatewayTools.deny = gatewaySubagentDeny;
|
||||
gateway.tools = gatewayTools;
|
||||
config.gateway = gateway;
|
||||
modified = true;
|
||||
} else if (!Array.isArray(gatewayTools.deny) || gatewayTools.deny.length !== gatewaySubagentDeny.length) {
|
||||
gatewayTools.deny = gatewaySubagentDeny;
|
||||
gateway.tools = gatewayTools;
|
||||
config.gateway = gateway;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
let skillsConfig = (
|
||||
config.skills && typeof config.skills === 'object' && !Array.isArray(config.skills)
|
||||
? { ...(config.skills as Record<string, unknown>) }
|
||||
|
||||
Reference in New Issue
Block a user