@@ -54,6 +54,25 @@ const requestContains = (req: ChatCompletionRequest, expected: string[]) => {
5454 return expected . every ( ( text ) => rawRequest . includes ( text ) )
5555}
5656
57+ // aimock's `userMessage` matcher only inspects the LAST user message. Fixtures that need
58+ // whole-request exclusions must replicate that scoping inside a predicate so they keep the
59+ // same matching semantics as the bare-regex fixtures they replace.
60+ const lastUserMessageContains = ( req : ChatCompletionRequest , text : string ) => {
61+ const userMessages = req . messages ?. filter ( ( message ) => message . role === "user" ) ?? [ ]
62+ const last = userMessages . at ( - 1 )
63+ if ( ! last ) return false
64+ const content = typeof last . content === "string" ? last . content : JSON . stringify ( last . content ?? "" )
65+ return content . includes ( text )
66+ }
67+
68+ // reopenParentFromDelegation injects the child result into the resumed parent's history as
69+ // `Subtask <childId> completed.\n\nResult:\n<summary>`. Matching on this injected prefix (in
70+ // its JSON-serialized form) keeps parent-resume fixtures robust when
71+ // validateAndFixToolResultIds rewrites tool-use ids on resume — matching on the new_task
72+ // tool-call id directly proved flaky (the id can be rewritten, the fixture then misses, and
73+ // a looser child fixture wins and serves the child's response to the parent).
74+ const SUBTASK_RESULT_INJECTION = "completed.\\n\\nResult:"
75+
5776const completionAfterAnswer = ( followupId : string , completionId : string ) => ( {
5877 match : {
5978 predicate : ( req : ChatCompletionRequest ) =>
@@ -100,9 +119,14 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
100119 } ,
101120 } )
102121
122+ // The parent prompt embeds SUBTASK_FAST_CHILD_MARKER verbatim, so parent-resume turns
123+ // can also match a bare substring check (same collision class as #561). Exclude the
124+ // parent marker so those turns fall through to the parent-resume fixture below.
103125 mock . addFixture ( {
104126 match : {
105- userMessage : new RegExp ( SUBTASK_FAST_CHILD_MARKER ) ,
127+ predicate : ( req : ChatCompletionRequest ) =>
128+ lastUserMessageContains ( req , SUBTASK_FAST_CHILD_MARKER ) &&
129+ ! requestContains ( req , [ SUBTASK_FAST_PARENT_MARKER ] ) ,
106130 } ,
107131 response : {
108132 toolCalls : [
@@ -118,7 +142,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
118142 mock . addFixture ( {
119143 match : {
120144 predicate : ( req : ChatCompletionRequest ) =>
121- requestContains ( req , [ SUBTASK_FAST_PARENT_MARKER , "call_subtasks_fast_parent_new_task_001 " ] ) ,
145+ requestContains ( req , [ SUBTASK_FAST_PARENT_MARKER , "Fast child completed " ] ) ,
122146 } ,
123147 response : {
124148 toolCalls : [
@@ -149,9 +173,12 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
149173 } ,
150174 } )
151175
176+ // Same collision guard as the fast-child fixture above: SUBTASK_PARENT_PROMPT embeds
177+ // SUBTASK_CHILD_MARKER verbatim, so parent-resume turns must not match this fixture.
152178 mock . addFixture ( {
153179 match : {
154- userMessage : new RegExp ( SUBTASK_CHILD_MARKER ) ,
180+ predicate : ( req : ChatCompletionRequest ) =>
181+ lastUserMessageContains ( req , SUBTASK_CHILD_MARKER ) && ! requestContains ( req , [ SUBTASK_PARENT_MARKER ] ) ,
155182 } ,
156183 response : {
157184 toolCalls : [
@@ -172,7 +199,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
172199 mock . addFixture ( {
173200 match : {
174201 predicate : ( req : ChatCompletionRequest ) =>
175- requestContains ( req , [ SUBTASK_PARENT_MARKER , "call_subtasks_parent_new_task_001" ] ) ,
202+ requestContains ( req , [ SUBTASK_PARENT_MARKER , SUBTASK_RESULT_INJECTION ] ) ,
176203 } ,
177204 response : {
178205 toolCalls : [
@@ -241,11 +268,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
241268 mock . addFixture ( {
242269 match : {
243270 predicate : ( req : ChatCompletionRequest ) =>
244- requestContains ( req , [
245- SUBTASK_API_HANG_PARENT_MARKER ,
246- "call_api_hang_parent_new_task_001" ,
247- SUBTASK_API_HANG_CHILD_RESULT ,
248- ] ) ,
271+ requestContains ( req , [ SUBTASK_API_HANG_PARENT_MARKER , SUBTASK_API_HANG_CHILD_RESULT ] ) ,
249272 } ,
250273 response : {
251274 toolCalls : [
@@ -433,7 +456,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
433456 mock . addFixture ( {
434457 match : {
435458 predicate : ( req : ChatCompletionRequest ) =>
436- requestContains ( req , [ SUBTASK_INTERRUPT_PARENT_MARKER , "call_interrupt_parent_new_task_001" ] ) ,
459+ requestContains ( req , [ SUBTASK_INTERRUPT_PARENT_MARKER , SUBTASK_RESULT_INJECTION ] ) ,
437460 } ,
438461 response : {
439462 toolCalls : [
0 commit comments