outputsScanner.ts
utils/filePersistence/outputsScanner.ts
127
Lines
3682
Bytes
3
Exports
5
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with file-tools. It contains 127 lines, 5 detected imports, and 3 detected exports.
Important relationships
Detected exports
logDebuggetEnvironmentKindfindModifiedFiles
Keywords
entryfilespathparentpathstatturnturnstarttimedirectorymodifiedkind
Detected imports
fs/promisespath../debug.js../teleport/environments.js./types.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
/**
* Outputs directory scanner for file persistence
*
* This module provides utilities to:
* - Detect the session type from environment variables
* - Capture turn start timestamp
* - Find modified files by comparing file mtimes against turn start time
*/
import * as fs from 'fs/promises'
import * as path from 'path'
import { logForDebugging } from '../debug.js'
import type { EnvironmentKind } from '../teleport/environments.js'
import type { TurnStartTime } from './types.js'
/** Shared debug logger for file persistence modules */
export function logDebug(message: string): void {
logForDebugging(`[file-persistence] ${message}`)
}
/**
* Get the environment kind from CLAUDE_CODE_ENVIRONMENT_KIND.
* Returns null if not set or not a recognized value.
*/
export function getEnvironmentKind(): EnvironmentKind | null {
const kind = process.env.CLAUDE_CODE_ENVIRONMENT_KIND
if (kind === 'byoc' || kind === 'anthropic_cloud') {
return kind
}
return null
}
function hasParentPath(
entry: object,
): entry is { parentPath: string; name: string } {
return 'parentPath' in entry && typeof entry.parentPath === 'string'
}
function hasPath(entry: object): entry is { path: string; name: string } {
return 'path' in entry && typeof entry.path === 'string'
}
function getEntryParentPath(entry: object, fallback: string): string {
if (hasParentPath(entry)) {
return entry.parentPath
}
if (hasPath(entry)) {
return entry.path
}
return fallback
}
/**
* Find files that have been modified since the turn started.
* Returns paths of files with mtime >= turnStartTime.
*
* Uses recursive directory listing and parallelized stat calls for efficiency.
*
* @param turnStartTime - The timestamp when the turn started
* @param outputsDir - The directory to scan for modified files
*/
export async function findModifiedFiles(
turnStartTime: TurnStartTime,
outputsDir: string,
): Promise<string[]> {
// Use recursive flag to get all entries in one call
let entries: Awaited<ReturnType<typeof fs.readdir>>
try {
entries = await fs.readdir(outputsDir, {
withFileTypes: true,
recursive: true,
})
} catch {
// Directory doesn't exist or is not accessible
return []
}
// Filter to regular files only (skip symlinks for security) and build full paths
const filePaths: string[] = []
for (const entry of entries) {
if (entry.isSymbolicLink()) {
continue
}
if (entry.isFile()) {
// entry.parentPath is available in Node 20+, fallback to entry.path for older versions
const parentPath = getEntryParentPath(entry, outputsDir)
filePaths.push(path.join(parentPath, entry.name))
}
}
if (filePaths.length === 0) {
logDebug('No files found in outputs directory')
return []
}
// Parallelize stat calls for all files
const statResults = await Promise.all(
filePaths.map(async filePath => {
try {
const stat = await fs.lstat(filePath)
// Skip if it became a symlink between readdir and stat (race condition)
if (stat.isSymbolicLink()) {
return null
}
return { filePath, mtimeMs: stat.mtimeMs }
} catch {
// File may have been deleted between readdir and stat
return null
}
}),
)
// Filter to files modified since turn start
const modifiedFiles: string[] = []
for (const result of statResults) {
if (result && result.mtimeMs >= turnStartTime) {
modifiedFiles.push(result.filePath)
}
}
logDebug(
`Found ${modifiedFiles.length} modified files since turn start (scanned ${filePaths.length} total)`,
)
return modifiedFiles
}