fix: proper OpenCode v1.1.1 permission migration (#490)

* fix: implement proper version-aware permission format for OpenCode v1.1.1

- Rewrite permission-compat.ts with runtime version detection
- createAgentToolRestrictions() returns correct format per version
- v1.1.1+ uses permission format, older uses tools format
- Add migrateToolsToPermission/migratePermissionToTools helpers
- Update test suite for new API

🤖 Generated with assistance of OhMyOpenCode
https://github.com/code-yeongyu/oh-my-opencode

* fix: update all agents to use createAgentToolRestrictions()

- Replace hardcoded tools: { X: false } format with version-aware utility
- All agents now use createAgentToolRestrictions([...])
- Ensures compatibility with both old and new OpenCode versions

🤖 Generated with assistance of OhMyOpenCode
https://github.com/code-yeongyu/oh-my-opencode

* fix: add runtime migration for user agent configs in config-handler

Migrate tools/permission format in user/project/plugin agent configs
based on detected OpenCode version at load time.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-01-05 05:28:25 +09:00
committed by GitHub
parent 09f72e2902
commit 8f2209a138
9 changed files with 232 additions and 170 deletions

View File

@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "google/gemini-3-flash-preview" const DEFAULT_MODEL = "google/gemini-3-flash-preview"
@@ -15,12 +16,14 @@ export const DOCUMENT_WRITER_PROMPT_METADATA: AgentPromptMetadata = {
export function createDocumentWriterAgent( export function createDocumentWriterAgent(
model: string = DEFAULT_MODEL model: string = DEFAULT_MODEL
): AgentConfig { ): AgentConfig {
const restrictions = createAgentToolRestrictions(["background_task"])
return { return {
description: description:
"A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.", "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
mode: "subagent" as const, mode: "subagent" as const,
model, model,
tools: { background_task: false }, ...restrictions,
prompt: `<role> prompt: `<role>
You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy. You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy.

View File

@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "opencode/grok-code" const DEFAULT_MODEL = "opencode/grok-code"
@@ -24,14 +25,19 @@ export const EXPLORE_PROMPT_METADATA: AgentPromptMetadata = {
} }
export function createExploreAgent(model: string = DEFAULT_MODEL): AgentConfig { export function createExploreAgent(model: string = DEFAULT_MODEL): AgentConfig {
const restrictions = createAgentToolRestrictions([
"write",
"edit",
"background_task",
])
return { return {
description: description:
'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.', 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
mode: "subagent" as const, mode: "subagent" as const,
model, model,
temperature: 0.1, temperature: 0.1,
tools: { write: false, background_task: false }, ...restrictions,
permission: { edit: "deny" as const },
prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results. prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
## Your Mission ## Your Mission

View File

@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "google/gemini-3-pro-preview" const DEFAULT_MODEL = "google/gemini-3-pro-preview"
@@ -21,12 +22,14 @@ export const FRONTEND_PROMPT_METADATA: AgentPromptMetadata = {
export function createFrontendUiUxEngineerAgent( export function createFrontendUiUxEngineerAgent(
model: string = DEFAULT_MODEL model: string = DEFAULT_MODEL
): AgentConfig { ): AgentConfig {
const restrictions = createAgentToolRestrictions(["background_task"])
return { return {
description: description:
"A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.", "A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.",
mode: "subagent" as const, mode: "subagent" as const,
model, model,
tools: { background_task: false }, ...restrictions,
prompt: `# Role: Designer-Turned-Developer prompt: `# Role: Designer-Turned-Developer
You are a designer who learned to code. You see what pure developers miss—spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces. You are a designer who learned to code. You see what pure developers miss—spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces.

View File

@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5" const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5"
@@ -21,13 +22,19 @@ export const LIBRARIAN_PROMPT_METADATA: AgentPromptMetadata = {
} }
export function createLibrarianAgent(model: string = DEFAULT_MODEL): AgentConfig { export function createLibrarianAgent(model: string = DEFAULT_MODEL): AgentConfig {
const restrictions = createAgentToolRestrictions([
"write",
"edit",
"background_task",
])
return { return {
description: description:
"Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.", "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
mode: "subagent" as const, mode: "subagent" as const,
model, model,
temperature: 0.1, temperature: 0.1,
tools: { write: false, edit: false, background_task: false }, ...restrictions,
prompt: `# THE LIBRARIAN prompt: `# THE LIBRARIAN
You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent. You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent.

View File

@@ -1,5 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "google/gemini-3-flash" const DEFAULT_MODEL = "google/gemini-3-flash"
@@ -13,13 +14,20 @@ export const MULTIMODAL_LOOKER_PROMPT_METADATA: AgentPromptMetadata = {
export function createMultimodalLookerAgent( export function createMultimodalLookerAgent(
model: string = DEFAULT_MODEL model: string = DEFAULT_MODEL
): AgentConfig { ): AgentConfig {
const restrictions = createAgentToolRestrictions([
"write",
"edit",
"bash",
"background_task",
])
return { return {
description: description:
"Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.", "Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.",
mode: "subagent" as const, mode: "subagent" as const,
model, model,
temperature: 0.1, temperature: 0.1,
tools: { write: false, edit: false, bash: false, background_task: false }, ...restrictions,
prompt: `You interpret media files that cannot be read as plain text. prompt: `You interpret media files that cannot be read as plain text.
Your job: examine the attached file and extract ONLY what was requested. Your job: examine the attached file and extract ONLY what was requested.

View File

@@ -1,6 +1,7 @@
import type { AgentConfig } from "@opencode-ai/sdk" import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentPromptMetadata } from "./types" import type { AgentPromptMetadata } from "./types"
import { isGptModel } from "./types" import { isGptModel } from "./types"
import { createAgentToolRestrictions } from "../shared/permission-compat"
const DEFAULT_MODEL = "openai/gpt-5.2" const DEFAULT_MODEL = "openai/gpt-5.2"
@@ -97,22 +98,28 @@ Organize your final answer in three tiers:
Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.` Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`
export function createOracleAgent(model: string = DEFAULT_MODEL): AgentConfig { export function createOracleAgent(model: string = DEFAULT_MODEL): AgentConfig {
const restrictions = createAgentToolRestrictions([
"write",
"edit",
"task",
"background_task",
])
const base = { const base = {
description: description:
"Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.", "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
mode: "subagent" as const, mode: "subagent" as const,
model, model,
temperature: 0.1, temperature: 0.1,
tools: { write: false, task: false, background_task: false }, ...restrictions,
permission: { edit: "deny" as const },
prompt: ORACLE_SYSTEM_PROMPT, prompt: ORACLE_SYSTEM_PROMPT,
} } as AgentConfig
if (isGptModel(model)) { if (isGptModel(model)) {
return { ...base, reasoningEffort: "medium", textVerbosity: "high" } return { ...base, reasoningEffort: "medium", textVerbosity: "high" } as AgentConfig
} }
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } } as AgentConfig
} }
export const oracleAgent = createOracleAgent() export const oracleAgent = createOracleAgent()

View File

@@ -21,6 +21,7 @@ import { loadAllPluginComponents } from "../features/claude-code-plugin-loader";
import { createBuiltinMcps } from "../mcp"; import { createBuiltinMcps } from "../mcp";
import type { OhMyOpenCodeConfig } from "../config"; import type { OhMyOpenCodeConfig } from "../config";
import { log } from "../shared"; import { log } from "../shared";
import { migrateAgentConfig } from "../shared/permission-compat";
import { PLAN_SYSTEM_PROMPT, PLAN_PERMISSION } from "../agents/plan-prompt"; import { PLAN_SYSTEM_PROMPT, PLAN_PERMISSION } from "../agents/plan-prompt";
import type { ModelCacheState } from "../plugin-state"; import type { ModelCacheState } from "../plugin-state";
@@ -95,13 +96,32 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
config.model as string | undefined config.model as string | undefined
); );
const userAgents = (pluginConfig.claude_code?.agents ?? true) const rawUserAgents = (pluginConfig.claude_code?.agents ?? true)
? loadUserAgents() ? loadUserAgents()
: {}; : {};
const projectAgents = (pluginConfig.claude_code?.agents ?? true) const rawProjectAgents = (pluginConfig.claude_code?.agents ?? true)
? loadProjectAgents() ? loadProjectAgents()
: {}; : {};
const pluginAgents = pluginComponents.agents; const rawPluginAgents = pluginComponents.agents;
const userAgents = Object.fromEntries(
Object.entries(rawUserAgents).map(([k, v]) => [
k,
v ? migrateAgentConfig(v as Record<string, unknown>) : v,
])
);
const projectAgents = Object.fromEntries(
Object.entries(rawProjectAgents).map(([k, v]) => [
k,
v ? migrateAgentConfig(v as Record<string, unknown>) : v,
])
);
const pluginAgents = Object.fromEntries(
Object.entries(rawPluginAgents).map(([k, v]) => [
k,
v ? migrateAgentConfig(v as Record<string, unknown>) : v,
])
);
const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true; const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
const builderEnabled = const builderEnabled =
@@ -162,15 +182,20 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
: plannerSisyphusBase; : plannerSisyphusBase;
} }
const filteredConfigAgents = configAgent const filteredConfigAgents = configAgent
? Object.fromEntries( ? Object.fromEntries(
Object.entries(configAgent).filter(([key]) => { Object.entries(configAgent)
.filter(([key]) => {
if (key === "build") return false; if (key === "build") return false;
if (key === "plan" && replacePlan) return false; if (key === "plan" && replacePlan) return false;
return true; return true;
}) })
) .map(([key, value]) => [
: {}; key,
value ? migrateAgentConfig(value as Record<string, unknown>) : value,
])
)
: {};
config.agent = { config.agent = {
...agentConfig, ...agentConfig,

View File

@@ -1,161 +1,158 @@
import { describe, test, expect } from "bun:test" import { describe, test, expect, beforeEach, afterEach } from "bun:test"
import { import {
createToolDenyList, createAgentToolRestrictions,
permissionValueToBoolean, migrateToolsToPermission,
booleanToPermissionValue, migratePermissionToTools,
convertToolsToPermission, migrateAgentConfig,
convertPermissionToTools,
createAgentRestrictions,
} from "./permission-compat" } from "./permission-compat"
import { setVersionCache, resetVersionCache } from "./opencode-version"
describe("permission-compat", () => { describe("permission-compat", () => {
describe("createToolDenyList", () => { beforeEach(() => {
test("creates tools config with all values false", () => { resetVersionCache()
// #given a list of tool names })
const tools = ["write", "edit", "task"]
// #when creating deny list afterEach(() => {
const result = createToolDenyList(tools) resetVersionCache()
})
// #then all values are false describe("createAgentToolRestrictions", () => {
expect(result).toEqual({ write: false, edit: false, task: false }) test("returns permission format for v1.1.1+", () => {
// #given version is 1.1.1
setVersionCache("1.1.1")
// #when creating restrictions
const result = createAgentToolRestrictions(["write", "edit"])
// #then returns permission format
expect(result).toEqual({
permission: { write: "deny", edit: "deny" },
})
}) })
test("returns empty object for empty array", () => { test("returns tools format for versions below 1.1.1", () => {
// #given empty array // #given version is below 1.1.1
// #when creating deny list setVersionCache("1.0.150")
const result = createToolDenyList([])
// #then returns empty object // #when creating restrictions
expect(result).toEqual({}) const result = createAgentToolRestrictions(["write", "edit"])
// #then returns tools format
expect(result).toEqual({
tools: { write: false, edit: false },
})
})
test("assumes new format when version unknown", () => {
// #given version is null
setVersionCache(null)
// #when creating restrictions
const result = createAgentToolRestrictions(["write"])
// #then returns permission format (assumes new version)
expect(result).toEqual({
permission: { write: "deny" },
})
}) })
}) })
describe("permissionValueToBoolean", () => { describe("migrateToolsToPermission", () => {
test("converts allow to true", () => { test("converts boolean tools to permission values", () => {
expect(permissionValueToBoolean("allow")).toBe(true) // #given tools config
})
test("converts deny to false", () => {
expect(permissionValueToBoolean("deny")).toBe(false)
})
test("converts ask to false", () => {
expect(permissionValueToBoolean("ask")).toBe(false)
})
})
describe("booleanToPermissionValue", () => {
test("converts true to allow", () => {
expect(booleanToPermissionValue(true)).toBe("allow")
})
test("converts false to deny", () => {
expect(booleanToPermissionValue(false)).toBe("deny")
})
})
describe("convertToolsToPermission", () => {
test("converts boolean tools config to permission format", () => {
// #given tools config with booleans
const tools = { write: false, edit: true, bash: false } const tools = { write: false, edit: true, bash: false }
// #when converting to permission // #when migrating
const result = convertToolsToPermission(tools) const result = migrateToolsToPermission(tools)
// #then converts to permission values // #then converts correctly
expect(result).toEqual({ write: "deny", edit: "allow", bash: "deny" }) expect(result).toEqual({
}) write: "deny",
edit: "allow",
test("handles empty tools config", () => { bash: "deny",
// #given empty config })
// #when converting
const result = convertToolsToPermission({})
// #then returns empty object
expect(result).toEqual({})
}) })
}) })
describe("convertPermissionToTools", () => { describe("migratePermissionToTools", () => {
test("converts permission to boolean tools config", () => { test("converts permission to boolean tools", () => {
// #given permission config // #given permission config
const permission = { write: "deny" as const, edit: "allow" as const } const permission = { write: "deny" as const, edit: "allow" as const }
// #when converting to tools // #when migrating
const result = convertPermissionToTools(permission) const result = migratePermissionToTools(permission)
// #then converts to boolean values // #then converts correctly
expect(result).toEqual({ write: false, edit: true }) expect(result).toEqual({ write: false, edit: true })
}) })
test("excludes ask values", () => { test("excludes ask values", () => {
// #given permission with ask value // #given permission with ask
const permission = { const permission = {
write: "deny" as const, write: "deny" as const,
edit: "ask" as const, edit: "ask" as const,
bash: "allow" as const, bash: "allow" as const,
} }
// #when converting // #when migrating
const result = convertPermissionToTools(permission) const result = migratePermissionToTools(permission)
// #then ask is excluded // #then ask is excluded
expect(result).toEqual({ write: false, bash: true }) expect(result).toEqual({ write: false, bash: true })
}) })
}) })
describe("createAgentRestrictions", () => { describe("migrateAgentConfig", () => {
test("creates restrictions with denied tools", () => { test("migrates tools to permission for v1.1.1+", () => {
// #given deny tools list // #given v1.1.1 and config with tools
const config = { denyTools: ["write", "task"] } setVersionCache("1.1.1")
// #when creating restrictions
const result = createAgentRestrictions(config)
// #then creates tools config
expect(result).toEqual({ tools: { write: false, task: false } })
})
test("creates restrictions with permission", () => {
// #given permission config
const config = { const config = {
permission: { edit: "deny" as const, bash: "ask" as const }, model: "test",
tools: { write: false, edit: false },
} }
// #when creating restrictions // #when migrating
const result = createAgentRestrictions(config) const result = migrateAgentConfig(config)
// #then creates permission config // #then converts to permission
expect(result).toEqual({ expect(result.tools).toBeUndefined()
permission: { edit: "deny", bash: "ask" }, expect(result.permission).toEqual({ write: "deny", edit: "deny" })
}) expect(result.model).toBe("test")
}) })
test("combines tools and permission", () => { test("migrates permission to tools for old versions", () => {
// #given both deny tools and permission // #given old version and config with permission
setVersionCache("1.0.150")
const config = { const config = {
denyTools: ["task"], model: "test",
permission: { edit: "deny" as const }, permission: { write: "deny" as const, edit: "deny" as const },
} }
// #when creating restrictions // #when migrating
const result = createAgentRestrictions(config) const result = migrateAgentConfig(config)
// #then includes both // #then converts to tools
expect(result).toEqual({ expect(result.permission).toBeUndefined()
tools: { task: false }, expect(result.tools).toEqual({ write: false, edit: false })
permission: { edit: "deny" },
})
}) })
test("returns empty object when no config provided", () => { test("preserves other config fields", () => {
// #given empty config // #given config with other fields
// #when creating restrictions setVersionCache("1.1.1")
const result = createAgentRestrictions({}) const config = {
model: "test",
temperature: 0.5,
prompt: "hello",
tools: { write: false },
}
// #then returns empty object // #when migrating
expect(result).toEqual({}) const result = migrateAgentConfig(config)
// #then preserves other fields
expect(result.model).toBe("test")
expect(result.temperature).toBe(0.5)
expect(result.prompt).toBe("hello")
}) })
}) })
}) })

View File

@@ -1,72 +1,78 @@
import { supportsNewPermissionSystem as checkNewPermissionSystem } from "./opencode-version" import { supportsNewPermissionSystem } from "./opencode-version"
export type PermissionValue = "ask" | "allow" | "deny" export type PermissionValue = "ask" | "allow" | "deny"
export type BashPermission = PermissionValue | Record<string, PermissionValue>
export interface StandardPermission { export interface LegacyToolsFormat {
edit?: PermissionValue tools: Record<string, boolean>
bash?: BashPermission
webfetch?: PermissionValue
doom_loop?: PermissionValue
external_directory?: PermissionValue
} }
export interface ToolsConfig { export interface NewPermissionFormat {
[toolName: string]: boolean permission: Record<string, PermissionValue>
} }
export interface AgentPermissionConfig { export type VersionAwareRestrictions = LegacyToolsFormat | NewPermissionFormat
permission?: StandardPermission
tools?: ToolsConfig export function createAgentToolRestrictions(
denyTools: string[]
): VersionAwareRestrictions {
if (supportsNewPermissionSystem()) {
return {
permission: Object.fromEntries(
denyTools.map((tool) => [tool, "deny" as const])
),
}
}
return {
tools: Object.fromEntries(denyTools.map((tool) => [tool, false])),
}
} }
export { checkNewPermissionSystem as supportsNewPermissionSystemFromCompat } export function migrateToolsToPermission(
tools: Record<string, boolean>
export function createToolDenyList(toolNames: string[]): ToolsConfig {
return Object.fromEntries(toolNames.map((name) => [name, false]))
}
export function permissionValueToBoolean(value: PermissionValue): boolean {
return value === "allow"
}
export function booleanToPermissionValue(value: boolean): PermissionValue {
return value ? "allow" : "deny"
}
export function convertToolsToPermission(
tools: ToolsConfig
): Record<string, PermissionValue> { ): Record<string, PermissionValue> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(tools).map(([key, value]) => [ Object.entries(tools).map(([key, value]) => [
key, key,
booleanToPermissionValue(value), value ? ("allow" as const) : ("deny" as const),
]) ])
) )
} }
export function convertPermissionToTools( export function migratePermissionToTools(
permission: Record<string, PermissionValue> permission: Record<string, PermissionValue>
): ToolsConfig { ): Record<string, boolean> {
return Object.fromEntries( return Object.fromEntries(
Object.entries(permission) Object.entries(permission)
.filter(([, value]) => value !== "ask") .filter(([, value]) => value !== "ask")
.map(([key, value]) => [key, permissionValueToBoolean(value)]) .map(([key, value]) => [key, value === "allow"])
) )
} }
export function createAgentRestrictions(config: { export function migrateAgentConfig(
denyTools?: string[] config: Record<string, unknown>
permission?: StandardPermission ): Record<string, unknown> {
}): AgentPermissionConfig { const result = { ...config }
const result: AgentPermissionConfig = {}
if (config.denyTools && config.denyTools.length > 0) { if (supportsNewPermissionSystem()) {
result.tools = createToolDenyList(config.denyTools) if (result.tools && typeof result.tools === "object") {
} const existingPermission =
(result.permission as Record<string, PermissionValue>) || {}
if (config.permission) { const migratedPermission = migrateToolsToPermission(
result.permission = config.permission result.tools as Record<string, boolean>
)
result.permission = { ...migratedPermission, ...existingPermission }
delete result.tools
}
} else {
if (result.permission && typeof result.permission === "object") {
const existingTools = (result.tools as Record<string, boolean>) || {}
const migratedTools = migratePermissionToTools(
result.permission as Record<string, PermissionValue>
)
result.tools = { ...migratedTools, ...existingTools }
delete result.permission
}
} }
return result return result