Merge pull request #2 from leuel-a/feat/add-select-intent-tool

Feat/add select intent tool
This commit is contained in:
Leuel Asfaw 2026-02-21 13:03:18 +03:00 committed by GitHub
commit ec41ccc661
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 551 additions and 35 deletions

View file

@ -47,6 +47,7 @@ export const toolNames = [
"generate_image",
"custom_tool",
"select_active_intent",
"list_active_intents",
] as const
export const toolNamesSchema = z.enum(toolNames)

View file

@ -17,6 +17,7 @@ import { Task } from "../task/Task"
import { listFilesTool } from "../tools/ListFilesTool"
import { readFileTool } from "../tools/ReadFileTool"
import { readCommandOutputTool } from "../tools/ReadCommandOutputTool"
import { listActiveIntentsTool } from "../tools/ListActiveIntents"
import { selectActiveIntentTool } from "../tools/SelectActiveIntent"
import { writeToFileTool } from "../tools/WriteToFileTool"
import { editTool } from "../tools/EditTool"
@ -41,13 +42,16 @@ import { codebaseSearchTool } from "../tools/CodebaseSearchTool"
import { formatResponse } from "../prompts/responses"
import { sanitizeToolUseId } from "../../utils/tool-id"
import { MiddlewareChain } from "../middlewares/MiddlewareChain"
import { IntentValidationMiddleware } from "../middlewares/IntentValidationMiddleware"
import { ScopeEnforcementMiddleware } from "../middlewares/ScopeEnforcementMiddleware"
/**
* Processes and presents assistant message content to the user interface.
*
* This function is the core message handling system that:
* - Sequentially processes content blocks from the assistant's response.
* - Displays text content to the user.
* - Displays text content to the user
* - Executes tool use requests with appropriate user approval.
* - Manages the flow of conversation by determining when to proceed to the next content block.
* - Coordinates file system checkpointing for modified files.
@ -336,8 +340,10 @@ export async function presentAssistantMessage(cline: Task) {
return readFileTool.getReadFileToolDescription(block.name, block.nativeArgs)
}
return readFileTool.getReadFileToolDescription(block.name, block.params)
case "select_active_intent":
case "list_active_intents":
return `[${block.name}]`
case "select_active_intent":
return `[${block.name}] for '${block.params.path}'`
case "write_to_file":
return `[${block.name} for '${block.params.path}']`
case "apply_diff":
@ -678,8 +684,33 @@ export async function presentAssistantMessage(cline: Task) {
}
}
const middlewareChain = new MiddlewareChain()
middlewareChain.add(new IntentValidationMiddleware())
middlewareChain.add(new ScopeEnforcementMiddleware())
const middlewareResult = await middlewareChain.executeBefore(block.params, cline, block.name)
if (!middlewareResult.allow) {
pushToolResult(middlewareResult.error || "Middleware validation failed")
cline.consecutiveMistakeCount++
cline.didToolFailInCurrentTurn = true
cline.recordToolError(block.name as ToolName, middlewareResult.error || "Middleware validation failed")
break
}
switch (block.name) {
case "list_active_intents":
await listActiveIntentsTool.handle(cline, block as ToolUse<"list_active_intents">, {
askApproval,
handleError,
pushToolResult,
})
break
case "select_active_intent":
await selectActiveIntentTool.handle(cline, block as ToolUse<"select_active_intent">, {
askApproval,
handleError,
pushToolResult,
})
break
case "write_to_file":
await checkpointSaveAndMark(cline)

View file

@ -0,0 +1,85 @@
import * as fs from "fs/promises"
import * as path from "path"
import { logger } from "../../utils/logging"
import type { Intent, ActiveIntentsFile } from "./types"
export class IntentLoader {
private intents: Map<string, Intent> = new Map()
private cwd: string
private readonly log = logger.child({ component: "IntentLoader" })
private lastLoadTime = 0
private readonly CACHE_TTL_MS = 5000 // 5 seconds
constructor(cwd: string) {
this.cwd = cwd
}
async ensureLoaded(force = false): Promise<void> {
const now = Date.now()
if (!force && this.intents.size > 0 && now - this.lastLoadTime < this.CACHE_TTL_MS) {
// Cache is still valid
return
}
await this.loadIntents()
this.lastLoadTime = now
}
private async loadIntents(): Promise<void> {
const intentsPath = path.join(this.cwd, ".orchestration", "active_intents.json")
try {
const content = await fs.readFile(intentsPath, "utf-8")
const parsed = this.parseJsonSafely(content, intentsPath)
if (!parsed?.active_intents || !Array.isArray(parsed.active_intents)) {
this.log.warn(`No active_intents found in ${intentsPath}`)
this.intents.clear()
return
}
this.intents.clear()
for (const intent of parsed.active_intents) {
if (intent?.id && typeof intent.id === "string") {
this.intents.set(intent.id, intent)
}
}
this.log.info(`Loaded ${this.intents.size} intents from ${intentsPath}`)
} catch (error: any) {
if (error?.code !== "ENOENT") {
this.log.error(`Failed to load intents from ${intentsPath}`, error)
}
this.intents.clear()
}
}
getIntent(id: string): Intent | undefined {
return this.intents.get(id)
}
getAllIntents(): Intent[] {
return Array.from(this.intents.values())
}
hasIntent(id: string): boolean {
return this.intents.has(id)
}
private parseJsonSafely(content: string, filePath: string): ActiveIntentsFile {
try {
const cleaned = this.stripBom(content)
const parsed = JSON.parse(cleaned)
return (parsed ?? { active_intents: [] }) as ActiveIntentsFile
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
this.log.error(`Failed to parse JSON from ${filePath}: ${msg}`)
return { active_intents: [] }
}
}
private stripBom(s: string): string {
return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s
}
}

16
src/core/intents/types.ts Normal file
View file

@ -0,0 +1,16 @@
enum INTENT_STATUS {
IN_PROGRESS,
}
export interface ActiveIntentsFile {
active_intents: Intent[]
}
export interface Intent {
id: string
name: string
status: INTENT_STATUS
owned_scopes: string[]
constraints: string[]
acceptance_criteria: string[]
}

View file

@ -0,0 +1,23 @@
import { ToolName } from "@roo-code/types"
import { Task } from "../task/Task"
import { ToolMiddleware, MiddlewareResult } from "./ToolMiddleware"
export class IntentValidationMiddleware implements ToolMiddleware {
name = "intentValidation"
async beforeExecute(_params: any, task: Task, toolName: ToolName): Promise<MiddlewareResult> {
if (toolName === "select_active_intent" || toolName === "list_active_intents") {
return { allow: true }
}
const selectedIntentId = task.getSelectedIntentId()
if (!selectedIntentId) {
return {
allow: false,
error: "No active intent selected. Use select_active_intent first.",
}
}
return { allow: true }
}
}

View file

@ -0,0 +1,25 @@
import { ToolName } from "@roo-code/types"
import { Task } from "../task/Task"
import { ToolMiddleware, MiddlewareResult } from "./ToolMiddleware"
export class MiddlewareChain {
private middlewares: ToolMiddleware[] = []
add(middleware: ToolMiddleware): void {
this.middlewares.push(middleware)
}
async executeBefore(params: any, task: Task, toolName: ToolName): Promise<MiddlewareResult> {
for (const middleware of this.middlewares) {
const result = await middleware.beforeExecute?.(params, task, toolName)
if (result?.allow === false) {
return result
}
if (result?.modifiedParams) {
params = result.modifiedParams
}
}
return { allow: true }
}
}

View file

@ -0,0 +1,56 @@
import { ToolName } from "@roo-code/types"
import { Task } from "../task/Task"
import { ToolMiddleware, MiddlewareResult } from "./ToolMiddleware"
export class ScopeEnforcementMiddleware implements ToolMiddleware {
name = "scopeEnforcement"
async beforeExecute(params: any, task: Task, toolName: ToolName): Promise<MiddlewareResult> {
const destructiveTools: Array<ToolName> = ["write_to_file", "edit", "apply_diff", "execute_command"]
if (!destructiveTools.includes(toolName)) {
return { allow: true }
}
const selectedIntentId = task.getSelectedIntentId()
if (!selectedIntentId) {
return { allow: false, error: "No active intent selected" }
}
const provider = task.providerRef.deref()
if (!provider) {
return { allow: false, error: "Provider unavailable" }
}
try {
const intentLoader = provider.getIntentLoader()
await intentLoader.ensureLoaded()
const intent = intentLoader.getIntent(selectedIntentId)
if (!intent) {
return { allow: false, error: `Intent '${selectedIntentId}' not found` }
}
if (toolName === "write_to_file" && params.path) {
if (intent.owned_scopes?.length) {
const isAuthorized = intent.owned_scopes.some(
(scope) => params.path.startsWith(scope) || params.path === scope,
)
if (!isAuthorized) {
return {
allow: false,
error: `Scope Violation: ${selectedIntentId} not authorized to edit ${params.path}`,
}
}
}
}
return { allow: true }
} catch (error) {
return {
allow: false,
error: `Error validating scope: ${error instanceof Error ? error.message : String(error)}`,
}
}
}
}

View file

@ -0,0 +1,13 @@
import { Task } from "../task/Task"
export interface MiddlewareResult {
allow: boolean
error?: string
modifiedParams?: any
}
export interface ToolMiddleware {
name: string
beforeExecute?(params: any, task: Task, toolName: string): Promise<MiddlewareResult>
afterExecute?(result: any, task: Task, toolName: string): Promise<MiddlewareResult>
}

View file

@ -199,6 +199,13 @@ Otherwise, if you have not completed the task and do not need additional informa
const prettyPatchLines = lines.slice(4)
return prettyPatchLines.join("\n")
},
invalidIntentId: (intentId: string) =>
JSON.stringify({
status: "error",
type: "invalid_intent",
intent_id: intentId,
}),
}
// to avoid circular dependency

View file

@ -8,3 +8,5 @@ export { getCapabilitiesSection } from "./capabilities"
export { getModesSection } from "./modes"
export { markdownFormattingSection } from "./markdown-formatting"
export { getSkillsSection } from "./skills"
export { intentIndexSection } from "./intent-index"
export { intentProtocolSection } from "./intent-protocol"

View file

@ -0,0 +1,16 @@
import { Intent } from "../../intents/types"
export function intentIndexSection(intents: Intent[]) {
const rows = (intents ?? []).slice(0, 30).map((i) => {
const scope = (i.owned_scopes ?? []).slice(0, 4).join(", ")
return `- ${i.id}: ${i.name} [${i.status}] scope: ${scope}${(i.owned_scopes?.length ?? 0) > 4 ? ", ..." : ""}`
})
const extra =
(intents?.length ?? 0) > 30 ? `\n(Showing 30 of ${intents.length}. Use list_active_intents for full list.)` : ""
return `
[ACTIVE INTENT INDEX]
${rows.length ? rows.join("\n") : "- (none found)"}${extra}
`.trim()
}

View file

@ -0,0 +1,19 @@
export function intentProtocolSection(): string {
return `
===
INTENT-DRIVEN PROTOCOL
You are an Intent-Driven Architect. You MUST follow this two-step process before making any changes:
You CANNOT write code or modify files until you have successfully selected an intent. The intent context will provide you with the scope, constraints, and acceptance criteria for your work.1. You MUST call the tool "select_active_intent" before making any code edits or running destructive commands.
1. First, call list_active_intents to discover all available intents
2. Then, call select_active_intent(intent_id) with the appropriate intent ID
3. You may call read-only tools before selecting an intent, but you must select an intent before:
- apply_diff / write_to_file / edit_file / apply_patch
- execute_command that changes the repo (git commit, installs, deletions, etc.)
4. If you are unsure which intent applies, request the list of intents from the user or consult the intent index below.
5. After selecting an intent, you must keep all actions within owned_scope and obey constraints.
`.trim()
}

View file

@ -23,6 +23,8 @@ import {
addCustomInstructions,
markdownFormattingSection,
getSkillsSection,
intentProtocolSection,
intentIndexSection,
} from "./sections"
// Helper function to get prompt component, filtering out empty objects
@ -92,6 +94,8 @@ ${getSharedToolUseSection()}${toolsCatalog}
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
${intentProtocolSection()}
${modesSection}
${skillsSection ? `\n${skillsSection}` : ""}
${getRulesSection(cwd, settings)}

View file

@ -21,6 +21,7 @@ import switchMode from "./switch_mode"
import updateTodoList from "./update_todo_list"
import writeToFile from "./write_to_file"
import selectActiveIntent from "./select_active_intent"
import listActiveIntents from "./list_active_intents"
export { getMcpServerTools } from "./mcp_server"
export { convertOpenAIToolToAnthropic, convertOpenAIToolsToAnthropic } from "./converters"
@ -70,6 +71,7 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch
updateTodoList,
writeToFile,
selectActiveIntent,
listActiveIntents,
] satisfies OpenAI.Chat.ChatCompletionTool[]
}

View file

@ -0,0 +1,33 @@
import type OpenAI from "openai"
const LIST_ACTIVE_INTENTS_DESCRIPTION = `List all currently active intents available for selection. Use this tool when you need to choose which intent to work on before making any code changes.
This tool reads the workspace intent registry (typically .orchestration/active_intents.json) and returns a compact list of intents with:
- id
- name
- status
- owned_scope (high-level scope paths)
- constraints (short summary)
- acceptance_criteria (short summary)
When to use:
- At the start of a task, before calling select_active_intent
- When you are unsure which intent ID applies
- When select_active_intent fails due to an invalid ID
Example: List all available active intents
{}`
export default {
type: "function",
function: {
name: "list_active_intents",
description: LIST_ACTIVE_INTENTS_DESCRIPTION,
parameters: {
type: "object",
properties: {},
additionalProperties: false,
required: [],
},
},
} satisfies OpenAI.Chat.ChatCompletionTool

View file

@ -1,7 +1,20 @@
import type OpenAI from "openai"
const SELECT_ACTIVE_INTENT_DESCRIPTION = ` `
const INTENT_ID_DESCRIPTION = ``
const SELECT_ACTIVE_INTENT_DESCRIPTION = `Select and activate a specific intent by ID. This MUST be called before making any code changes.
After selecting an intent, all subsequent work should stay within the intent's owned_scope and obey its constraints. The tool returns an <intent_context> block containing the intent details for prompt injection.
When to use:
- Immediately after list_active_intents, once you choose the correct ID
- At the start of a new task or when switching to a different intent
Parameters:
- intent_id: (required) The ID of the intent to activate (e.g., "INT-001")
Example: Activate intent INT-001
{ "intent_id": "INT-001" }`
const INTENT_ID_DESCRIPTION = `Intent ID to activate (e.g., "INT-001"). Choose from list_active_intents results.`
export default {
type: "function",
@ -12,11 +25,13 @@ export default {
parameters: {
type: "object",
properties: {
path: {
intent_id: {
type: "string",
description: INTENT_ID_DESCRIPTION,
},
},
required: ["intent_id"],
additionalProperties: false,
},
},
} satisfies OpenAI.Chat.ChatCompletionTool

View file

@ -350,6 +350,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam | Anthropic.ToolResultBlockParam)[] = []
userMessageContentReady = false
// Intent
private selectedIntentId?: string
private hasSelectedIntent = false
/**
* Flag indicating whether the assistant message for the current streaming session
* has been saved to API conversation history.
@ -859,6 +863,31 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
return [instance, promise]
}
private async validateIntentGate(): Promise<boolean> {
const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1]
const lastMessageContent = lastMessage?.content
if (Array.isArray(lastMessageContent)) {
const hasIntentSection = lastMessageContent.some((block) => {
return (
block.type === "tool_use" &&
(block.name === "select_active_intent" || block.name === "list_active_intents")
)
})
if (!hasIntentSection && !this.selectedIntentId) {
await this.say(
"error",
"You must cite a valid Intent ID. Use select_active_intent or list_active_intents first.",
)
return false
}
return true
}
return false
}
// API Messages
private async getSavedApiConversationHistory(): Promise<ApiMessage[]> {
@ -1263,6 +1292,19 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
return undefined
}
public setSelectedIntent(intentId: string): void {
this.selectedIntentId = intentId
this.hasSelectedIntent = true
}
public getHasSelectedIntent(): boolean {
return this.hasSelectedIntent
}
public getSelectedIntentId(): string | undefined {
return this.selectedIntentId
}
// Note that `partial` has three valid states true (partial message),
// false (completion of partial message), undefined (individual complete
// message).
@ -2776,6 +2818,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const streamModelInfo = this.cachedStreamingModel.info
const cachedModelId = this.cachedStreamingModel.id
const hasValidIntent = await this.validateIntentGate()
if (!hasValidIntent) {
return true
}
// Yields only if the first chunk is successful, otherwise will
// allow the user to retry the request (most likely due to rate
// limit error, which gets thrown on the first chunk).
@ -4008,6 +4055,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
Task.lastGlobalApiRequestTime = performance.now()
const systemPrompt = await this.getSystemPrompt()
const { contextTokens } = this.getTokenUsage()
if (contextTokens) {

View file

@ -0,0 +1,46 @@
import { Task } from "../task/Task"
import type { Intent } from "../intents/types"
import { BaseTool, ToolCallbacks } from "./BaseTool"
import { ToolUse } from "../../shared/tools"
export class ListActiveIntents extends BaseTool<"list_active_intents"> {
readonly name = "list_active_intents" as const
async execute(_params: any, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { pushToolResult, handleError } = callbacks
try {
const provider = task.providerRef.deref()
if (!provider) {
// TODO: Figure out what to do when provider becomes undefined
return
}
const intentLoader = provider.getIntentLoader()
await intentLoader.ensureLoaded()
const intents = intentLoader.getAllIntents()
if (intents.length === 0) {
pushToolResult("No active intents found in .orchestration/active_intents.json")
return
}
pushToolResult(`Available intents:\n\n${this.formatIntentList(intents)}`)
} catch (error) {
handleError("list active intents", error)
}
}
private formatIntentList(intents: Array<Intent>) {
return intents
.map(
(intent) =>
`- ${intent.id}: ${intent.name} (${intent.status})\n Scope: ${intent.owned_scopes?.join(", ") || "None"}\n Constraints: ${intent.constraints?.join(", ") || "None"}`,
)
.join("\n\n")
}
override async handlePartial(_task: Task, _block: ToolUse<"list_active_intents">): Promise<void> {}
}
export const listActiveIntentsTool = new ListActiveIntents()

View file

@ -1,16 +1,86 @@
import { Task } from "../task/Task"
import { BaseTool, ToolCallbacks } from "./BaseTool"
import { Intent } from "../intents/types"
import { formatResponse } from "../prompts/responses"
import { ToolUse } from "../../shared/tools"
interface SelectActiveIntentParams {
intent_id: string
}
export class SelectActiveIntent extends BaseTool<"select_active_intent"> {
export class SelectActiveIntentTool extends BaseTool<"select_active_intent"> {
readonly name = "select_active_intent" as const
override execute(_params: SelectActiveIntentParams, _task: Task, _callbacks: ToolCallbacks): Promise<void> {
throw new Error("Method not implemented.")
async execute(params: SelectActiveIntentParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { intent_id } = params
const { handleError, pushToolResult } = callbacks
try {
if (!intent_id) {
task.consecutiveMistakeCount++
task.recordToolError("select_active_intent")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("select_active_intent", "intent_id"))
return
}
const provider = task.providerRef.deref()
if (!provider) {
// TODO: Figure out what to do when the provider becomes undefined
return
}
const intentLoader = provider.getIntentLoader()
await intentLoader.ensureLoaded()
const intent = intentLoader.getIntent(intent_id)
if (!intent) {
task.setSelectedIntent(intent_id)
pushToolResult(formatResponse.invalidIntentId(intent_id))
return
}
pushToolResult(this.formatIntentContextXml(intent))
} catch (error) {
handleError("selecting intents", error)
}
}
/**
* Formats the selected intent as an <intent_context> XML block for prompt injection.
* Keep this deterministic and safe (escape XML).
*/
private formatIntentContextXml(intent: Intent): string {
const escapeXml = (text: string): string =>
String(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")
const ownedScopes = intent.owned_scopes ?? []
const renderList = (containerTag: string, itemTag: string, items: string[]): string => {
const inner = (items ?? []).map((x) => ` <${itemTag}>${escapeXml(x)}</${itemTag}>`).join("\n")
return items && items.length
? `<${containerTag}>\n${inner}\n </${containerTag}>`
: `<${containerTag}></${containerTag}>`
}
return [
`<intent_context>`,
` <id>${escapeXml(intent.id)}</id>`,
` <name>${escapeXml(intent.name)}</name>`,
` <status>${escapeXml(intent.status as unknown as string)}</status>`,
` ${renderList("owned_scope", "path", ownedScopes)}`,
` ${renderList("constraints", "constraint", intent.constraints ?? [])}`,
` ${renderList("acceptance_criteria", "criteria", intent.acceptance_criteria ?? [])}`,
`</intent_context>`,
].join("\n")
}
override async handlePartial(_task: Task, _block: ToolUse<"select_active_intent">): Promise<void> {}
}
export const selectActiveIntentTool = new SelectActiveIntent()
export const selectActiveIntentTool = new SelectActiveIntentTool()

View file

@ -103,6 +103,7 @@ import { getNonce } from "./getNonce"
import { getUri } from "./getUri"
import { REQUESTY_BASE_URL } from "../../shared/utils/requesty"
import { validateAndFixToolResultIds } from "../task/validateToolResultIds"
import { IntentLoader } from "../intents/IntentLoader"
/**
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@ -170,6 +171,11 @@ export class ClineProvider
public readonly providerSettingsManager: ProviderSettingsManager
public readonly customModesManager: CustomModesManager
/**
* Intent Loader
*/
private intentLoader?: IntentLoader
constructor(
readonly context: vscode.ExtensionContext,
private readonly outputChannel: vscode.OutputChannel,
@ -420,6 +426,14 @@ export class ClineProvider
}
}
public getIntentLoader(): IntentLoader {
if (!this.intentLoader) {
const cwd = this.currentWorkspacePath || process.cwd()
this.intentLoader = new IntentLoader(cwd)
}
return this.intentLoader
}
// Adds a new Task instance to clineStack, marking the start of a new task.
// The instance is pushed to the top of the stack (LIFO order).
// When the task is completed, the top instance is removed, reactivating the
@ -659,6 +673,7 @@ export class ClineProvider
}
}
this.intentLoader = undefined
this._workspaceTracker?.dispose()
this._workspaceTracker = undefined
await this.mcpHub?.unregisterClient()

View file

@ -1,25 +1 @@
import { Task } from "../core/task/Task"
import { OrchestrationStore } from "../orchestration/OrchestrationStore"
interface HookEngineOptions {
task: Task
}
export class HookEngine {
private readonly store: OrchestrationStore
constructor({ task }: HookEngineOptions) {
this.store = new OrchestrationStore({ workspaceRoot: task.cwd })
}
/** Called before any tool execution */
preToolHook() {
this.store.ensureInitialized()
}
/** Called after any tool execution */
postToolHook() {}
/** */
preLLMHook() {}
}
export class HookEngine {}

View file

@ -115,6 +115,9 @@ export type NativeToolArgs = {
update_todo_list: { todos: string }
use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record<string, unknown> }
write_to_file: { path: string; content: string }
select_active_intent: { intent_id: string }
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
list_active_intents: {}
// Add more tools as they are migrated to native protocol
}
@ -290,12 +293,20 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
generate_image: "generate images",
custom_tool: "use custom tools",
select_active_intent: "select the active intent",
list_active_intents: "list active intents",
} as const
// Define available tool groups.
export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
read: {
tools: ["read_file", "search_files", "list_files", "codebase_search"],
tools: [
"read_file",
"search_files",
"list_files",
"codebase_search",
"select_active_intent",
"list_active_intents",
],
},
edit: {
tools: ["apply_diff", "write_to_file", "generate_image"],
@ -322,6 +333,8 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [
"update_todo_list",
"run_slash_command",
"skill",
"select_active_intent",
"list_active_intents",
] as const
/**