@@ -11,6 +11,7 @@ import {
1111 ProviderDriverKind ,
1212 ProviderInstanceId ,
1313 RunId ,
14+ RuntimeRequestId ,
1415 ThreadId ,
1516 TurnItemId ,
1617 type OrchestrationV2ProjectedTurnItem ,
@@ -125,6 +126,85 @@ it.effect("checks capability through the production registration", () =>
125126 ) ,
126127) ;
127128
129+ it . effect ( "an answered question reports already answered instead of not found" , ( ) => {
130+ const shell = McpToolAccessTestkit . liveThreadShell ( threadId ) ;
131+ const requestId = RuntimeRequestId . make ( "answered-question" ) ;
132+ const projection = McpToolAccessTestkit . idleThreadProjection ( shell ) ;
133+ return Effect . gen ( function * ( ) {
134+ const server = yield * McpServer . McpServer ;
135+ const respond = ( id : string ) =>
136+ server
137+ . callTool ( {
138+ name : "t3_pending_request_respond" ,
139+ arguments : { requestId : id , answers : { size : "large" } } ,
140+ } )
141+ . pipe (
142+ Effect . provideService ( McpInvocationContext . McpInvocationContext , scope ) ,
143+ Effect . provideService ( McpSchema . McpServerClient , client ) ,
144+ ) ;
145+ expect ( declaredFailure ( yield * respond ( requestId ) ) ) . toMatchObject ( {
146+ code : "invalid_request" ,
147+ message : "The user-input request was already answered." ,
148+ } ) ;
149+ expect ( declaredFailure ( yield * respond ( "missing-question" ) ) ) . toMatchObject ( {
150+ code : "invalid_request" ,
151+ message : "The pending user-input request was not found." ,
152+ } ) ;
153+ } ) . pipe (
154+ Effect . provide (
155+ layerThreadToolkit . pipe (
156+ Layer . provideMerge ( McpServer . McpServer . layer ) ,
157+ Layer . provide ( NodeCrypto . layer ) ,
158+ Layer . provide (
159+ Layer . mock ( ThreadManagement . ThreadManagementService ) ( {
160+ getThreadShell : ( ) => Effect . succeed ( shell ) ,
161+ getProjectThreadRecords : ( ) =>
162+ Effect . succeed ( {
163+ ...projection ,
164+ runtimeRequests : [
165+ {
166+ id : requestId ,
167+ nodeId : NodeId . make ( "root" ) ,
168+ providerTurnId : null ,
169+ nativeRequestRef : null ,
170+ kind : "user_input" ,
171+ status : "resolved" ,
172+ responseCapability : { type : "message" } ,
173+ createdAt : shell . createdAt ,
174+ resolvedAt : shell . updatedAt ,
175+ answers : { size : "large" } ,
176+ } ,
177+ ] ,
178+ turnItems : [
179+ {
180+ id : TurnItemId . make ( "answered-question-item" ) ,
181+ threadId,
182+ runId : null ,
183+ nodeId : null ,
184+ providerThreadId : null ,
185+ providerTurnId : null ,
186+ nativeItemRef : null ,
187+ parentItemId : null ,
188+ ordinal : 0 ,
189+ status : "completed" ,
190+ title : null ,
191+ startedAt : shell . createdAt ,
192+ completedAt : shell . updatedAt ,
193+ updatedAt : shell . updatedAt ,
194+ type : "user_input_request" ,
195+ requestId,
196+ questions : [ ] ,
197+ } ,
198+ ] ,
199+ } ) ,
200+ dispatch : ( ) => Effect . die ( "an answered question must not dispatch again" ) ,
201+ } ) ,
202+ ) ,
203+ ) ,
204+ ) ,
205+ ) ;
206+ } ) ;
207+
128208it ( "bounds public command rejections and redacts internal dispatch causes" , ( ) => {
129209 const command = { commandId : CommandId . make ( "mcp-core-command" ) , commandType : "thread.settle" } ;
130210 expect (
0 commit comments