11// Human-facing background task commands.
22// Handles task listing/show/cancel/notify/audit plus registry maintenance for tasks, flows, and sessions.
33
4- import fs from "node:fs" ;
54import { timestampMsToIsoString } from "@openclaw/normalization-core/number-coercion" ;
65import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce" ;
76import { isRich , theme } from "../../packages/terminal-core/src/theme.js" ;
87import { formatCliCommand } from "../cli/command-format.js" ;
98import { formatLookupMiss } from "../cli/error-format.js" ;
109import { getRuntimeConfig } from "../config/config.js" ;
1110import {
12- loadSessionStore ,
13- pruneStaleEntries ,
1411 resolveAllAgentSessionStoreTargetsSync ,
15- updateSessionStore ,
16- type SessionEntry ,
12+ runSessionRegistryMaintenanceForStore ,
1713} from "../config/sessions.js" ;
1814import { loadCronJobsStoreSync , resolveCronJobsStorePath } from "../cron/store.js" ;
1915import type { RuntimeEnv } from "../runtime.js" ;
20- import { parseAgentSessionKey } from "../sessions/session-key-utils.js" ;
2116import { getTaskById , updateTaskNotifyPolicyById } from "../tasks/runtime-internal.js" ;
2217import { cancelDetachedTaskRunById } from "../tasks/task-executor.js" ;
2318import { listTaskFlowAuditFindings } from "../tasks/task-flow-registry.audit.js" ;
@@ -133,14 +128,6 @@ type SessionRegistryMaintenanceSummary = {
133128 stores : SessionRegistryMaintenanceStoreSummary [ ] ;
134129} ;
135130
136- function parseCronRunSessionJobId ( sessionKey : string ) : string | undefined {
137- const parsed = parseAgentSessionKey ( sessionKey ) ;
138- if ( ! parsed ) {
139- return undefined ;
140- }
141- return / ^ c r o n : ( [ ^ : ] + ) : r u n : [ ^ : ] + $ / u. exec ( parsed . rest ) ?. [ 1 ] ;
142- }
143-
144131function readRunningCronJobIds ( ) : Set < string > {
145132 try {
146133 const cronStorePath = resolveCronJobsStorePath ( getRuntimeConfig ( ) . cron ?. store ) ;
@@ -155,95 +142,26 @@ function readRunningCronJobIds(): Set<string> {
155142 }
156143}
157144
158- function buildSessionRegistryPreserveKeys ( params : {
159- store : Record < string , SessionEntry > ;
160- runningCronJobIds : ReadonlySet < string > ;
161- } ) : { preserveKeys : Set < string > ; preservedRunning : number } {
162- const preserveKeys = new Set < string > ( ) ;
163- let preservedRunning = 0 ;
164- for ( const key of Object . keys ( params . store ) ) {
165- const jobId = parseCronRunSessionJobId ( key ) ;
166- if ( ! jobId ) {
167- // Non-cron session rows are outside this maintenance pass; preserve them.
168- preserveKeys . add ( key ) ;
169- continue ;
170- }
171- if ( params . runningCronJobIds . has ( jobId ) ) {
172- preserveKeys . add ( key ) ;
173- preservedRunning += 1 ;
174- }
175- }
176- return { preserveKeys, preservedRunning } ;
177- }
178-
179145async function runSessionRegistryMaintenance ( params : {
180146 apply : boolean ;
181147} ) : Promise < SessionRegistryMaintenanceSummary > {
182148 const cfg = getRuntimeConfig ( ) ;
183149 const runningCronJobIds = readRunningCronJobIds ( ) ;
184150 const stores : SessionRegistryMaintenanceStoreSummary [ ] = [ ] ;
185151 for ( const target of resolveAllAgentSessionStoreTargetsSync ( cfg ) ) {
186- if ( ! fs . existsSync ( target . storePath ) ) {
187- stores . push ( {
188- agentId : target . agentId ,
189- storePath : target . storePath ,
190- beforeCount : 0 ,
191- afterCount : 0 ,
192- pruned : 0 ,
193- preservedRunning : 0 ,
194- } ) ;
195- continue ;
196- }
197- const beforeStore = loadSessionStore ( target . storePath , { skipCache : true } ) ;
198- const beforeCount = Object . keys ( beforeStore ) . length ;
199- if ( params . apply ) {
200- // Apply mode mutates each store atomically through updateSessionStore.
201- const applied = await updateSessionStore (
202- target . storePath ,
203- ( store ) => {
204- const { preserveKeys, preservedRunning } = buildSessionRegistryPreserveKeys ( {
205- store,
206- runningCronJobIds,
207- } ) ;
208- const pruned = pruneStaleEntries ( store , SESSION_REGISTRY_RETENTION_MS , {
209- log : false ,
210- preserveKeys,
211- } ) ;
212- return {
213- pruned,
214- afterCount : Object . keys ( store ) . length ,
215- preservedRunning,
216- } ;
217- } ,
218- { skipMaintenance : true } ,
219- ) ;
220- stores . push ( {
221- agentId : target . agentId ,
222- storePath : target . storePath ,
223- beforeCount,
224- afterCount : applied . afterCount ,
225- pruned : applied . pruned ,
226- preservedRunning : applied . preservedRunning ,
227- } ) ;
228- continue ;
229- }
230- const previewStore = structuredClone ( beforeStore ) ;
231- // Preview mode runs pruning against a clone so dry-run output cannot change stores.
232- const { preserveKeys, preservedRunning } = buildSessionRegistryPreserveKeys ( {
233- store : previewStore ,
152+ const result = await runSessionRegistryMaintenanceForStore ( {
153+ apply : params . apply ,
154+ retentionMs : SESSION_REGISTRY_RETENTION_MS ,
234155 runningCronJobIds,
235- } ) ;
236- const pruned = pruneStaleEntries ( previewStore , SESSION_REGISTRY_RETENTION_MS , {
237- log : false ,
238- preserveKeys,
156+ storePath : target . storePath ,
239157 } ) ;
240158 stores . push ( {
241159 agentId : target . agentId ,
242160 storePath : target . storePath ,
243- beforeCount,
244- afterCount : Object . keys ( previewStore ) . length ,
245- pruned,
246- preservedRunning,
161+ beforeCount : result . beforeCount ,
162+ afterCount : result . afterCount ,
163+ pruned : result . pruned ,
164+ preservedRunning : result . preservedRunning ,
247165 } ) ;
248166 }
249167 return {
0 commit comments