@@ -13,14 +13,20 @@ import {
1313} from "../shell/internals.js" ;
1414import { handleCtrlC } from "../shell/keys.js" ;
1515import { withTestRenderer } from "../harness.js" ;
16- import { buildShellStopAffordance } from "./wiring.js" ;
16+ import {
17+ badgeCount ,
18+ createSessionQueue ,
19+ enqueue ,
20+ isPaused ,
21+ } from "../delivery-queue.js" ;
22+ import { buildShellStopAffordance , cancelWorkersForStop } from "./wiring.js" ;
1723
1824// CL-10149 Phase 4 -- the integration half of Phase 3 deferred here.
1925//
2026// The runner wires the REAL stop affordance into the shell inside
2127// wirePostStartup via buildShellStopAffordance: the affordance's
2228// liveWorkerCount reports the underlying session store's live fleet and its
23- // onStopWorkers drives the production `cancelWorkersForStop ` closure. This test
29+ // onStopWorkers drives the production `cancelLiveWorkers ` closure. This test
2430// proves that end-to-end against a harnessed store: a 2nd Ctrl+C press stops
2531// the live workers and the app STAYS RUNNING (no exit), and liveWorkerCount
2632// reports the harnessed store's live fleet before and after the stop.
@@ -58,14 +64,11 @@ describe("shell stop affordance wiring (CL-10149 Phase 4)", () => {
5864 } ) ;
5965
6066 const store = harnessWithLiveWorkers ( 2 ) ;
61- const bridge = { clearQueuedDelivery : ( ) => undefined } ;
62- // The production dep shapes feed cancelWorkersForStop; fleetRecords is
63- // cleared after a stop so the idle-with-fleet gauge drops.
6467 const fleetRecords = { clear : ( ) => undefined } ;
65- const affordance = buildShellStopAffordance (
66- { bridge } ,
67- { subAgentSessions : store , toolset : { fleetRecords } } ,
68- ) ;
68+ const affordance = buildShellStopAffordance ( {
69+ subAgentSessions : store ,
70+ toolset : { fleetRecords } ,
71+ } ) ;
6972 // The runner's wirePostStartup registers the affordance on the shell;
7073 // mirror that so the key machine's readStopAffordance sees it.
7174 setShellStopAffordance ( shell , affordance ) ;
@@ -82,7 +85,7 @@ describe("shell stop affordance wiring (CL-10149 Phase 4)", () => {
8285 expect ( liveStripCount ( store ) ) . toBe ( 2 ) ;
8386
8487 // 2nd press inside the window stops the live workers via the real
85- // cancelWorkersForStop closure -- app STAYS RUNNING (no exit).
88+ // cancelLiveWorkers closure -- app STAYS RUNNING (no exit).
8689 handleCtrlC ( shell , 1 ) ;
8790 expect ( liveStripCount ( store ) ) . toBe ( 0 ) ;
8891 expect ( exits ) . toBe ( 0 ) ;
@@ -113,10 +116,10 @@ describe("shell stop affordance wiring (CL-10149 Phase 4)", () => {
113116 } ) ;
114117
115118 const store = createSubAgentSessionStore ( ) ;
116- const affordance = buildShellStopAffordance (
117- { bridge : { clearQueuedDelivery : ( ) => undefined } } ,
118- { subAgentSessions : store , toolset : { fleetRecords : undefined } } ,
119- ) ;
119+ const affordance = buildShellStopAffordance ( {
120+ subAgentSessions : store ,
121+ toolset : { fleetRecords : undefined } ,
122+ } ) ;
120123 setShellStopAffordance ( shell , affordance ) ;
121124 try {
122125 expect ( affordance . liveWorkerCount ( ) ) . toBe ( 0 ) ;
@@ -130,4 +133,68 @@ describe("shell stop affordance wiring (CL-10149 Phase 4)", () => {
130133 }
131134 } ) ;
132135 } ) ;
136+
137+ test ( "stay-alive stop keeps a held follow-up paused; quit still wipes" , async ( ) => {
138+ await withTestRenderer ( async ( h ) => {
139+ const shell = createAppShell ( h . renderer , {
140+ terminal : { columns : 80 , rows : 24 } ,
141+ wireKeys : false ,
142+ run : "busy" ,
143+ } ) ;
144+ let exits = 0 ;
145+ setShellExitHandler ( shell , ( ) => {
146+ exits += 1 ;
147+ } ) ;
148+
149+ const store = harnessWithLiveWorkers ( 2 ) ;
150+ let cleared = 0 ;
151+ const bridge = {
152+ clearQueuedDelivery : ( ) => {
153+ cleared += 1 ;
154+ shell . session = createSessionQueue ( "idle" ) ;
155+ } ,
156+ } ;
157+ const fleetRecords = { clear : ( ) => undefined } ;
158+ const inner = buildShellStopAffordance ( {
159+ subAgentSessions : store ,
160+ toolset : { fleetRecords } ,
161+ } ) ;
162+ let stopDone : Promise < void > = Promise . resolve ( ) ;
163+ const affordance = {
164+ liveWorkerCount : inner . liveWorkerCount ,
165+ onStopWorkers : ( ) => {
166+ stopDone = Promise . resolve ( inner . onStopWorkers ( ) ) ;
167+ return stopDone ;
168+ } ,
169+ } ;
170+ setShellStopAffordance ( shell , affordance ) ;
171+ try {
172+ shell . session = enqueue ( shell . session , "follow up later" ) ;
173+ expect ( badgeCount ( shell . session ) ) . toBe ( 1 ) ;
174+
175+ handleCtrlC ( shell , 0 ) ;
176+ expect ( isPaused ( shell . session ) ) . toBe ( true ) ;
177+ expect ( badgeCount ( shell . session ) ) . toBe ( 1 ) ;
178+ expect ( liveStripCount ( store ) ) . toBe ( 2 ) ;
179+
180+ handleCtrlC ( shell , 1 ) ;
181+ await stopDone ;
182+ expect ( exits ) . toBe ( 0 ) ;
183+ expect ( liveStripCount ( store ) ) . toBe ( 0 ) ;
184+ expect ( cleared ) . toBe ( 0 ) ;
185+ expect ( badgeCount ( shell . session ) ) . toBe ( 1 ) ;
186+ expect ( isPaused ( shell . session ) ) . toBe ( true ) ;
187+
188+ await cancelWorkersForStop ( {
189+ subAgentSessions : store ,
190+ fleetRecords,
191+ bridge,
192+ } ) ;
193+ expect ( cleared ) . toBe ( 1 ) ;
194+ expect ( badgeCount ( shell . session ) ) . toBe ( 0 ) ;
195+ } finally {
196+ shell . dispose ( ) ;
197+ }
198+ } ) ;
199+ } ) ;
133200} ) ;
0 commit comments