@@ -26,11 +26,15 @@ async function loadMaintenanceModule(params: {
2626 tasks : TaskRecord [ ] ;
2727 sessionStore ?: Record < string , unknown > ;
2828 acpEntry ?: unknown ;
29+ activeCronJobIds ?: string [ ] ;
30+ activeRunIds ?: string [ ] ;
2931} ) {
3032 vi . resetModules ( ) ;
3133
3234 const sessionStore = params . sessionStore ?? { } ;
3335 const acpEntry = params . acpEntry ;
36+ const activeCronJobIds = new Set ( params . activeCronJobIds ?? [ ] ) ;
37+ const activeRunIds = new Set ( params . activeRunIds ?? [ ] ) ;
3438 const currentTasks = new Map ( params . tasks . map ( ( task ) => [ task . taskId , { ...task } ] ) ) ;
3539
3640 vi . doMock ( "../acp/runtime/session-meta.js" , ( ) => ( {
@@ -45,6 +49,15 @@ async function loadMaintenanceModule(params: {
4549 resolveStorePath : ( ) => "" ,
4650 } ) ) ;
4751
52+ vi . doMock ( "../cron/active-jobs.js" , ( ) => ( {
53+ isCronJobActive : ( jobId : string ) => activeCronJobIds . has ( jobId ) ,
54+ } ) ) ;
55+
56+ vi . doMock ( "../infra/agent-events.js" , ( ) => ( {
57+ getAgentRunContext : ( runId : string ) =>
58+ activeRunIds . has ( runId ) ? { sessionKey : "main" } : undefined ,
59+ } ) ) ;
60+
4861 vi . doMock ( "./runtime-internal.js" , ( ) => ( {
4962 deleteTaskRecordById : ( taskId : string ) => currentTasks . delete ( taskId ) ,
5063 ensureTaskRegistryReady : ( ) => { } ,
@@ -90,38 +103,45 @@ async function loadMaintenanceModule(params: {
90103}
91104
92105describe ( "task-registry maintenance issue #60299" , ( ) => {
93- it ( "marks cron tasks with no child session key lost after the grace period" , async ( ) => {
106+ it ( "marks stale cron tasks lost once the runtime no longer tracks the job as active" , async ( ) => {
107+ const childSessionKey = "agent:main:slack:channel:test-channel" ;
94108 const task = makeStaleTask ( {
95109 runtime : "cron" ,
96- childSessionKey : undefined ,
110+ sourceId : "cron-job-1" ,
111+ childSessionKey,
97112 } ) ;
98113
99- const { mod, currentTasks } = await loadMaintenanceModule ( { tasks : [ task ] } ) ;
114+ const { mod, currentTasks } = await loadMaintenanceModule ( {
115+ tasks : [ task ] ,
116+ sessionStore : { [ childSessionKey ] : { updatedAt : Date . now ( ) } } ,
117+ } ) ;
100118
101119 expect ( await mod . runTaskRegistryMaintenance ( ) ) . toMatchObject ( { reconciled : 1 } ) ;
102120 expect ( currentTasks . get ( task . taskId ) ) . toMatchObject ( { status : "lost" } ) ;
103121 } ) ;
104122
105- it ( "marks cron tasks lost even if their transient child key still exists in the session store" , async ( ) => {
106- const childSessionKey = "agent:main:slack:channel:test-channel" ;
123+ it ( "keeps active cron tasks live while the cron runtime still owns the job" , async ( ) => {
107124 const task = makeStaleTask ( {
108125 runtime : "cron" ,
109- childSessionKey,
126+ sourceId : "cron-job-2" ,
127+ childSessionKey : undefined ,
110128 } ) ;
111129
112130 const { mod, currentTasks } = await loadMaintenanceModule ( {
113131 tasks : [ task ] ,
114- sessionStore : { [ childSessionKey ] : { updatedAt : Date . now ( ) } } ,
132+ activeCronJobIds : [ "cron-job-2" ] ,
115133 } ) ;
116134
117- expect ( await mod . runTaskRegistryMaintenance ( ) ) . toMatchObject ( { reconciled : 1 } ) ;
118- expect ( currentTasks . get ( task . taskId ) ) . toMatchObject ( { status : "lost " } ) ;
135+ expect ( await mod . runTaskRegistryMaintenance ( ) ) . toMatchObject ( { reconciled : 0 } ) ;
136+ expect ( currentTasks . get ( task . taskId ) ) . toMatchObject ( { status : "running " } ) ;
119137 } ) ;
120138
121- it ( "treats cli tasks backed only by a persistent chat session as stale " , async ( ) => {
139+ it ( "marks chat-backed cli tasks lost after the owning run context disappears " , async ( ) => {
122140 const channelKey = "agent:main:slack:channel:C1234567890" ;
123141 const task = makeStaleTask ( {
124142 runtime : "cli" ,
143+ sourceId : "run-chat-cli-stale" ,
144+ runId : "run-chat-cli-stale" ,
125145 ownerKey : "agent:main:main" ,
126146 requesterSessionKey : channelKey ,
127147 childSessionKey : channelKey ,
@@ -136,18 +156,21 @@ describe("task-registry maintenance issue #60299", () => {
136156 expect ( currentTasks . get ( task . taskId ) ) . toMatchObject ( { status : "lost" } ) ;
137157 } ) ;
138158
139- it ( "keeps subagent tasks live while their child session still exists " , async ( ) => {
140- const childKey = "agent:main:subagent:abc123 " ;
159+ it ( "keeps chat-backed cli tasks live while the owning run context is still active " , async ( ) => {
160+ const channelKey = "agent:main:slack:channel:C1234567890 " ;
141161 const task = makeStaleTask ( {
142- runtime : "subagent" ,
162+ runtime : "cli" ,
163+ sourceId : "run-chat-cli-live" ,
164+ runId : "run-chat-cli-live" ,
143165 ownerKey : "agent:main:main" ,
144- requesterSessionKey : "agent:main:main" ,
145- childSessionKey : childKey ,
166+ requesterSessionKey : channelKey ,
167+ childSessionKey : channelKey ,
146168 } ) ;
147169
148170 const { mod, currentTasks } = await loadMaintenanceModule ( {
149171 tasks : [ task ] ,
150- sessionStore : { [ childKey ] : { updatedAt : Date . now ( ) } } ,
172+ sessionStore : { [ channelKey ] : { updatedAt : Date . now ( ) } } ,
173+ activeRunIds : [ "run-chat-cli-live" ] ,
151174 } ) ;
152175
153176 expect ( await mod . runTaskRegistryMaintenance ( ) ) . toMatchObject ( { reconciled : 0 } ) ;
0 commit comments