2026-02-08 14:34:11 +09:00
import { afterEach , describe , expect , test } from "bun:test"
2026-02-01 16:59:50 +09:00
import { _resetForTesting , setMainSession } from "../../features/claude-code-session-state"
import type { BackgroundTask } from "../../features/background-agent"
2026-02-18 21:54:20 +02:00
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
2026-02-01 16:59:50 +09:00
import { createUnstableAgentBabysitterHook } from "./index"
2026-02-08 14:34:11 +09:00
const projectDir = process . cwd ( )
2026-02-01 16:59:50 +09:00
type BabysitterContext = Parameters < typeof createUnstableAgentBabysitterHook > [ 0 ]
function createMockPluginInput ( options : {
messagesBySession : Record < string , unknown [ ] >
promptCalls : Array < { input : unknown } >
} ) : BabysitterContext {
const { messagesBySession , promptCalls } = options
return {
directory : projectDir ,
client : {
session : {
messages : async ( { path } : { path : { id : string } } ) = > ( {
data : messagesBySession [ path . id ] ? ? [ ] ,
} ) ,
prompt : async ( input : unknown ) = > {
promptCalls . push ( { input } )
} ,
2026-02-08 14:34:11 +09:00
promptAsync : async ( input : unknown ) = > {
promptCalls . push ( { input } )
} ,
2026-02-01 16:59:50 +09:00
} ,
} ,
}
}
function createBackgroundManager ( tasks : BackgroundTask [ ] ) {
return {
getTasksByParentSession : ( ) = > tasks ,
}
}
function createTask ( overrides : Partial < BackgroundTask > = { } ) : BackgroundTask {
return {
id : "task-1" ,
sessionID : "bg-1" ,
parentSessionID : "main-1" ,
parentMessageID : "msg-1" ,
description : "unstable task" ,
prompt : "run work" ,
agent : "test-agent" ,
status : "running" ,
progress : {
toolCalls : 1 ,
lastUpdate : new Date ( ) ,
lastMessage : "still working" ,
lastMessageAt : new Date ( Date . now ( ) - 121000 ) ,
} ,
model : { providerID : "google" , modelID : "gemini-1.5" } ,
. . . overrides ,
}
}
describe ( "unstable-agent-babysitter hook" , ( ) = > {
afterEach ( ( ) = > {
_resetForTesting ( )
} )
test ( "fires reminder for hung gemini task" , async ( ) = > {
// #given
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const ctx = createMockPluginInput ( {
messagesBySession : {
"main-1" : [
{ info : { agent : "sisyphus" , model : { providerID : "openai" , modelID : "gpt-4" } } } ,
] ,
"bg-1" : [
{ info : { role : "assistant" } , parts : [ { type : "thinking" , thinking : "deep thought" } ] } ,
] ,
} ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [ createTask ( ) ] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
2026-02-01 19:44:34 +09:00
config : { timeout_ms : 120000 } ,
2026-02-01 16:59:50 +09:00
} )
// #when
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
// #then
expect ( promptCalls . length ) . toBe ( 1 )
const payload = promptCalls [ 0 ] . input as { body ? : { parts? : Array < { text? : string } > } }
const text = payload . body ? . parts ? . [ 0 ] ? . text ? ? ""
expect ( text ) . toContain ( "background_output" )
expect ( text ) . toContain ( "background_cancel" )
expect ( text ) . toContain ( "deep thought" )
2026-02-18 21:54:20 +02:00
expect ( text ) . toContain ( OMO_INTERNAL_INITIATOR_MARKER )
2026-02-01 16:59:50 +09:00
} )
2026-02-01 17:20:01 +09:00
test ( "fires reminder for hung minimax task" , async ( ) = > {
// #given
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const ctx = createMockPluginInput ( {
messagesBySession : {
"main-1" : [
{ info : { agent : "sisyphus" , model : { providerID : "openai" , modelID : "gpt-4" } } } ,
] ,
"bg-1" : [
{ info : { role : "assistant" } , parts : [ { type : "thinking" , thinking : "minimax thought" } ] } ,
] ,
} ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [
createTask ( { model : { providerID : "minimax" , modelID : "minimax-1" } } ) ,
] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
2026-02-01 19:44:34 +09:00
config : { timeout_ms : 120000 } ,
2026-02-01 17:20:01 +09:00
} )
// #when
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
// #then
expect ( promptCalls . length ) . toBe ( 1 )
const payload = promptCalls [ 0 ] . input as { body ? : { parts? : Array < { text? : string } > } }
const text = payload . body ? . parts ? . [ 0 ] ? . text ? ? ""
expect ( text ) . toContain ( "background_output" )
expect ( text ) . toContain ( "background_cancel" )
expect ( text ) . toContain ( "minimax thought" )
2026-02-18 21:54:20 +02:00
expect ( text ) . toContain ( OMO_INTERNAL_INITIATOR_MARKER )
2026-02-01 17:20:01 +09:00
} )
2026-02-01 16:59:50 +09:00
test ( "does not remind stable model tasks" , async ( ) = > {
// #given
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const ctx = createMockPluginInput ( {
messagesBySession : { "main-1" : [ ] } ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [
createTask ( { model : { providerID : "openai" , modelID : "gpt-4" } } ) ,
] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
2026-02-01 19:44:34 +09:00
config : { timeout_ms : 120000 } ,
2026-02-01 16:59:50 +09:00
} )
// #when
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
// #then
expect ( promptCalls . length ) . toBe ( 0 )
} )
test ( "respects per-task cooldown" , async ( ) = > {
// #given
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const ctx = createMockPluginInput ( {
messagesBySession : { "main-1" : [ ] , "bg-1" : [ ] } ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [ createTask ( ) ] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
2026-02-01 19:44:34 +09:00
config : { timeout_ms : 120000 } ,
2026-02-01 16:59:50 +09:00
} )
const now = Date . now ( )
const originalNow = Date . now
Date . now = ( ) = > now
// #when
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
// #then
expect ( promptCalls . length ) . toBe ( 1 )
Date . now = originalNow
} )
2026-04-03 18:40:02 +09:00
test ( "skips follow-up reminder after the main session is cancelled" , async ( ) = > {
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const ctx = createMockPluginInput ( {
messagesBySession : {
"main-1" : [
{ info : { agent : "sisyphus" , model : { providerID : "openai" , modelID : "gpt-4" } } } ,
] ,
"bg-1" : [
{ info : { role : "assistant" } , parts : [ { type : "thinking" , thinking : "deep thought" } ] } ,
] ,
} ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [ createTask ( ) ] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
config : { timeout_ms : 120000 } ,
} )
const firstNow = Date . now ( )
const originalNow = Date . now
let currentNow = firstNow
Date . now = ( ) = > currentNow
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
await hook . event ( { event : { type : "session.error" , properties : { sessionID : "main-1" , error : { name : "AbortError" } } } } )
currentNow += 5 * 60 * 1000 + 1
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
expect ( promptCalls . length ) . toBe ( 1 )
Date . now = originalNow
} )
2026-04-07 15:10:38 +09:00
test ( "#given the main session model includes variant #when injecting a babysitter reminder #then promptAsync receives variant as a top-level field" , async ( ) = > {
// given
setMainSession ( "main-1" )
const promptCalls : Array < { input : unknown } > = [ ]
const mainModel = {
providerID : "openai" ,
modelID : "gpt-4" ,
variant : "max" ,
}
const ctx = createMockPluginInput ( {
messagesBySession : {
"main-1" : [
{ info : { agent : "sisyphus" , model : mainModel } } ,
] ,
"bg-1" : [
{ info : { role : "assistant" } , parts : [ { type : "thinking" , thinking : "deep thought" } ] } ,
] ,
} ,
promptCalls ,
} )
const backgroundManager = createBackgroundManager ( [ createTask ( ) ] )
const hook = createUnstableAgentBabysitterHook ( ctx , {
backgroundManager ,
config : { timeout_ms : 120000 } ,
} )
// when
await hook . event ( { event : { type : "session.idle" , properties : { sessionID : "main-1" } } } )
// then
expect ( promptCalls . length ) . toBe ( 1 )
const payload = promptCalls [ 0 ] . input as {
body ? : {
model ? : { providerID : string ; modelID : string }
variant? : string
}
}
expect ( payload . body ? . model ) . toEqual ( { providerID : "openai" , modelID : "gpt-4" } )
expect ( payload . body ? . variant ) . toBe ( "max" )
} )
2026-02-01 16:59:50 +09:00
} )