refactor: remove legacy tools format, use permission only
BREAKING: Requires OpenCode 1.1.1+ - Remove supportsNewPermissionSystem/usesLegacyToolsSystem checks - Simplify permission-compat.ts to permission format only - Unify explore/librarian deny lists: write, edit, task, sisyphus_task, call_omo_agent - Add sisyphus_task to oracle deny list - Update agent-tool-restrictions.ts with correct per-agent restrictions - Clean config-handler.ts conditional version checks - Update tests for simplified API
This commit is contained in:
@@ -4,7 +4,7 @@ import { join } from "node:path"
|
||||
import { ALLOWED_AGENTS, CALL_OMO_AGENT_DESCRIPTION } from "./constants"
|
||||
import type { CallOmoAgentArgs } from "./types"
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
import { log } from "../../shared/logger"
|
||||
import { log, getAgentToolRestrictions } from "../../shared"
|
||||
import { consumeNewMessages } from "../../shared/session-cursor"
|
||||
import { findFirstMessageWithAgent, findNearestMessageWithFields, MESSAGE_STORAGE } from "../../features/hook-message-injector"
|
||||
import { getSessionAgent } from "../../features/claude-code-session-state"
|
||||
@@ -188,6 +188,7 @@ async function executeSync(
|
||||
body: {
|
||||
agent: args.subagent_type,
|
||||
tools: {
|
||||
...getAgentToolRestrictions(args.subagent_type),
|
||||
task: false,
|
||||
sisyphus_task: false,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import {
|
||||
lsp_goto_definition,
|
||||
lsp_find_references,
|
||||
lsp_symbols,
|
||||
lsp_diagnostics,
|
||||
lsp_prepare_rename,
|
||||
lsp_rename,
|
||||
@@ -52,6 +55,9 @@ export function createBackgroundTools(manager: BackgroundManager, client: Openco
|
||||
}
|
||||
|
||||
export const builtinTools: Record<string, ToolDefinition> = {
|
||||
lsp_goto_definition,
|
||||
lsp_find_references,
|
||||
lsp_symbols,
|
||||
lsp_diagnostics,
|
||||
lsp_prepare_rename,
|
||||
lsp_rename,
|
||||
|
||||
@@ -509,6 +509,37 @@ export class LSPClient {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
}
|
||||
|
||||
async definition(filePath: string, line: number, character: number): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/definition", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
})
|
||||
}
|
||||
|
||||
async references(filePath: string, line: number, character: number, includeDeclaration = true): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/references", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
context: { includeDeclaration },
|
||||
})
|
||||
}
|
||||
|
||||
async documentSymbols(filePath: string): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/documentSymbol", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
})
|
||||
}
|
||||
|
||||
async workspaceSymbols(query: string): Promise<unknown> {
|
||||
return this.send("workspace/symbol", { query })
|
||||
}
|
||||
|
||||
async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> {
|
||||
const absPath = resolve(filePath)
|
||||
const uri = pathToFileURL(absPath).href
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
import type { LSPServerConfig } from "./types"
|
||||
|
||||
export const SYMBOL_KIND_MAP: Record<number, string> = {
|
||||
1: "File",
|
||||
2: "Module",
|
||||
3: "Namespace",
|
||||
4: "Package",
|
||||
5: "Class",
|
||||
6: "Method",
|
||||
7: "Property",
|
||||
8: "Field",
|
||||
9: "Constructor",
|
||||
10: "Enum",
|
||||
11: "Interface",
|
||||
12: "Function",
|
||||
13: "Variable",
|
||||
14: "Constant",
|
||||
15: "String",
|
||||
16: "Number",
|
||||
17: "Boolean",
|
||||
18: "Array",
|
||||
19: "Object",
|
||||
20: "Key",
|
||||
21: "Null",
|
||||
22: "EnumMember",
|
||||
23: "Struct",
|
||||
24: "Event",
|
||||
25: "Operator",
|
||||
26: "TypeParameter",
|
||||
}
|
||||
|
||||
export const SEVERITY_MAP: Record<number, string> = {
|
||||
1: "error",
|
||||
2: "warning",
|
||||
@@ -7,6 +36,8 @@ export const SEVERITY_MAP: Record<number, string> = {
|
||||
4: "hint",
|
||||
}
|
||||
|
||||
export const DEFAULT_MAX_REFERENCES = 200
|
||||
export const DEFAULT_MAX_SYMBOLS = 200
|
||||
export const DEFAULT_MAX_DIAGNOSTICS = 200
|
||||
|
||||
export const LSP_INSTALL_HINTS: Record<string, string> = {
|
||||
|
||||
@@ -4,4 +4,4 @@ export * from "./config"
|
||||
export * from "./client"
|
||||
export * from "./utils"
|
||||
// NOTE: lsp_servers removed - duplicates OpenCode's built-in LspServers
|
||||
export { lsp_diagnostics, lsp_prepare_rename, lsp_rename } from "./tools"
|
||||
export { lsp_goto_definition, lsp_find_references, lsp_symbols, lsp_diagnostics, lsp_prepare_rename, lsp_rename } from "./tools"
|
||||
|
||||
+148
-2
@@ -1,9 +1,14 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import {
|
||||
DEFAULT_MAX_REFERENCES,
|
||||
DEFAULT_MAX_SYMBOLS,
|
||||
DEFAULT_MAX_DIAGNOSTICS,
|
||||
} from "./constants"
|
||||
import {
|
||||
withLspClient,
|
||||
formatLocation,
|
||||
formatDocumentSymbol,
|
||||
formatSymbolInfo,
|
||||
formatDiagnostic,
|
||||
filterDiagnosticsBySeverity,
|
||||
formatPrepareRenameResult,
|
||||
@@ -11,14 +16,155 @@ import {
|
||||
formatApplyResult,
|
||||
} from "./utils"
|
||||
import type {
|
||||
Location,
|
||||
LocationLink,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
Diagnostic,
|
||||
PrepareRenameResult,
|
||||
PrepareRenameDefaultBehavior,
|
||||
WorkspaceEdit,
|
||||
} from "./types"
|
||||
|
||||
// NOTE: lsp_goto_definition, lsp_find_references, lsp_symbols are removed
|
||||
// as they duplicate OpenCode's built-in LSP tools (LspGotoDefinition, LspFindReferences, LspDocumentSymbols, LspWorkspaceSymbols)
|
||||
export const lsp_goto_definition: ToolDefinition = tool({
|
||||
description: "Jump to symbol definition. Find WHERE something is defined.",
|
||||
args: {
|
||||
filePath: tool.schema.string(),
|
||||
line: tool.schema.number().min(1).describe("1-based"),
|
||||
character: tool.schema.number().min(0).describe("0-based"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.definition(args.filePath, args.line, args.character)) as
|
||||
| Location
|
||||
| Location[]
|
||||
| LocationLink[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const locations = Array.isArray(result) ? result : [result]
|
||||
if (locations.length === 0) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const output = locations.map(formatLocation).join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_find_references: ToolDefinition = tool({
|
||||
description: "Find ALL usages/references of a symbol across the entire workspace.",
|
||||
args: {
|
||||
filePath: tool.schema.string(),
|
||||
line: tool.schema.number().min(1).describe("1-based"),
|
||||
character: tool.schema.number().min(0).describe("0-based"),
|
||||
includeDeclaration: tool.schema.boolean().optional().describe("Include the declaration itself"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.references(args.filePath, args.line, args.character, args.includeDeclaration ?? true)) as
|
||||
| Location[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
const output = "No references found"
|
||||
return output
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const truncated = total > DEFAULT_MAX_REFERENCES
|
||||
const limited = truncated ? result.slice(0, DEFAULT_MAX_REFERENCES) : result
|
||||
const lines = limited.map(formatLocation)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`)
|
||||
}
|
||||
const output = lines.join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_symbols: ToolDefinition = tool({
|
||||
description: "Get symbols from file (document) or search across workspace. Use scope='document' for file outline, scope='workspace' for project-wide symbol search.",
|
||||
args: {
|
||||
filePath: tool.schema.string().describe("File path for LSP context"),
|
||||
scope: tool.schema.enum(["document", "workspace"]).default("document").describe("'document' for file symbols, 'workspace' for project-wide search"),
|
||||
query: tool.schema.string().optional().describe("Symbol name to search (required for workspace scope)"),
|
||||
limit: tool.schema.number().optional().describe("Max results (default 50)"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const scope = args.scope ?? "document"
|
||||
|
||||
if (scope === "workspace") {
|
||||
if (!args.query) {
|
||||
return "Error: 'query' is required for workspace scope"
|
||||
}
|
||||
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.workspaceSymbols(args.query!)) as SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = result.slice(0, limit)
|
||||
const lines = limited.map(formatSymbolInfo)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
} else {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.documentSymbols(args.filePath)) as DocumentSymbol[] | SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = truncated ? result.slice(0, limit) : result
|
||||
|
||||
const lines: string[] = []
|
||||
if (truncated) {
|
||||
lines.push(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
|
||||
if ("range" in limited[0]) {
|
||||
lines.push(...(limited as DocumentSymbol[]).map((s) => formatDocumentSymbol(s)))
|
||||
} else {
|
||||
lines.push(...(limited as SymbolInfo[]).map(formatSymbolInfo))
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
return `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_diagnostics: ToolDefinition = tool({
|
||||
description: "Get errors, warnings, hints from language server BEFORE running build.",
|
||||
|
||||
@@ -17,6 +17,33 @@ export interface Range {
|
||||
end: Position
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
uri: string
|
||||
range: Range
|
||||
}
|
||||
|
||||
export interface LocationLink {
|
||||
targetUri: string
|
||||
targetRange: Range
|
||||
targetSelectionRange: Range
|
||||
originSelectionRange?: Range
|
||||
}
|
||||
|
||||
export interface SymbolInfo {
|
||||
name: string
|
||||
kind: number
|
||||
location: Location
|
||||
containerName?: string
|
||||
}
|
||||
|
||||
export interface DocumentSymbol {
|
||||
name: string
|
||||
kind: number
|
||||
range: Range
|
||||
selectionRange: Range
|
||||
children?: DocumentSymbol[]
|
||||
}
|
||||
|
||||
export interface Diagnostic {
|
||||
range: Range
|
||||
severity?: number
|
||||
|
||||
+45
-1
@@ -3,8 +3,12 @@ import { fileURLToPath } from "node:url"
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs"
|
||||
import { LSPClient, lspManager } from "./client"
|
||||
import { findServerForExtension } from "./config"
|
||||
import { SEVERITY_MAP } from "./constants"
|
||||
import { SYMBOL_KIND_MAP, SEVERITY_MAP } from "./constants"
|
||||
import type {
|
||||
Location,
|
||||
LocationLink,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
Diagnostic,
|
||||
PrepareRenameResult,
|
||||
PrepareRenameDefaultBehavior,
|
||||
@@ -106,11 +110,51 @@ export async function withLspClient<T>(filePath: string, fn: (client: LSPClient)
|
||||
}
|
||||
}
|
||||
|
||||
export function formatLocation(loc: Location | LocationLink): string {
|
||||
if ("targetUri" in loc) {
|
||||
const uri = uriToPath(loc.targetUri)
|
||||
const line = loc.targetRange.start.line + 1
|
||||
const char = loc.targetRange.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
const uri = uriToPath(loc.uri)
|
||||
const line = loc.range.start.line + 1
|
||||
const char = loc.range.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
export function formatSymbolKind(kind: number): string {
|
||||
return SYMBOL_KIND_MAP[kind] || `Unknown(${kind})`
|
||||
}
|
||||
|
||||
export function formatSeverity(severity: number | undefined): string {
|
||||
if (!severity) return "unknown"
|
||||
return SEVERITY_MAP[severity] || `unknown(${severity})`
|
||||
}
|
||||
|
||||
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string {
|
||||
const prefix = " ".repeat(indent)
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const line = symbol.range.start.line + 1
|
||||
let result = `${prefix}${symbol.name} (${kind}) - line ${line}`
|
||||
|
||||
if (symbol.children && symbol.children.length > 0) {
|
||||
for (const child of symbol.children) {
|
||||
result += "\n" + formatDocumentSymbol(child, indent + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatSymbolInfo(symbol: SymbolInfo): string {
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const loc = formatLocation(symbol.location)
|
||||
const container = symbol.containerName ? ` (in ${symbol.containerName})` : ""
|
||||
return `${symbol.name} (${kind})${container} - ${loc}`
|
||||
}
|
||||
|
||||
export function formatDiagnostic(diag: Diagnostic): string {
|
||||
const severity = formatSeverity(diag.severity)
|
||||
const line = diag.range.start.line + 1
|
||||
|
||||
@@ -11,7 +11,7 @@ import { discoverSkills } from "../../features/opencode-skill-loader"
|
||||
import { getTaskToastManager } from "../../features/task-toast-manager"
|
||||
import type { ModelFallbackInfo } from "../../features/task-toast-manager/types"
|
||||
import { subagentSessions, getSessionAgent } from "../../features/claude-code-session-state"
|
||||
import { log } from "../../shared/logger"
|
||||
import { log, getAgentToolRestrictions } from "../../shared"
|
||||
|
||||
type OpencodeClient = PluginInput["client"]
|
||||
|
||||
@@ -322,6 +322,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
||||
...(resumeModel !== undefined ? { model: resumeModel } : {}),
|
||||
tools: {
|
||||
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
|
||||
task: false,
|
||||
sisyphus_task: false,
|
||||
call_omo_agent: true,
|
||||
|
||||
Reference in New Issue
Block a user