@@ -21,6 +21,7 @@ type fakeACPDebugChatServerOptions struct {
2121 SessionID string
2222 LoadReplay []map [string ]any
2323 LoadReplayAfter []map [string ]any
24+ HoldPrompt bool
2425 PromptChunks []string
2526 PromptError * acpBootstrapJSONRPCError
2627 StopReason string
@@ -34,6 +35,7 @@ type fakeACPDebugChatServer struct {
3435
3536 initCalls int
3637 newCalls int
38+ cancelSessionIDs []string
3739 loadSessionIDs []string
3840 promptSessionIDs []string
3941 promptTexts []string
@@ -162,6 +164,9 @@ func newFakeACPDebugChatServer(t *testing.T, options fakeACPDebugChatServerOptio
162164 fakeServer .promptSessionIDs = append (fakeServer .promptSessionIDs , params .SessionID )
163165 fakeServer .promptTexts = append (fakeServer .promptTexts , text )
164166 fakeServer .mu .Unlock ()
167+ if options .HoldPrompt {
168+ continue
169+ }
165170 if options .PromptError != nil {
166171 if err := conn .WriteJSON (map [string ]any {
167172 "jsonrpc" : "2.0" ,
@@ -202,6 +207,16 @@ func newFakeACPDebugChatServer(t *testing.T, options fakeACPDebugChatServerOptio
202207 }); err != nil {
203208 t .Fatalf ("failed to write prompt result: %v" , err )
204209 }
210+ case "session/cancel" :
211+ var params struct {
212+ SessionID string `json:"sessionId"`
213+ }
214+ if err := json .Unmarshal (message .Params , & params ); err != nil {
215+ t .Fatalf ("failed to decode cancel params: %v" , err )
216+ }
217+ fakeServer .mu .Lock ()
218+ fakeServer .cancelSessionIDs = append (fakeServer .cancelSessionIDs , params .SessionID )
219+ fakeServer .mu .Unlock ()
205220 default :
206221 t .Fatalf ("unexpected ACP method %q" , message .Method )
207222 }
@@ -458,6 +473,44 @@ func TestCleanupInternalDebugConversationUsesFallbackContextWhenCanceled(t *test
458473 }
459474}
460475
476+ func TestInternalDebugChatSendCancelsPromptWhenPromptTimesOut (t * testing.T ) {
477+ spritz := readyACPSpritz ("tidy-otter" , "user-1" )
478+ fakeACP := newFakeACPDebugChatServer (t , fakeACPDebugChatServerOptions {
479+ SessionID : "session-fresh" ,
480+ HoldPrompt : true ,
481+ })
482+
483+ s := newACPTestServer (t , spritz )
484+ s .internalAuth = internalAuthConfig {enabled : true , token : "internal-token" }
485+ s .acp .instanceURL = func (namespace , name string ) string { return fakeACP .url }
486+ s .acp .promptTimeout = 20 * time .Millisecond
487+
488+ e := echo .New ()
489+ internal := e .Group ("" , s .internalAuthMiddleware (), s .authMiddleware ())
490+ internal .POST ("/api/internal/v1/debug/chat/send" , s .sendInternalDebugChat )
491+
492+ req := httptest .NewRequest (http .MethodPost , "/api/internal/v1/debug/chat/send" , strings .NewReader (`{
493+ "target":{"spritzName":"tidy-otter","cwd":"/workspace/app","title":"Debug Run"},
494+ "message":"hello from cli"
495+ }` ))
496+ req .Header .Set (internalTokenHeader , "internal-token" )
497+ req .Header .Set ("Content-Type" , "application/json" )
498+ req .Header .Set ("X-Spritz-User-Id" , "user-1" )
499+ rec := httptest .NewRecorder ()
500+ e .ServeHTTP (rec , req )
501+
502+ if rec .Code != http .StatusBadGateway {
503+ t .Fatalf ("expected status 502, got %d: %s" , rec .Code , rec .Body .String ())
504+ }
505+ time .Sleep (20 * time .Millisecond )
506+
507+ fakeACP .mu .Lock ()
508+ defer fakeACP .mu .Unlock ()
509+ if len (fakeACP .cancelSessionIDs ) != 1 || fakeACP .cancelSessionIDs [0 ] != "session-fresh" {
510+ t .Fatalf ("expected one session/cancel for session-fresh, got %#v" , fakeACP .cancelSessionIDs )
511+ }
512+ }
513+
461514func TestDebugChatRouteIsUnavailableWithoutAuthOrInternalAuth (t * testing.T ) {
462515 s := newACPTestServer (t )
463516 s .auth = authConfig {mode : authModeNone }
0 commit comments