feat(permissions): add permission system and rename folder trust command

This commit is contained in:
LaZzyMan 2026-03-04 19:24:43 +08:00
parent 407a66c959
commit eeb4d85785
33 changed files with 3295 additions and 205 deletions

View file

@ -68,6 +68,7 @@ import { ideContextStore } from '../ide/ideContext.js';
import { InputFormat, OutputFormat } from '../output/types.js';
import { PromptRegistry } from '../prompts/prompt-registry.js';
import { SkillManager } from '../skills/skill-manager.js';
import { PermissionManager } from '../permissions/permission-manager.js';
import { SubagentManager } from '../subagents/subagent-manager.js';
import type { SubagentConfig } from '../subagents/types.js';
import {
@ -289,9 +290,18 @@ export interface ConfigParameters {
debugMode: boolean;
includePartialMessages?: boolean;
question?: string;
/** @deprecated Use `permissions.allow` instead. Migrated automatically. */
coreTools?: string[];
/** @deprecated Use `permissions.allow` instead. Migrated automatically. */
allowedTools?: string[];
/** @deprecated Use `permissions.deny` instead. Migrated automatically. */
excludeTools?: string[];
/** Merged permission rules from all sources (settings + CLI args). */
permissions?: {
allow?: string[];
ask?: string[];
deny?: string[];
};
toolDiscoveryCommand?: string;
toolCallCommand?: string;
mcpServerCommand?: string;
@ -420,6 +430,7 @@ export class Config {
private subagentManager!: SubagentManager;
private extensionManager!: ExtensionManager;
private skillManager: SkillManager | null = null;
private permissionManager: PermissionManager | null = null;
private fileSystemService: FileSystemService;
private contentGeneratorConfig!: ContentGeneratorConfig;
private contentGeneratorConfigSources: ContentGeneratorConfigSources = {};
@ -439,6 +450,9 @@ export class Config {
private readonly coreTools: string[] | undefined;
private readonly allowedTools: string[] | undefined;
private readonly excludeTools: string[] | undefined;
private readonly permissionsAllow: string[] | undefined;
private readonly permissionsAsk: string[] | undefined;
private readonly permissionsDeny: string[] | undefined;
private readonly toolDiscoveryCommand: string | undefined;
private readonly toolCallCommand: string | undefined;
private readonly mcpServerCommand: string | undefined;
@ -544,6 +558,9 @@ export class Config {
this.coreTools = params.coreTools;
this.allowedTools = params.allowedTools;
this.excludeTools = params.excludeTools;
this.permissionsAllow = params.permissions?.allow;
this.permissionsAsk = params.permissions?.ask;
this.permissionsDeny = params.permissions?.deny;
this.toolDiscoveryCommand = params.toolDiscoveryCommand;
this.toolCallCommand = params.toolCallCommand;
this.mcpServerCommand = params.mcpServerCommand;
@ -701,6 +718,10 @@ export class Config {
await this.skillManager.startWatching();
this.debugLogger.debug('Skill manager initialized');
this.permissionManager = new PermissionManager(this);
this.permissionManager.initialize();
this.debugLogger.debug('Permission manager initialized');
// Load session subagents if they were provided before initialization
if (this.sessionSubagents.length > 0) {
this.subagentManager.loadSessionSubagents(this.sessionSubagents);
@ -1073,6 +1094,10 @@ export class Config {
return this.targetDir;
}
getCwd(): string {
return this.targetDir;
}
getWorkspaceContext(): WorkspaceContext {
return this.workspaceContext;
}
@ -1115,18 +1140,69 @@ export class Config {
return this.question;
}
/** @deprecated Use getPermissionsAllow() instead. */
getCoreTools(): string[] | undefined {
return this.coreTools;
}
/** @deprecated Use getPermissionsAllow() instead. */
getAllowedTools(): string[] | undefined {
return this.allowedTools;
}
/** @deprecated Use getPermissionsDeny() instead. */
getExcludeTools(): string[] | undefined {
return this.excludeTools;
}
/**
* Returns the merged allow-rules for PermissionManager.
*
* This merges all sources so that PermissionManager receives a single,
* authoritative list:
* - settings.permissions.allow (persistent rules from all scopes)
* - coreTools param (SDK / argv allowlist mode: only these tools run)
* - allowedTools param (SDK / argv auto-approve list)
*
* CLI callers (loadCliConfig) already pre-merge argv into permissionsAllow
* before constructing Config, so those fields will be empty for CLI usage.
* SDK callers construct Config directly and rely on coreTools/allowedTools.
*/
getPermissionsAllow(): string[] | undefined {
const base = this.permissionsAllow ?? [];
const sdkAllow = [...(this.coreTools ?? []), ...(this.allowedTools ?? [])];
if (sdkAllow.length === 0) return base.length > 0 ? base : undefined;
const merged = [...base];
for (const t of sdkAllow) {
if (t && !merged.includes(t)) merged.push(t);
}
return merged;
}
getPermissionsAsk(): string[] | undefined {
return this.permissionsAsk;
}
/**
* Returns the merged deny-rules for PermissionManager.
*
* Merges:
* - settings.permissions.deny (persistent rules from all scopes)
* - excludeTools param (SDK / argv blocklist)
*
* CLI callers pre-merge argv.excludeTools into permissionsDeny.
*/
getPermissionsDeny(): string[] | undefined {
const base = this.permissionsDeny ?? [];
const sdkDeny = this.excludeTools ?? [];
if (sdkDeny.length === 0) return base.length > 0 ? base : undefined;
const merged = [...base];
for (const t of sdkDeny) {
if (t && !merged.includes(t)) merged.push(t);
}
return merged;
}
getToolDiscoveryCommand(): string | undefined {
return this.toolDiscoveryCommand;
}
@ -1642,6 +1718,10 @@ export class Config {
return this.skillManager;
}
getPermissionManager(): PermissionManager | null {
return this.permissionManager;
}
async createToolRegistry(
sendSdkMcpMessage?: SendSdkMcpMessage,
): Promise<ToolRegistry> {
@ -1669,7 +1749,20 @@ export class Config {
return;
}
if (isToolEnabled(toolName, coreToolsConfig, excludeToolsConfig)) {
// Two-layer check: legacy coreTools/excludeTools whitelist + PM deny rules.
// Legacy isToolEnabled() preserves the whitelist semantic where coreTools
// acts as a strict allowlist (only listed tools are registered).
// PM.isToolEnabled() handles deny rules from the new permissions system.
const legacyEnabled = isToolEnabled(
toolName,
coreToolsConfig,
excludeToolsConfig,
);
const pmEnabled = this.permissionManager
? this.permissionManager.isToolEnabled(toolName)
: true; // Should never reach here after initialize(), but safe default.
if (legacyEnabled && pmEnabled) {
try {
registry.registerTool(new ToolClass(...args));
} catch (error) {