2025-06-12 21:04:34 +00:00
import { Tool } from "./tool"
import DESCRIPTION from "./task.txt"
2025-10-26 19:50:41 +00:00
import z from "zod"
2025-06-12 21:04:34 +00:00
import { Session } from "../session"
2026-03-11 23:30:17 +00:00
import { SessionID , MessageID } from "../session/schema"
2025-07-07 19:53:43 +00:00
import { MessageV2 } from "../session/message-v2"
2025-07-13 21:22:11 +00:00
import { Identifier } from "../id/id"
2025-07-25 01:20:43 +00:00
import { Agent } from "../agent/agent"
2025-09-13 09:46:14 +00:00
import { SessionPrompt } from "../session/prompt"
2025-11-19 06:17:26 +00:00
import { iife } from "@/util/iife"
2025-11-17 15:57:18 +00:00
import { defer } from "@/util/defer"
2025-12-03 17:19:43 +00:00
import { Config } from "../config/config"
2026-03-18 01:59:54 +00:00
import { PermissionNext } from "@/permission"
2026-01-07 04:29:17 +00:00
2026-01-07 19:28:13 +00:00
const parameters = z . object ( {
description : z.string ( ) . describe ( "A short (3-5 words) description of the task" ) ,
prompt : z.string ( ) . describe ( "The task for the agent to perform" ) ,
subagent_type : z.string ( ) . describe ( "The type of specialized agent to use for this task" ) ,
2026-02-05 06:04:03 +00:00
task_id : z
. string ( )
. describe (
"This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)" ,
)
. optional ( ) ,
2026-01-07 19:28:13 +00:00
command : z.string ( ) . describe ( "The command that triggered this task" ) . optional ( ) ,
} )
2026-01-07 04:29:17 +00:00
2026-01-07 19:28:13 +00:00
export const TaskTool = Tool . define ( "task" , async ( ctx ) = > {
2025-08-07 20:32:12 +00:00
const agents = await Agent . list ( ) . then ( ( x ) = > x . filter ( ( a ) = > a . mode !== "primary" ) )
2026-01-07 19:28:13 +00:00
// Filter agents by permissions if agent provided
const caller = ctx ? . agent
const accessibleAgents = caller
? agents . filter ( ( a ) = > PermissionNext . evaluate ( "task" , a . name , caller . permission ) . action !== "deny" )
: agents
2026-03-19 20:04:03 +00:00
const list = accessibleAgents . toSorted ( ( a , b ) = > a . name . localeCompare ( b . name ) )
2026-01-07 19:28:13 +00:00
2025-08-07 20:32:12 +00:00
const description = DESCRIPTION . replace (
"{agents}" ,
2026-03-19 20:04:03 +00:00
list . map ( ( a ) = > ` - ${ a . name } : ${ a . description ? ? "This subagent should only be called manually by the user." } ` ) . join (
"\n" ,
) ,
2025-08-07 20:32:12 +00:00
)
2025-07-25 01:20:43 +00:00
return {
description ,
2026-01-07 19:28:13 +00:00
parameters ,
async execute ( params : z.infer < typeof parameters > , ctx ) {
2026-01-01 22:54:11 +00:00
const config = await Config . get ( )
2026-01-07 04:29:17 +00:00
2026-01-07 19:28:13 +00:00
// Skip permission check when user explicitly invoked via @ or command subtask
if ( ! ctx . extra ? . bypassAgentCheck ) {
2026-01-06 23:05:09 +00:00
await ctx . ask ( {
permission : "task" ,
patterns : [ params . subagent_type ] ,
always : [ "*" ] ,
metadata : {
description : params.description ,
subagent_type : params.subagent_type ,
} ,
} )
}
2026-01-01 22:54:11 +00:00
2025-07-25 01:20:43 +00:00
const agent = await Agent . get ( params . subagent_type )
2025-11-08 01:59:02 +00:00
if ( ! agent ) throw new Error ( ` Unknown agent type: ${ params . subagent_type } is not a valid agent type ` )
2026-01-13 02:37:42 +00:00
const hasTaskPermission = agent . permission . some ( ( rule ) = > rule . permission === "task" )
2025-11-19 06:17:26 +00:00
const session = await iife ( async ( ) = > {
2026-02-05 06:04:03 +00:00
if ( params . task_id ) {
2026-03-11 23:16:56 +00:00
const found = await Session . get ( SessionID . make ( params . task_id ) ) . catch ( ( ) = > { } )
2025-11-19 06:17:26 +00:00
if ( found ) return found
}
return await Session . create ( {
parentID : ctx.sessionID ,
title : params.description + ` (@ ${ agent . name } subagent) ` ,
2026-01-01 22:54:11 +00:00
permission : [
{
permission : "todowrite" ,
pattern : "*" ,
action : "deny" ,
} ,
{
permission : "todoread" ,
pattern : "*" ,
action : "deny" ,
} ,
2026-01-13 02:37:42 +00:00
. . . ( hasTaskPermission
? [ ]
: [
{
permission : "task" as const ,
pattern : "*" as const ,
action : "deny" as const ,
} ,
] ) ,
2026-01-01 22:54:11 +00:00
. . . ( config . experimental ? . primary_tools ? . map ( ( t ) = > ( {
pattern : "*" ,
action : "allow" as const ,
permission : t ,
} ) ) ? ? [ ] ) ,
] ,
2025-11-19 06:17:26 +00:00
} )
2025-10-06 23:37:30 +00:00
} )
2025-11-07 19:46:58 +00:00
const msg = await MessageV2 . get ( { sessionID : ctx.sessionID , messageID : ctx.messageID } )
2025-08-15 15:16:08 +00:00
if ( msg . info . role !== "assistant" ) throw new Error ( "Not an assistant message" )
2025-10-30 17:15:21 +00:00
2026-01-24 05:10:40 +00:00
const model = agent . model ? ? {
modelID : msg.info.modelID ,
providerID : msg.info.providerID ,
}
2025-10-30 17:15:21 +00:00
ctx . metadata ( {
title : params.description ,
metadata : {
sessionId : session.id ,
2026-01-24 05:10:40 +00:00
model ,
2025-10-30 17:15:21 +00:00
} ,
} )
2026-03-11 23:30:17 +00:00
const messageID = MessageID . ascending ( )
2025-07-25 01:20:43 +00:00
2025-11-17 15:57:18 +00:00
function cancel() {
SessionPrompt . cancel ( session . id )
}
ctx . abort . addEventListener ( "abort" , cancel )
using _ = defer ( ( ) = > ctx . abort . removeEventListener ( "abort" , cancel ) )
2025-11-12 01:38:50 +00:00
const promptParts = await SessionPrompt . resolvePromptParts ( params . prompt )
2025-12-03 17:19:43 +00:00
2025-09-13 09:46:14 +00:00
const result = await SessionPrompt . prompt ( {
2025-07-25 01:20:43 +00:00
messageID ,
sessionID : session.id ,
2025-09-01 21:15:49 +00:00
model : {
modelID : model.modelID ,
providerID : model.providerID ,
} ,
2025-08-07 20:32:12 +00:00
agent : agent.name ,
2025-07-25 17:29:29 +00:00
tools : {
2025-08-07 20:32:12 +00:00
todowrite : false ,
todoread : false ,
2026-01-13 02:37:42 +00:00
. . . ( hasTaskPermission ? { } : { task : false } ) ,
2025-12-03 17:19:43 +00:00
. . . Object . fromEntries ( ( config . experimental ? . primary_tools ? ? [ ] ) . map ( ( t ) = > [ t , false ] ) ) ,
2025-07-25 17:29:29 +00:00
} ,
2025-11-12 01:38:50 +00:00
parts : promptParts ,
2026-01-28 06:04:43 +00:00
} )
2025-11-19 06:17:26 +00:00
const text = result . parts . findLast ( ( x ) = > x . type === "text" ) ? . text ? ? ""
2026-02-05 06:04:03 +00:00
const output = [
` task_id: ${ session . id } (for resuming to continue this task if needed) ` ,
"" ,
"<task_result>" ,
text ,
"</task_result>" ,
] . join ( "\n" )
2025-11-19 06:17:26 +00:00
2025-07-25 01:20:43 +00:00
return {
2025-06-18 17:40:21 +00:00
title : params.description ,
2025-07-07 19:53:43 +00:00
metadata : {
2025-10-30 17:15:21 +00:00
sessionId : session.id ,
2026-01-24 05:10:40 +00:00
model ,
2025-06-12 21:04:34 +00:00
} ,
2025-11-19 06:17:26 +00:00
output ,
2025-07-25 01:20:43 +00:00
}
} ,
}
2025-06-12 21:04:34 +00:00
} )