@@ -12,6 +12,7 @@ import (
1212 providertypes "neo-code/internal/provider/types"
1313 "neo-code/internal/runtime/controlplane"
1414 agentsession "neo-code/internal/session"
15+ "neo-code/internal/tools"
1516)
1617
1718type checkpointStoreSpy struct {
@@ -202,6 +203,15 @@ func countPerEditCheckpointMetaFiles(t *testing.T, root string) int {
202203 return count
203204}
204205
206+ func hasEventType (events []RuntimeEvent , want EventType ) bool {
207+ for _ , event := range events {
208+ if event .Type == want {
209+ return true
210+ }
211+ }
212+ return false
213+ }
214+
205215func TestCreateStartOfTurnCheckpoint_PendingWrite (t * testing.T ) {
206216 fixture := newRuntimeCheckpointFixture (t )
207217 fixture .captureFile (t , "main.go" , []byte ("package main\n const v = 1\n " ))
@@ -389,6 +399,133 @@ func TestApplyResumeCheckpointVerifyClosureStrategy(t *testing.T) {
389399 assertEventSequence (t , events , []EventType {EventResumeApplied , EventRuntimeSnapshotUpdated })
390400}
391401
402+ func TestApplyResumeCheckpointSkipsUnsupportedInputs (t * testing.T ) {
403+ t .Parallel ()
404+
405+ fixture := newRuntimeCheckpointFixture (t )
406+ service := & Service {
407+ checkpointStore : & checkpointStoreSpy {},
408+ events : make (chan RuntimeEvent , 16 ),
409+ runtimeSnapshots : make (map [string ]RuntimeSnapshot ),
410+ }
411+
412+ service .applyResumeCheckpoint (context .Background (), nil )
413+
414+ emptyState := newRunState ("run-empty-session" , fixture .session )
415+ emptyState .session .ID = " "
416+ service .applyResumeCheckpoint (context .Background (), & emptyState )
417+
418+ unsupportedState := newRunState ("run-unsupported-resume" , fixture .session )
419+ service .checkpointStore = & checkpointStoreSpy {
420+ latestResume : & agentsession.ResumeCheckpoint {
421+ RunID : "run-unknown" ,
422+ SessionID : fixture .session .ID ,
423+ Turn : 1 ,
424+ Phase : "stopped" ,
425+ CompletionState : "completed" ,
426+ },
427+ }
428+ service .applyResumeCheckpoint (context .Background (), & unsupportedState )
429+
430+ if unsupportedState .resumeNextBaseLifecycle != "" {
431+ t .Fatalf ("resumeNextBaseLifecycle = %q, want empty" , unsupportedState .resumeNextBaseLifecycle )
432+ }
433+ if strings .TrimSpace (unsupportedState .pendingSystemReminder ) != "" {
434+ t .Fatalf ("pendingSystemReminder = %q, want empty" , unsupportedState .pendingSystemReminder )
435+ }
436+ if len (collectRuntimeEvents (service .Events ())) != 0 {
437+ t .Fatal ("expected no runtime events for skipped resume checkpoint cases" )
438+ }
439+ }
440+
441+ func TestDeriveResumeBaseLifecycle (t * testing.T ) {
442+ t .Parallel ()
443+
444+ tests := []struct {
445+ name string
446+ phase string
447+ completionState string
448+ want controlplane.RunState
449+ }{
450+ {name : "verify completed resumes verify" , phase : " verify " , completionState : " completed " , want : controlplane .RunStateVerify },
451+ {name : "verify incomplete falls back to plan" , phase : "verify" , completionState : "running" , want : controlplane .RunStatePlan },
452+ {name : "plan resumes plan" , phase : "plan" , completionState : "" , want : controlplane .RunStatePlan },
453+ {name : "execute resumes plan" , phase : "execute" , completionState : "" , want : controlplane .RunStatePlan },
454+ {name : "unknown phase ignored" , phase : "stopped" , completionState : "completed" , want : "" },
455+ }
456+
457+ for _ , tt := range tests {
458+ tt := tt
459+ t .Run (tt .name , func (t * testing.T ) {
460+ t .Parallel ()
461+
462+ if got := deriveResumeBaseLifecycle (tt .phase , tt .completionState ); got != tt .want {
463+ t .Fatalf ("deriveResumeBaseLifecycle(%q, %q) = %q, want %q" , tt .phase , tt .completionState , got , tt .want )
464+ }
465+ })
466+ }
467+ }
468+
469+ func TestServiceRunResumeVerifyClosureBootstrapsFirstTurn (t * testing.T ) {
470+ t .Parallel ()
471+
472+ manager := newRuntimeConfigManager (t )
473+ store := newMemoryStore ()
474+ providerImpl := & scriptedProvider {
475+ responses : []scriptedResponse {
476+ {
477+ Message : providertypes.Message {
478+ Role : providertypes .RoleAssistant ,
479+ Parts : []providertypes.ContentPart {
480+ providertypes .NewTextPart ("verification summary" ),
481+ },
482+ },
483+ FinishReason : "stop" ,
484+ },
485+ },
486+ }
487+ resumeStore := & checkpointStoreSpy {
488+ latestResume : & agentsession.ResumeCheckpoint {
489+ RunID : "run-old-verify" ,
490+ SessionID : "ignored-by-spy" ,
491+ Turn : 2 ,
492+ Phase : "verify" ,
493+ CompletionState : "completed" ,
494+ },
495+ }
496+ service := NewWithFactory (
497+ manager ,
498+ tools .NewRegistry (),
499+ store ,
500+ & scriptedProviderFactory {provider : providerImpl },
501+ & stubContextBuilder {},
502+ )
503+ service .checkpointStore = resumeStore
504+ service .events = make (chan RuntimeEvent , 32 )
505+ service .runtimeSnapshots = make (map [string ]RuntimeSnapshot )
506+
507+ if err := service .Run (context .Background (), UserInput {
508+ RunID : "run-resume-verify-first-turn" ,
509+ Parts : []providertypes.ContentPart {providertypes .NewTextPart ("continue" )},
510+ }); err != nil {
511+ t .Fatalf ("Run() error = %v" , err )
512+ }
513+
514+ events := collectRuntimeEvents (service .Events ())
515+ if ! hasEventType (events , EventResumeApplied ) {
516+ t .Fatalf ("expected %s event, got %+v" , EventResumeApplied , events )
517+ }
518+ if ! hasPhaseTransition (events , "" , "plan" ) {
519+ t .Fatalf ("missing bootstrap transition '' -> plan, events=%+v" , events )
520+ }
521+ if ! hasPhaseTransition (events , "plan" , "verify" ) {
522+ t .Fatalf ("missing resume transition plan -> verify, events=%+v" , events )
523+ }
524+ if ! hasEventType (events , EventVerificationStarted ) {
525+ t .Fatalf ("expected %s event, got %+v" , EventVerificationStarted , events )
526+ }
527+ }
528+
392529func TestRuntimeCheckpointFacadeMethods (t * testing.T ) {
393530 t .Run ("list checkpoints delegates to store" , func (t * testing.T ) {
394531 spy := & checkpointStoreSpy {
0 commit comments