11import fs from "node:fs" ;
22import { beforeEach , describe , expect , it , vi } from "vitest" ;
3+ import { configureTaskFlowRegistryRuntime } from "../tasks/task-flow-registry.store.js" ;
4+ import { createManagedTaskFlow , getTaskFlowById , resetTaskFlowRegistryForTests } from "../tasks/task-flow-runtime-internal.js" ;
5+ import { configureTaskRegistryRuntime } from "../tasks/task-registry.store.js" ;
6+ import { createTaskRecord , getTaskById , resetTaskRegistryForTests } from "../tasks/runtime-internal.js" ;
37import { makeRuntime , mockSessionsConfig , writeStore } from "./sessions.test-helpers.js" ;
48
59const mocks = vi . hoisted ( ( ) => ( {
@@ -15,9 +19,37 @@ mockSessionsConfig();
1519import { sessionsContinueCommand } from "./sessions.js" ;
1620
1721describe ( "sessionsContinueCommand" , ( ) => {
22+ const taskStore = {
23+ loadSnapshot : ( ) => ( {
24+ tasks : new Map ( ) ,
25+ deliveryStates : new Map ( ) ,
26+ } ) ,
27+ saveSnapshot : ( ) => { } ,
28+ upsertTaskWithDeliveryState : ( ) => { } ,
29+ upsertTask : ( ) => { } ,
30+ deleteTaskWithDeliveryState : ( ) => { } ,
31+ deleteTask : ( ) => { } ,
32+ upsertDeliveryState : ( ) => { } ,
33+ deleteDeliveryState : ( ) => { } ,
34+ close : ( ) => { } ,
35+ } ;
36+ const flowStore = {
37+ loadSnapshot : ( ) => ( {
38+ flows : new Map ( ) ,
39+ } ) ,
40+ saveSnapshot : ( ) => { } ,
41+ upsertFlow : ( ) => { } ,
42+ deleteFlow : ( ) => { } ,
43+ close : ( ) => { } ,
44+ } ;
45+
1846 beforeEach ( ( ) => {
1947 vi . clearAllMocks ( ) ;
2048 mocks . agentCliCommandMock . mockResolvedValue ( { } ) ;
49+ resetTaskRegistryForTests ( { persist : false } ) ;
50+ resetTaskFlowRegistryForTests ( { persist : false } ) ;
51+ configureTaskRegistryRuntime ( { store : taskStore } ) ;
52+ configureTaskFlowRegistryRuntime ( { store : flowStore } ) ;
2153 } ) ;
2254
2355 it ( "wraps JSON output with resolved session metadata and forwarded agent result" , async ( ) => {
@@ -303,4 +335,107 @@ describe("sessionsContinueCommand", () => {
303335 fs . rmSync ( store , { force : true } ) ;
304336 }
305337 } ) ;
338+
339+ it ( "marks related tasks and flows as reattached for foreground continue" , async ( ) => {
340+ const store = writeStore ( {
341+ "agent:coder:acp:child" : {
342+ sessionId : "sess-child-continue-reattach" ,
343+ updatedAt : Date . now ( ) - 5 * 60_000 ,
344+ } ,
345+ } ) ;
346+ const task = createTaskRecord ( {
347+ runtime : "acp" ,
348+ ownerKey : "agent:main:main" ,
349+ scopeKind : "session" ,
350+ childSessionKey : "agent:coder:acp:child" ,
351+ originKind : "detached_session" ,
352+ originSessionKey : "agent:main:main" ,
353+ task : "Detached child" ,
354+ status : "running" ,
355+ deliveryStatus : "pending" ,
356+ notifyPolicy : "state_changes" ,
357+ } ) ;
358+ const flow = createManagedTaskFlow ( {
359+ ownerKey : "agent:coder:acp:child" ,
360+ controllerId : "tests/sessions-continue" ,
361+ goal : "Detached child" ,
362+ status : "waiting" ,
363+ } ) ;
364+
365+ try {
366+ const { runtime, logs } = makeRuntime ( ) ;
367+ await sessionsContinueCommand (
368+ {
369+ lookup : "sess-child-continue-reattach" ,
370+ message : "Bring this back to foreground" ,
371+ store,
372+ json : true ,
373+ } ,
374+ runtime ,
375+ ) ;
376+
377+ const updatedTask = getTaskById ( task . taskId ) ;
378+ const updatedFlow = getTaskFlowById ( flow . flowId ) ;
379+ expect ( updatedTask ?. reattachedAt ) . toBeTypeOf ( "number" ) ;
380+ expect ( updatedFlow ?. reattachedAt ) . toBeTypeOf ( "number" ) ;
381+
382+ const payload = JSON . parse ( logs [ 0 ] ?? "{}" ) as {
383+ continuedSession ?: { reattachedAt ?: number | null } ;
384+ } ;
385+ expect ( payload . continuedSession ?. reattachedAt ) . toBe ( updatedTask ?. reattachedAt ) ;
386+ expect ( updatedFlow ?. reattachedAt ) . toBe ( updatedTask ?. reattachedAt ) ;
387+ } finally {
388+ fs . rmSync ( store , { force : true } ) ;
389+ }
390+ } ) ;
391+
392+ it ( "does not mark related tasks and flows as reattached for background continue" , async ( ) => {
393+ const store = writeStore ( {
394+ "agent:coder:acp:bg-child" : {
395+ sessionId : "sess-child-continue-bg" ,
396+ updatedAt : Date . now ( ) - 5 * 60_000 ,
397+ } ,
398+ } ) ;
399+ const task = createTaskRecord ( {
400+ runtime : "acp" ,
401+ ownerKey : "agent:main:main" ,
402+ scopeKind : "session" ,
403+ childSessionKey : "agent:coder:acp:bg-child" ,
404+ originKind : "detached_session" ,
405+ originSessionKey : "agent:main:main" ,
406+ task : "Detached child" ,
407+ status : "running" ,
408+ deliveryStatus : "pending" ,
409+ notifyPolicy : "state_changes" ,
410+ } ) ;
411+ const flow = createManagedTaskFlow ( {
412+ ownerKey : "agent:coder:acp:bg-child" ,
413+ controllerId : "tests/sessions-continue" ,
414+ goal : "Detached child" ,
415+ status : "waiting" ,
416+ } ) ;
417+
418+ try {
419+ const { runtime, logs } = makeRuntime ( ) ;
420+ await sessionsContinueCommand (
421+ {
422+ lookup : "sess-child-continue-bg" ,
423+ message : "Keep it detached" ,
424+ store,
425+ json : true ,
426+ background : true ,
427+ } ,
428+ runtime ,
429+ ) ;
430+
431+ expect ( getTaskById ( task . taskId ) ?. reattachedAt ) . toBeUndefined ( ) ;
432+ expect ( getTaskFlowById ( flow . flowId ) ?. reattachedAt ) . toBeUndefined ( ) ;
433+ const payload = JSON . parse ( logs [ 0 ] ?? "{}" ) as {
434+ continuedSession ?: { reattachedAt ?: number | null } ;
435+ } ;
436+ expect ( payload . continuedSession ?. reattachedAt ) . toBeNull ( ) ;
437+ } finally {
438+ fs . rmSync ( store , { force : true } ) ;
439+ }
440+ } ) ;
306441} ) ;
0 commit comments