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-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-12-03 17:19:43 +00:00
import { Config } from "../config/config"
2026-04-07 23:48:12 +00:00
import { Effect } from "effect"
2026-04-09 15:56:19 +00:00
import { Log } from "@/util/log"
2026-01-07 04:29:17 +00:00
2026-04-08 23:02:19 +00:00
const id = "task"
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" ) ,
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 ( ) ,
command : z.string ( ) . describe ( "The command that triggered this task" ) . optional ( ) ,
} )
export const TaskTool = Tool . defineEffect (
id ,
Effect . gen ( function * ( ) {
const agent = yield * Agent . Service
const config = yield * Config . Service
const run = Effect . fn ( "TaskTool.execute" ) ( function * ( params : z.infer < typeof parameters > , ctx : Tool.Context ) {
const cfg = yield * config . get ( )
2026-01-07 04:29:17 +00:00
2026-01-07 19:28:13 +00:00
if ( ! ctx . extra ? . bypassAgentCheck ) {
2026-04-08 23:02:19 +00:00
yield * Effect . promise ( ( ) = >
ctx . ask ( {
permission : id ,
patterns : [ params . subagent_type ] ,
always : [ "*" ] ,
metadata : {
description : params.description ,
subagent_type : params.subagent_type ,
} ,
} ) ,
)
2026-01-06 23:05:09 +00:00
}
2026-01-01 22:54:11 +00:00
2026-04-08 23:02:19 +00:00
const next = yield * agent . get ( params . subagent_type )
if ( ! next ) {
return yield * Effect . fail ( new Error ( ` Unknown agent type: ${ params . subagent_type } is not a valid agent type ` ) )
}
2025-10-30 17:15:21 +00:00
2026-04-08 23:02:19 +00:00
const canTask = next . permission . some ( ( rule ) = > rule . permission === id )
const canTodo = next . permission . some ( ( rule ) = > rule . permission === "todowrite" )
const taskID = params . task_id
const session = taskID
? yield * Effect . promise ( ( ) = > {
const id = SessionID . make ( taskID )
return Session . get ( id ) . catch ( ( ) = > undefined )
} )
: undefined
const nextSession =
session ? ?
( yield * Effect . promise ( ( ) = >
Session . create ( {
parentID : ctx.sessionID ,
title : params.description + ` (@ ${ next . name } subagent) ` ,
permission : [
. . . ( canTodo
? [ ]
: [
{
permission : "todowrite" as const ,
pattern : "*" as const ,
action : "deny" as const ,
} ,
] ) ,
. . . ( canTask
? [ ]
: [
{
permission : id ,
pattern : "*" as const ,
action : "deny" as const ,
} ,
] ) ,
. . . ( cfg . experimental ? . primary_tools ? . map ( ( item ) = > ( {
pattern : "*" ,
action : "allow" as const ,
permission : item ,
} ) ) ? ? [ ] ) ,
] ,
} ) ,
) )
const msg = yield * Effect . sync ( ( ) = > MessageV2 . get ( { sessionID : ctx.sessionID , messageID : ctx.messageID } ) )
if ( msg . info . role !== "assistant" ) return yield * Effect . fail ( new Error ( "Not an assistant message" ) )
const model = next . model ? ? {
2026-01-24 05:10:40 +00:00
modelID : msg.info.modelID ,
providerID : msg.info.providerID ,
}
2025-10-30 17:15:21 +00:00
ctx . metadata ( {
title : params.description ,
metadata : {
2026-04-08 23:02:19 +00:00
sessionId : nextSession.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() {
2026-04-08 23:02:19 +00:00
SessionPrompt . cancel ( nextSession . id )
2025-11-17 15:57:18 +00:00
}
2025-11-19 06:17:26 +00:00
2026-04-08 23:02:19 +00:00
return yield * Effect . acquireUseRelease (
Effect . sync ( ( ) = > {
ctx . abort . addEventListener ( "abort" , cancel )
} ) ,
( ) = >
Effect . gen ( function * ( ) {
const parts = yield * Effect . promise ( ( ) = > SessionPrompt . resolvePromptParts ( params . prompt ) )
const result = yield * Effect . promise ( ( ) = >
SessionPrompt . prompt ( {
messageID ,
sessionID : nextSession.id ,
model : {
modelID : model.modelID ,
providerID : model.providerID ,
} ,
agent : next.name ,
tools : {
. . . ( canTodo ? { } : { todowrite : false } ) ,
. . . ( canTask ? { } : { task : false } ) ,
. . . Object . fromEntries ( ( cfg . experimental ? . primary_tools ? ? [ ] ) . map ( ( item ) = > [ item , false ] ) ) ,
} ,
parts ,
} ) ,
)
return {
title : params.description ,
metadata : {
sessionId : nextSession.id ,
model ,
} ,
output : [
` task_id: ${ nextSession . id } (for resuming to continue this task if needed) ` ,
"" ,
"<task_result>" ,
result . parts . findLast ( ( item ) = > item . type === "text" ) ? . text ? ? "" ,
"</task_result>" ,
] . join ( "\n" ) ,
}
} ) ,
( ) = >
Effect . sync ( ( ) = > {
ctx . abort . removeEventListener ( "abort" , cancel )
} ) ,
)
} )
return {
description : DESCRIPTION ,
parameters ,
async execute ( params : z.infer < typeof parameters > , ctx ) {
return Effect . runPromise ( run ( params , ctx ) )
} ,
}
} ) ,
)