2026-05-22 00:05:42 +09:00
import { afterEach , describe , expect , test } from "bun:test"
2026-02-18 18:02:42 +09:00
import { injectContinuation } from "./continuation-injection"
2026-02-18 21:54:20 +02:00
import { OMO_INTERNAL_INITIATOR_MARKER } from "../../shared/internal-initiator-marker"
2026-05-15 21:52:53 +09:00
import {
2026-05-17 17:09:04 +09:00
dispatchInternalPrompt ,
2026-05-15 21:52:53 +09:00
releaseAllPromptAsyncReservationsForTesting ,
releasePromptAsyncReservation ,
} from "../shared/prompt-async-gate"
2026-02-18 18:02:42 +09:00
describe ( "injectContinuation" , ( ) = > {
2026-05-15 21:52:53 +09:00
afterEach ( ( ) = > {
releaseAllPromptAsyncReservationsForTesting ( )
} )
2026-04-08 16:18:26 +09:00
test ( "preserves the registered built-in agent name before promptAsync" , async ( ) = > {
2026-04-06 11:44:11 +09:00
// given
let capturedAgent : string | undefined
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( input : {
body : {
agent? : string
}
} ) = > {
capturedAgent = input . body . agent
return { }
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > ( { inFlight : false , lastInjectedAt : 0 , consecutiveFailures : 0 } ) ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_display_name_agent" ,
resolvedInfo : {
2026-05-22 00:05:42 +09:00
agent : "Sisyphus - ultraworker" ,
2026-04-06 11:44:11 +09:00
model : { providerID : "anthropic" , modelID : "claude-sonnet-4-20250514" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
2026-05-22 00:05:42 +09:00
expect ( capturedAgent ) . toBe ( "Sisyphus - ultraworker" )
2026-04-06 11:44:11 +09:00
} )
2026-05-15 19:44:44 +09:00
test ( "#given resolved agent name still carries a ZWSP sort prefix #when continuation is injected #then promptAsync receives the agent name without the ZWSP prefix" , async ( ) = > {
// given
let capturedAgent : string | undefined
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( input : {
body : {
agent? : string
}
} ) = > {
capturedAgent = input . body . agent
return { }
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > ( { inFlight : false , lastInjectedAt : 0 , consecutiveFailures : 0 } ) ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_zwsp_agent" ,
resolvedInfo : {
2026-05-22 00:05:42 +09:00
agent : "\u200B\u200BSisyphus - ultraworker" ,
2026-05-15 19:44:44 +09:00
model : { providerID : "anthropic" , modelID : "claude-sonnet-4-20250514" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
2026-05-22 00:05:42 +09:00
expect ( capturedAgent ) . toBe ( "Sisyphus - ultraworker" )
2026-05-15 19:44:44 +09:00
expect ( capturedAgent ) . not . toContain ( "\u200B" )
} )
2026-02-18 18:02:42 +09:00
test ( "inherits tools from resolved message info when reinjecting" , async ( ) = > {
// given
let capturedTools : Record < string , boolean > | undefined
2026-05-13 13:09:12 +09:00
let capturedPart :
| {
text : string
synthetic? : boolean
metadata? : Record < string , unknown >
}
| undefined
let capturedNoReply : boolean | undefined
2026-02-18 18:02:42 +09:00
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
2026-02-18 21:54:20 +02:00
promptAsync : async ( input : {
body : {
tools? : Record < string , boolean >
2026-05-13 13:09:12 +09:00
noReply? : boolean
parts? : Array < {
type : string
text : string
synthetic? : boolean
metadata? : Record < string , unknown >
} >
2026-02-18 21:54:20 +02:00
}
} ) = > {
2026-02-18 18:02:42 +09:00
capturedTools = input . body . tools
2026-05-13 13:09:12 +09:00
capturedNoReply = input . body . noReply
capturedPart = input . body . parts ? . [ 0 ]
2026-02-18 18:02:42 +09:00
return { }
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > ( { inFlight : false , lastInjectedAt : 0 , consecutiveFailures : 0 } ) ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_continuation_tools" ,
resolvedInfo : {
agent : "Hephaestus" ,
model : { providerID : "openai" , modelID : "gpt-5.3-codex" } ,
tools : { question : "deny" , bash : "allow" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
expect ( capturedTools ) . toEqual ( { question : false , bash : true } )
2026-05-13 13:09:12 +09:00
expect ( capturedNoReply ) . toBeUndefined ( )
expect ( capturedPart ? . text ) . toContain ( OMO_INTERNAL_INITIATOR_MARKER )
expect ( capturedPart ? . synthetic ) . toBe ( true )
expect ( capturedPart ? . metadata ? . compaction_continue ) . toBe ( true )
2026-02-18 18:02:42 +09:00
} )
2026-03-16 10:24:57 +09:00
test ( "skips injection when agent is plan (prevents Plan Mode infinite loop)" , async ( ) = > {
// given
let injected = false
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( ) = > {
injected = true
return { }
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > ( { inFlight : false , lastInjectedAt : 0 , consecutiveFailures : 0 } ) ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_plan_skip" ,
resolvedInfo : {
agent : "plan" ,
model : { providerID : "anthropic" , modelID : "claude-sonnet-4-20250514" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
expect ( injected ) . toBe ( false )
} )
2026-04-07 15:10:38 +09:00
test ( "#given resolved model info includes variant #when reinjecting continuation #then promptAsync receives variant as a top-level field" , async ( ) = > {
// given
let capturedBody :
| {
model ? : { providerID : string ; modelID : string }
variant? : string
}
| undefined
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( input : {
body : {
model ? : { providerID : string ; modelID : string }
variant? : string
}
} ) = > {
capturedBody = input . body
return { }
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > ( { inFlight : false , lastInjectedAt : 0 , consecutiveFailures : 0 } ) ,
}
const model = {
providerID : "openai" ,
modelID : "gpt-5.3-codex" ,
variant : "max" ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_continuation_variant" ,
resolvedInfo : {
agent : "Hephaestus" ,
model ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
expect ( capturedBody ? . model ) . toEqual ( {
providerID : "openai" ,
modelID : "gpt-5.3-codex" ,
} )
expect ( capturedBody ? . variant ) . toBe ( "max" )
} )
2026-05-15 21:52:53 +09:00
2026-05-19 17:43:37 +09:00
test ( "#given a peer-message hold survives an unrelated release #when todo continuation injects #then it does not record a queued prompt as injected" , async ( ) = > {
2026-05-15 21:52:53 +09:00
// given
const sessionID = "ses_todo_reserved_by_peer_message"
let promptCalls = 0
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( ) = > {
promptCalls += 1
return { }
} ,
} ,
} ,
}
2026-05-22 00:05:42 +09:00
const state = {
inFlight : false ,
lastInjectedAt : 0 ,
consecutiveFailures : 0 ,
awaitingPostInjectionProgressCheck : false ,
}
2026-05-15 21:52:53 +09:00
const sessionStateStore = {
getExistingState : ( ) = > state ,
}
// when
2026-05-17 17:09:04 +09:00
const peerMessageResult = await dispatchInternalPrompt ( {
mode : "async" ,
2026-05-15 21:52:53 +09:00
client : ctx.client ,
sessionID ,
source : "team-live-delivery" ,
settleMs : 0 ,
input : {
path : { id : sessionID } ,
body : { parts : [ { type : "text" , text : '<peer_message from="teammate">hello</peer_message>' } ] } ,
} ,
} )
releasePromptAsyncReservation ( sessionID , "ralph-loop:activity" )
await injectContinuation ( {
ctx : ctx as never ,
sessionID ,
resolvedInfo : {
2026-05-22 00:05:42 +09:00
agent : "Sisyphus - ultraworker" ,
2026-05-15 21:52:53 +09:00
model : { providerID : "anthropic" , modelID : "claude-sonnet-4-20250514" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
expect ( peerMessageResult . status ) . toBe ( "dispatched" )
expect ( promptCalls ) . toBe ( 1 )
expect ( state . inFlight ) . toBe ( false )
2026-05-19 17:43:37 +09:00
expect ( state . lastInjectedAt ) . toBe ( 0 )
expect ( state . awaitingPostInjectionProgressCheck ) . not . toBe ( true )
2026-05-19 16:02:18 +09:00
} )
test ( "#given promptAsync may have accepted before EOF #when continuation injection observes the failure #then it records an optimistic injection" , async ( ) = > {
// given
const state = {
inFlight : false ,
lastInjectedAt : 0 ,
awaitingPostInjectionProgressCheck : false ,
consecutiveFailures : 2 ,
}
let promptCalls = 0
const ctx = {
directory : "/tmp/test" ,
client : {
session : {
todo : async ( ) = > ( { data : [ { id : "1" , content : "todo" , status : "pending" , priority : "high" } ] } ) ,
promptAsync : async ( ) = > {
promptCalls += 1
throw new Error ( "JSON Parse error: Unexpected EOF" )
} ,
} ,
} ,
}
const sessionStateStore = {
getExistingState : ( ) = > state ,
}
// when
await injectContinuation ( {
ctx : ctx as never ,
sessionID : "ses_continuation_eof" ,
resolvedInfo : {
2026-05-22 00:05:42 +09:00
agent : "Sisyphus - ultraworker" ,
2026-05-19 16:02:18 +09:00
model : { providerID : "anthropic" , modelID : "claude-sonnet-4-20250514" } ,
} ,
sessionStateStore : sessionStateStore as never ,
} )
// then
expect ( promptCalls ) . toBe ( 1 )
expect ( state . inFlight ) . toBe ( false )
expect ( state . awaitingPostInjectionProgressCheck ) . toBe ( true )
expect ( state . consecutiveFailures ) . toBe ( 0 )
expect ( state . lastInjectedAt ) . toBeGreaterThan ( 0 )
2026-05-15 21:52:53 +09:00
} )
2026-02-18 18:02:42 +09:00
} )