@@ -36,6 +36,7 @@ import { buildConfiguredAgentSystemPrompt } from "../system-prompt-config.js";
3636import { buildSystemPromptParams } from "../system-prompt-params.js" ;
3737import type { SilentReplyPromptMode } from "../system-prompt.types.js" ;
3838import { sanitizeImageBlocks } from "../tool-images.js" ;
39+ import { cliBackendLog } from "./log.js" ;
3940import { formatTomlConfigOverride } from "./toml-inline.js" ;
4041/** Re-export CLI reliability helpers used by older runner call sites. */
4142export {
@@ -45,6 +46,8 @@ export {
4546} from "./reliability.js" ;
4647
4748const CLI_RUN_QUEUE = new KeyedAsyncQueue ( ) ;
49+ const CLI_IMAGE_SWEEP_TTL_MS = 7 * 24 * 60 * 60 * 1_000 ;
50+ const sweptCliImageRoots = new Set < string > ( ) ;
4851
4952function isClaudeCliProvider ( providerId : string ) : boolean {
5053 return normalizeOptionalLowercaseString ( providerId ) === "claude-cli" ;
@@ -295,6 +298,53 @@ function resolveCliImageRoot(params: { backend: CliBackendConfig; workspaceDir:
295298 return path . join ( resolvePreferredOpenClawTmpDir ( ) , "openclaw-cli-images" ) ;
296299}
297300
301+ function isFileNotFoundError ( error : unknown ) : boolean {
302+ return Boolean (
303+ error &&
304+ typeof error === "object" &&
305+ "code" in error &&
306+ ( error as { code ?: unknown } ) . code === "ENOENT" ,
307+ ) ;
308+ }
309+
310+ async function sweepCliImageRoot ( imageRoot : string ) : Promise < void > {
311+ if ( sweptCliImageRoots . has ( imageRoot ) ) {
312+ return ;
313+ }
314+ sweptCliImageRoots . add ( imageRoot ) ;
315+ try {
316+ const cutoffMs = Date . now ( ) - CLI_IMAGE_SWEEP_TTL_MS ;
317+ const entries = await fs . readdir ( imageRoot , { withFileTypes : true } ) ;
318+ for ( const entry of entries ) {
319+ if ( ! entry . isFile ( ) ) {
320+ continue ;
321+ }
322+ const entryPath = path . join ( imageRoot , entry . name ) ;
323+ const stat = await fs . stat ( entryPath ) . catch ( ( error : unknown ) => {
324+ if ( isFileNotFoundError ( error ) ) {
325+ return undefined ;
326+ }
327+ throw error ;
328+ } ) ;
329+ if ( ! stat ) {
330+ continue ;
331+ }
332+ if ( stat . mtimeMs >= cutoffMs ) {
333+ continue ;
334+ }
335+ try {
336+ await fs . rm ( entryPath , { force : true } ) ;
337+ } catch ( error ) {
338+ if ( ! isFileNotFoundError ( error ) ) {
339+ throw error ;
340+ }
341+ }
342+ }
343+ } catch ( error ) {
344+ cliBackendLog . debug ( `cli image cache sweep failed: ${ String ( error ) } ` ) ;
345+ }
346+ }
347+
298348function appendImagePathsToPrompt ( prompt : string , paths : string [ ] , prefix = "" ) : string {
299349 if ( ! paths . length ) {
300350 return prompt ;
@@ -353,6 +403,7 @@ export async function writeCliImages(params: {
353403 workspaceDir : params . workspaceDir ,
354404 } ) ;
355405 await fs . mkdir ( imageRoot , { recursive : true , mode : 0o700 } ) ;
406+ await sweepCliImageRoot ( imageRoot ) ;
356407 const store = privateFileStore ( imageRoot ) ;
357408 const paths : string [ ] = [ ] ;
358409 for ( const image of params . images ) {
0 commit comments