2025-07-25 01:20:43 +00:00
import { Config } from "../config/config"
2025-10-26 19:50:41 +00:00
import z from "zod"
2025-07-25 01:20:43 +00:00
import { Provider } from "../provider/provider"
import { generateObject , type ModelMessage } from "ai"
import PROMPT_GENERATE from "./generate.txt"
import { SystemPrompt } from "../session/system"
2025-09-01 21:15:49 +00:00
import { Instance } from "../project/instance"
2025-08-12 15:39:39 +00:00
import { mergeDeep } from "remeda"
2025-07-25 01:20:43 +00:00
export namespace Agent {
export const Info = z
. object ( {
name : z.string ( ) ,
2025-08-07 20:32:12 +00:00
description : z.string ( ) . optional ( ) ,
2025-11-17 06:06:40 +00:00
mode : z.enum ( [ "subagent" , "primary" , "all" ] ) ,
2025-08-14 22:58:44 +00:00
builtIn : z.boolean ( ) ,
2025-08-07 20:32:12 +00:00
topP : z.number ( ) . optional ( ) ,
temperature : z.number ( ) . optional ( ) ,
2025-11-12 00:32:44 +00:00
color : z.string ( ) . optional ( ) ,
2025-08-12 15:39:39 +00:00
permission : z.object ( {
edit : Config.Permission ,
bash : z.record ( z . string ( ) , Config . Permission ) ,
webfetch : Config.Permission.optional ( ) ,
2025-11-10 02:21:38 +00:00
doom_loop : Config.Permission.optional ( ) ,
external_directory : Config.Permission.optional ( ) ,
2025-08-12 15:39:39 +00:00
} ) ,
2025-07-25 01:20:43 +00:00
model : z
. object ( {
modelID : z.string ( ) ,
providerID : z.string ( ) ,
} )
. optional ( ) ,
prompt : z.string ( ) . optional ( ) ,
2025-09-15 07:12:07 +00:00
tools : z.record ( z . string ( ) , z . boolean ( ) ) ,
2025-08-11 01:34:28 +00:00
options : z.record ( z . string ( ) , z . any ( ) ) ,
2025-07-25 01:20:43 +00:00
} )
2025-09-15 07:12:07 +00:00
. meta ( {
2025-07-25 01:20:43 +00:00
ref : "Agent" ,
} )
export type Info = z . infer < typeof Info >
2025-08-07 20:32:12 +00:00
2025-09-01 21:15:49 +00:00
const state = Instance . state ( async ( ) = > {
2025-07-25 01:20:43 +00:00
const cfg = await Config . get ( )
2025-08-17 02:51:56 +00:00
const defaultTools = cfg . tools ? ? { }
2025-08-12 15:39:39 +00:00
const defaultPermission : Info [ "permission" ] = {
edit : "allow" ,
bash : {
"*" : "allow" ,
} ,
webfetch : "allow" ,
2025-11-10 02:21:38 +00:00
doom_loop : "ask" ,
external_directory : "ask" ,
2025-08-12 15:39:39 +00:00
}
2025-08-13 13:01:17 +00:00
const agentPermission = mergeAgentPermissions ( defaultPermission , cfg . permission ? ? { } )
2025-08-21 22:25:31 +00:00
const planPermission = mergeAgentPermissions (
{
2025-09-09 03:00:14 +00:00
edit : "deny" ,
2025-10-23 22:11:08 +00:00
bash : {
"cut*" : "allow" ,
"diff*" : "allow" ,
"du*" : "allow" ,
"file *" : "allow" ,
"find * -delete*" : "ask" ,
"find * -exec*" : "ask" ,
"find * -fprint*" : "ask" ,
"find * -fls*" : "ask" ,
"find * -fprintf*" : "ask" ,
"find * -ok*" : "ask" ,
"find *" : "allow" ,
"git diff*" : "allow" ,
"git log*" : "allow" ,
"git show*" : "allow" ,
"git status*" : "allow" ,
"git branch" : "allow" ,
"git branch -v" : "allow" ,
"grep*" : "allow" ,
"head*" : "allow" ,
"less*" : "allow" ,
"ls*" : "allow" ,
"more*" : "allow" ,
"pwd*" : "allow" ,
"rg*" : "allow" ,
"sort --output=*" : "ask" ,
"sort -o *" : "ask" ,
"sort*" : "allow" ,
"stat*" : "allow" ,
"tail*" : "allow" ,
"tree -o *" : "ask" ,
"tree*" : "allow" ,
"uniq*" : "allow" ,
"wc*" : "allow" ,
"whereis*" : "allow" ,
"which*" : "allow" ,
"*" : "ask" ,
} ,
2025-08-21 22:25:31 +00:00
webfetch : "allow" ,
} ,
cfg . permission ? ? { } ,
)
2025-07-25 01:20:43 +00:00
const result : Record < string , Info > = {
general : {
name : "general" ,
2025-11-29 21:40:08 +00:00
description : ` General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel. ` ,
2025-07-25 01:20:43 +00:00
tools : {
todoread : false ,
todowrite : false ,
2025-08-17 02:51:56 +00:00
. . . defaultTools ,
2025-07-25 01:20:43 +00:00
} ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-07 20:32:12 +00:00
mode : "subagent" ,
2025-11-29 02:13:07 +00:00
builtIn : true ,
} ,
explore : {
name : "explore" ,
tools : {
todoread : false ,
todowrite : false ,
edit : false ,
write : false ,
. . . defaultTools ,
} ,
2025-11-29 02:47:39 +00:00
description : ` Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions. ` ,
2025-11-29 02:13:07 +00:00
prompt : [
` You are a file search specialist. You excel at thoroughly navigating and exploring codebases. ` ,
` ` ,
` Your strengths: ` ,
` - Rapidly finding files using glob patterns ` ,
` - Searching code and text with powerful regex patterns ` ,
` - Reading and analyzing file contents ` ,
` ` ,
` Guidelines: ` ,
` - Use Glob for broad file pattern matching ` ,
` - Use Grep for searching file contents with regex ` ,
` - Use Read when you know the specific file path you need to read ` ,
` - Use Bash for file operations like copying, moving, or listing directory contents ` ,
` - Adapt your search approach based on the thoroughness level specified by the caller ` ,
` - Return file paths as absolute paths in your final response ` ,
` - For clear communication, avoid using emojis ` ,
` - Do not create any files, or run bash commands that modify the user's system state in any way ` ,
` ` ,
` Complete the user's search request efficiently and report your findings clearly. ` ,
] . join ( "\n" ) ,
options : { } ,
permission : agentPermission ,
mode : "subagent" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-08-07 20:32:12 +00:00
} ,
build : {
name : "build" ,
2025-08-17 02:51:56 +00:00
tools : { . . . defaultTools } ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-07 20:32:12 +00:00
mode : "primary" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-08-07 20:32:12 +00:00
} ,
plan : {
name : "plan" ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-21 22:25:31 +00:00
permission : planPermission ,
2025-08-07 20:32:12 +00:00
tools : {
2025-08-17 02:51:56 +00:00
. . . defaultTools ,
2025-08-07 20:32:12 +00:00
} ,
mode : "primary" ,
2025-08-14 22:58:44 +00:00
builtIn : true ,
2025-07-25 01:20:43 +00:00
} ,
}
for ( const [ key , value ] of Object . entries ( cfg . agent ? ? { } ) ) {
2025-08-05 10:20:00 +00:00
if ( value . disable ) {
delete result [ key ]
continue
}
2025-07-25 01:20:43 +00:00
let item = result [ key ]
if ( ! item )
item = result [ key ] = {
name : key ,
2025-08-07 20:32:12 +00:00
mode : "all" ,
2025-08-13 13:01:17 +00:00
permission : agentPermission ,
2025-08-11 00:30:25 +00:00
options : { } ,
2025-08-07 20:32:12 +00:00
tools : { } ,
2025-08-14 22:58:44 +00:00
builtIn : false ,
2025-07-25 01:20:43 +00:00
}
2025-11-12 00:32:44 +00:00
const { name , model , prompt , tools , description , temperature , top_p , mode , permission , color , . . . extra } = value
2025-08-11 01:34:28 +00:00
item . options = {
. . . item . options ,
. . . extra ,
}
2025-08-12 15:39:39 +00:00
if ( model ) item . model = Provider . parseModel ( model )
if ( prompt ) item . prompt = prompt
if ( tools )
2025-07-25 01:20:43 +00:00
item . tools = {
. . . item . tools ,
2025-08-12 15:39:39 +00:00
. . . tools ,
2025-07-25 01:20:43 +00:00
}
2025-08-17 02:51:56 +00:00
item . tools = {
. . . defaultTools ,
. . . item . tools ,
}
2025-08-12 15:39:39 +00:00
if ( description ) item . description = description
if ( temperature != undefined ) item . temperature = temperature
if ( top_p != undefined ) item . topP = top_p
if ( mode ) item . mode = mode
2025-11-12 00:32:44 +00:00
if ( color ) item . color = color
2025-08-18 23:15:20 +00:00
// just here for consistency & to prevent it from being added as an option
if ( name ) item . name = name
2025-08-12 15:39:39 +00:00
if ( permission ? ? cfg . permission ) {
2025-08-13 13:01:17 +00:00
item . permission = mergeAgentPermissions ( cfg . permission ? ? { } , permission ? ? { } )
2025-08-12 15:39:39 +00:00
}
2025-07-25 01:20:43 +00:00
}
return result
} )
export async function get ( agent : string ) {
return state ( ) . then ( ( x ) = > x [ agent ] )
}
export async function list() {
return state ( ) . then ( ( x ) = > Object . values ( x ) )
}
export async function generate ( input : { description : string } ) {
const defaultModel = await Provider . defaultModel ( )
const model = await Provider . getModel ( defaultModel . providerID , defaultModel . modelID )
const system = SystemPrompt . header ( defaultModel . providerID )
system . push ( PROMPT_GENERATE )
const existing = await list ( )
const result = await generateObject ( {
temperature : 0.3 ,
prompt : [
. . . system . map (
( item ) : ModelMessage = > ( {
role : "system" ,
content : item ,
} ) ,
) ,
{
role : "user" ,
content : ` Create an agent configuration based on this request: \ " ${ input . description } \ ". \ n \ nIMPORTANT: The following identifiers already exist and must NOT be used: ${ existing . map ( ( i ) = > i . name ) . join ( ", " ) } \ n Return ONLY the JSON object, no other text, do not wrap in backticks ` ,
} ,
] ,
model : model.language ,
schema : z.object ( {
identifier : z.string ( ) ,
whenToUse : z.string ( ) ,
systemPrompt : z.string ( ) ,
} ) ,
} )
return result . object
}
}
2025-08-13 13:01:17 +00:00
2025-11-08 01:59:02 +00:00
function mergeAgentPermissions ( basePermission : any , overridePermission : any ) : Agent . Info [ "permission" ] {
2025-10-20 20:15:14 +00:00
if ( typeof basePermission . bash === "string" ) {
basePermission . bash = {
"*" : basePermission . bash ,
}
}
if ( typeof overridePermission . bash === "string" ) {
overridePermission . bash = {
"*" : overridePermission . bash ,
}
}
2025-08-13 13:01:17 +00:00
const merged = mergeDeep ( basePermission ? ? { } , overridePermission ? ? { } ) as any
let mergedBash
if ( merged . bash ) {
if ( typeof merged . bash === "string" ) {
mergedBash = {
"*" : merged . bash ,
}
2025-10-20 20:15:14 +00:00
} else if ( typeof merged . bash === "object" ) {
2025-08-13 13:01:17 +00:00
mergedBash = mergeDeep (
{
2025-10-20 20:15:14 +00:00
"*" : "allow" ,
2025-08-13 13:01:17 +00:00
} ,
merged . bash ,
)
}
}
const result : Agent.Info [ "permission" ] = {
edit : merged.edit ? ? "allow" ,
webfetch : merged.webfetch ? ? "allow" ,
bash : mergedBash ? ? { "*" : "allow" } ,
2025-11-10 02:21:38 +00:00
doom_loop : merged.doom_loop ,
external_directory : merged.external_directory ,
2025-08-13 13:01:17 +00:00
}
return result
}