@@ -32,6 +32,95 @@ async function executeProviderTool(
3232}
3333
3434describe ( 'provider runtime context' , ( ) => {
35+ it . each ( [ false , true ] ) (
36+ 'keeps workflow diagnostics out of model context with secret projection=%s' ,
37+ async ( withRegistry ) => {
38+ const registry = new ResolvedSecretTraceRegistry ( [
39+ { name : 'TOKEN' , plaintext : 'secret-value' , encryptedValue : 'ciphertext' } ,
40+ ] )
41+ const rawResponse = {
42+ success : true ,
43+ output : {
44+ success : true ,
45+ result : { token : 'secret-value' , childTraceSpans : 'business field' } ,
46+ childTraceSpans : [ { input : { source : 'x' . repeat ( 17 * 1024 * 1024 ) } } ] ,
47+ _childWorkflowInstanceId : 'child-instance' ,
48+ cost : { total : 0.25 } ,
49+ } ,
50+ }
51+ mockExecuteTool . mockImplementationOnce ( async ( _toolId , _params , options ) => {
52+ options . resolvedSecretTraceRegistry ?. recordResolved ( 'TOKEN' , 'secret-value' , {
53+ propagated : true ,
54+ } )
55+ return rawResponse
56+ } )
57+ const result = await runWithProviderRuntimeContext (
58+ {
59+ toolIdByWireId : new Map ( [ [ 'selected_workflow' , 'workflow_executor' ] ] ) ,
60+ ...( withRegistry ? { resolvedSecretTraceRegistry : registry } : { } ) ,
61+ } ,
62+ ( ) => executeProviderToolWithInput ( 'selected_workflow' , { } )
63+ )
64+ expect ( result . rawResponse ) . toBe ( rawResponse )
65+ expect ( Object . keys ( result . modelResponse . output ) ) . not . toContain ( 'childTraceSpans' )
66+ expect ( Object . keys ( result . modelResponse . output ) ) . not . toContain ( '_childWorkflowInstanceId' )
67+ expect ( result . modelResponse . output . result ) . toEqual ( {
68+ token : withRegistry ? '{{TOKEN}}' : 'secret-value' ,
69+ childTraceSpans : 'business field' ,
70+ } )
71+ expect ( result . rawResponse . output . cost . total ) . toBe ( 0.25 )
72+ if ( withRegistry ) expect ( registry . isComplete ( ) ) . toBe ( true )
73+ }
74+ )
75+
76+ it ( 'preserves similarly named business fields from non-workflow tools' , async ( ) => {
77+ const response = {
78+ success : true ,
79+ output : {
80+ childTraceSpans : [ 'business data' ] ,
81+ _childWorkflowInstanceId : 'business identifier' ,
82+ } ,
83+ }
84+ mockExecuteTool . mockResolvedValueOnce ( response )
85+ const result = await executeProviderToolWithInput ( 'http_request' , { } )
86+ expect ( result . modelResponse ) . toEqual ( response )
87+ } )
88+
89+ it ( 'does not replay legacy workflow diagnostics into the model or rebill the child' , async ( ) => {
90+ const session = new AgentTurnStateMachine ( { save : vi . fn ( ) } )
91+ await session . captureStep ( {
92+ assistant : { role : 'assistant' , content : '' } ,
93+ calls : [ { toolId : 'workflow_executor' , providerCallId : 'child-call' , arguments : '{}' } ] ,
94+ native : {
95+ protocol : 'responses' ,
96+ providerId : 'openai' ,
97+ model : 'gpt-4.1-mini' ,
98+ binding : 'binding' ,
99+ value : { } ,
100+ } ,
101+ } )
102+ const invocationId = session . resolveInvocationId ( 'child-call' , 'workflow_executor' ) !
103+ const response = {
104+ success : true ,
105+ output : {
106+ result : { answer : 'retained answer' } ,
107+ childTraceSpans : [ { input : 'internal trace' } ] ,
108+ _childWorkflowInstanceId : 'child-instance' ,
109+ cost : { total : 0.25 } ,
110+ } ,
111+ }
112+ await session . recordToolResult ( { invocationId, rawResponse : response , modelResponse : response } )
113+ const replay = await runWithProviderRuntimeContext ( { agentConversation : session } , ( ) =>
114+ executeProviderToolWithInput ( 'workflow_executor' , { _context : { invocationId } } )
115+ )
116+ expect ( replay . rawResponse . output . childTraceSpans ) . toEqual ( response . output . childTraceSpans )
117+ expect ( replay . rawResponse . output . cost ) . toMatchObject ( { total : 0 } )
118+ expect ( Object . keys ( replay . modelResponse . output ) ) . not . toContain ( 'childTraceSpans' )
119+ expect ( Object . keys ( replay . modelResponse . output ) ) . not . toContain ( '_childWorkflowInstanceId' )
120+ expect ( replay . modelResponse . output . result ) . toEqual ( { answer : 'retained answer' } )
121+ expect ( mockExecuteTool ) . not . toHaveBeenCalled ( )
122+ } )
123+
35124 it ( 'dispatches the bound memory reader and reauthorizes repeated invocation reads' , async ( ) => {
36125 const execute = vi
37126 . fn ( )
0 commit comments