@@ -2,38 +2,36 @@ import * as Tool from "./tool"
22import DESCRIPTION from "./task.txt"
33import { ToolJsonSchema } from "./json-schema"
44import { BackgroundJob } from "@/background/job"
5- import { Bus } from "@/bus"
65import { Session } from "@/session/session"
76import { SessionID , MessageID } from "../session/schema"
87import { MessageV2 } from "../session/message-v2"
98import { Agent } from "../agent/agent"
109import { deriveSubagentSessionPermission } from "../agent/subagent-permissions"
1110import type { SessionPrompt } from "../session/prompt"
12- import { SessionStatus } from "@/session/status"
1311import { Config } from "@/config/config"
14- import { TuiEvent } from "@/cli/cmd/tui/event"
15- import { Cause , Effect , Exit , Option , Schema , Scope } from "effect"
12+ import { Cause , Effect , Exit , Schema , Scope } from "effect"
1613import { EffectBridge } from "@/effect/bridge"
1714import { RuntimeFlags } from "@/effect/runtime-flags"
1815
1916export interface TaskPromptOps {
2017 cancel ( sessionID : SessionID ) : Effect . Effect < void >
2118 resolvePromptParts ( template : string ) : Effect . Effect < SessionPrompt . PromptInput [ "parts" ] >
2219 prompt ( input : SessionPrompt . PromptInput ) : Effect . Effect < MessageV2 . WithParts >
23- loop ( input : SessionPrompt . LoopInput ) : Effect . Effect < MessageV2 . WithParts >
2420}
2521
2622const id = "task"
2723const BACKGROUND_DESCRIPTION = [
2824 "" ,
2925 "" ,
3026 [
31- "Background mode: background=true launches the subagent asynchronously." ,
32- "Use task_status(task_id=..., wait=false) to poll, or wait=true to block until done." ,
27+ "Background mode: background=true launches the subagent asynchronously and returns immediately." ,
28+ "Foreground is the default; use it when you need the result before continuing." ,
29+ "Use background only for independent work that can run while you continue elsewhere." ,
30+ "You will be notified automatically when it finishes." ,
3331 ] . join ( " " ) ,
3432] . join ( "\n" )
3533
36- const BaseParameters = Schema . Struct ( {
34+ const BaseParameterFields = {
3735 description : Schema . String . annotate ( { description : "A short (3-5 words) description of the task" } ) ,
3836 prompt : Schema . String . annotate ( { description : "The task for the agent to perform" } ) ,
3937 subagent_type : Schema . String . annotate ( { description : "The type of specialized agent to use for this task" } ) ,
@@ -42,40 +40,32 @@ const BaseParameters = Schema.Struct({
4240 "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)" ,
4341 } ) ,
4442 command : Schema . optional ( Schema . String ) . annotate ( { description : "The command that triggered this task" } ) ,
45- } )
43+ }
44+
45+ const BaseParameters = Schema . Struct ( BaseParameterFields )
4646
4747export const Parameters = Schema . Struct ( {
48- description : Schema . String . annotate ( { description : "A short (3-5 words) description of the task" } ) ,
49- prompt : Schema . String . annotate ( { description : "The task for the agent to perform" } ) ,
50- subagent_type : Schema . String . annotate ( { description : "The type of specialized agent to use for this task" } ) ,
51- task_id : Schema . optional ( Schema . String ) . annotate ( {
52- description :
53- "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)" ,
54- } ) ,
55- command : Schema . optional ( Schema . String ) . annotate ( { description : "The command that triggered this task" } ) ,
48+ ...BaseParameterFields ,
5649 background : Schema . optional ( Schema . Boolean ) . annotate ( {
57- description : "When true, launch the subagent in the background and return immediately " ,
50+ description : "Run the agent in the background. You will be notified when it completes. " ,
5851 } ) ,
5952} )
6053
6154function output ( sessionID : SessionID , text : string ) {
62- return [
63- `task_id: ${ sessionID } (for resuming to continue this task if needed)` ,
64- "" ,
65- "<task_result>" ,
66- text ,
67- "</task_result>" ,
68- ] . join ( "\n" )
55+ return [ `<task id="${ sessionID } " state="completed">` , "<task_result>" , text , "</task_result>" , "</task>" ] . join (
56+ "\n" ,
57+ )
6958}
7059
7160function backgroundOutput ( sessionID : SessionID ) {
7261 return [
73- `task_id: ${ sessionID } (for polling this task with task_status)` ,
74- "state: running" ,
75- "" ,
62+ `<task id="${ sessionID } " state="running">` ,
63+ "<summary>Background task started</summary>" ,
7664 "<task_result>" ,
77- "Background task started. Continue your current work and call task_status when you need the result." ,
65+ "Background task started. You will be notified automatically when it finishes; do not poll for progress." ,
66+ "Do not duplicate its work. Continue only with non-overlapping work, or stop if there is nothing else useful to do." ,
7867 "</task_result>" ,
68+ "</task>" ,
7969 ] . join ( "\n" )
8070}
8171
@@ -90,9 +80,14 @@ function backgroundMessage(input: {
9080 input . state === "completed"
9181 ? `Background task completed: ${ input . description } `
9282 : `Background task failed: ${ input . description } `
93- return [ title , `task_id: ${ input . sessionID } ` , `state: ${ input . state } ` , "" , `<${ tag } >` , input . text , `</${ tag } >` ] . join (
94- "\n" ,
95- )
83+ return [
84+ `<task id="${ input . sessionID } " state="${ input . state } ">` ,
85+ `<summary>${ title } </summary>` ,
86+ `<${ tag } >` ,
87+ input . text ,
88+ `</${ tag } >` ,
89+ "</task>" ,
90+ ] . join ( "\n" )
9691}
9792
9893function errorText ( error : unknown ) {
@@ -105,11 +100,9 @@ export const TaskTool = Tool.define(
105100 Effect . gen ( function * ( ) {
106101 const agent = yield * Agent . Service
107102 const background = yield * BackgroundJob . Service
108- const bus = yield * Bus . Service
109103 const config = yield * Config . Service
110104 const sessions = yield * Session . Service
111105 const scope = yield * Scope . Scope
112- const status = yield * SessionStatus . Service
113106 const flags = yield * RuntimeFlags . Service
114107
115108 const run = Effect . fn ( "TaskTool.execute" ) ( function * (
@@ -141,9 +134,8 @@ export const TaskTool = Tool.define(
141134 return yield * Effect . fail ( new Error ( `Unknown agent type: ${ params . subagent_type } is not a valid agent type` ) )
142135 }
143136
144- const taskID = params . task_id
145- const session = taskID
146- ? yield * sessions . get ( SessionID . make ( taskID ) ) . pipe ( Effect . catchCause ( ( ) => Effect . succeed ( undefined ) ) )
137+ const session = params . task_id
138+ ? yield * sessions . get ( SessionID . make ( params . task_id ) ) . pipe ( Effect . catchCause ( ( ) => Effect . succeed ( undefined ) ) )
147139 : undefined
148140 const parent = yield * sessions . get ( ctx . sessionID )
149141 const parentAgent = parent . agent
@@ -189,7 +181,6 @@ export const TaskTool = Tool.define(
189181
190182 const ops = ctx . extra ?. promptOps as TaskPromptOps
191183 if ( ! ops ) return yield * Effect . fail ( new Error ( "TaskTool requires promptOps in ctx.extra" ) )
192- const runCancel = yield * EffectBridge . make ( )
193184
194185 const runTask = Effect . fn ( "TaskTool.runTask" ) ( function * ( ) {
195186 const parts = yield * ops . resolvePromptParts ( params . prompt )
@@ -211,68 +202,34 @@ export const TaskTool = Tool.define(
211202 return result . parts . findLast ( ( item ) => item . type === "text" ) ?. text ?? ""
212203 } )
213204
214- const resumeWhenIdle : ( input : { userID : MessageID ; state : "completed" | "error" } ) => Effect . Effect < void > =
215- Effect . fn ( "TaskTool.resumeWhenIdle" ) ( function * ( input : { userID : MessageID ; state : "completed" | "error" } ) {
216- const latest = yield * sessions
217- . findMessage ( ctx . sessionID , ( item ) => item . info . role === "user" )
218- . pipe ( Effect . orDie )
219- if ( Option . isNone ( latest ) ) return
220- if ( latest . value . info . id !== input . userID ) return
221- if ( ( yield * status . get ( ctx . sessionID ) ) . type !== "idle" ) {
222- yield * Effect . sleep ( "300 millis" )
223- return yield * resumeWhenIdle ( input )
224- }
225- yield * bus . publish ( TuiEvent . ToastShow , {
226- title : input . state === "completed" ? "Background task complete" : "Background task failed" ,
227- message :
228- input . state === "completed"
229- ? `Background task "${ params . description } " finished. Resuming the main thread.`
230- : `Background task "${ params . description } " failed. Resuming the main thread.` ,
231- variant : input . state === "completed" ? "success" : "error" ,
232- duration : 5000 ,
233- } )
234- yield * ops
235- . loop ( { sessionID : ctx . sessionID } )
236- . pipe ( Effect . ignore , Effect . forkIn ( scope , { startImmediately : true } ) )
237- } )
238-
239- const continueIfIdle = Effect . fn ( "TaskTool.continueIfIdle" ) ( function * ( input : {
240- userID : MessageID
241- state : "completed" | "error"
242- } ) {
243- yield * resumeWhenIdle ( input ) . pipe ( Effect . ignore , Effect . forkIn ( scope , { startImmediately : true } ) )
244- } )
245-
246205 const inject = Effect . fn ( "TaskTool.injectBackgroundResult" ) ( function * (
247206 state : "completed" | "error" ,
248207 text : string ,
249208 ) {
250209 const currentParent = yield * sessions . get ( ctx . sessionID )
251- const message = yield * ops . prompt ( {
252- sessionID : ctx . sessionID ,
253- noReply : true ,
254- agent : currentParent . agent ?? ctx . agent ,
255- parts : [
256- {
257- type : "text" ,
258- synthetic : true ,
259- text : backgroundMessage ( {
260- sessionID : nextSession . id ,
261- description : params . description ,
262- state,
263- text,
264- } ) ,
265- } ,
266- ] ,
267- } )
268- yield * continueIfIdle ( { userID : message . info . id , state } )
210+ yield * ops
211+ . prompt ( {
212+ sessionID : ctx . sessionID ,
213+ agent : currentParent . agent ?? ctx . agent ,
214+ parts : [
215+ {
216+ type : "text" ,
217+ synthetic : true ,
218+ text : backgroundMessage ( {
219+ sessionID : nextSession . id ,
220+ description : params . description ,
221+ state,
222+ text,
223+ } ) ,
224+ } ,
225+ ] ,
226+ } )
227+ . pipe ( Effect . ignore , Effect . forkIn ( scope , { startImmediately : true } ) )
269228 } )
270229
271230 const existing = yield * background . get ( nextSession . id )
272231 if ( existing ?. status === "running" ) {
273- return yield * Effect . fail (
274- new Error ( `Task ${ nextSession . id } is already running. Use task_status to check progress.` ) ,
275- )
232+ return yield * Effect . fail ( new Error ( `Task ${ nextSession . id } is already running.` ) )
276233 }
277234
278235 if ( runInBackground ) {
@@ -302,6 +259,7 @@ export const TaskTool = Tool.define(
302259 }
303260 }
304261
262+ const runCancel = yield * EffectBridge . make ( )
305263 const cancel = ops . cancel ( nextSession . id )
306264
307265 function onAbort ( ) {
0 commit comments