sessionFileAccessHooks.ts
utils/sessionFileAccessHooks.ts
251
Lines
8138
Bytes
2
Exports
17
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 session-engine, file-tools. It contains 251 lines, 17 detected imports, and 2 detected exports.
Important relationships
Detected exports
isMemoryFileAccessregisterSessionFileAccessHooks
Keywords
parsedinputtoolscasefilepathlogeventtoolinputdatasubagentpropsaccess
Detected imports
bun:bundle../bootstrap/state.js../entrypoints/agentSdkTypes.js../services/analytics/index.js../tools/FileEditTool/constants.js../tools/FileEditTool/types.js../tools/FileReadTool/FileReadTool.js../tools/FileReadTool/prompt.js../tools/FileWriteTool/FileWriteTool.js../tools/FileWriteTool/prompt.js../tools/GlobTool/GlobTool.js../tools/GlobTool/prompt.js../tools/GrepTool/GrepTool.js../tools/GrepTool/prompt.js../types/hooks.js./memoryFileDetection.js./agentContext.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
/**
* Session file access analytics hooks.
* Tracks access to session memory and transcript files via Read, Grep, Glob tools.
* Also tracks memdir file access via Read, Grep, Glob, Edit, and Write tools.
*/
import { feature } from 'bun:bundle'
import { registerHookCallbacks } from '../bootstrap/state.js'
import type { HookInput, HookJSONOutput } from '../entrypoints/agentSdkTypes.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../services/analytics/index.js'
import { FILE_EDIT_TOOL_NAME } from '../tools/FileEditTool/constants.js'
import { inputSchema as editInputSchema } from '../tools/FileEditTool/types.js'
import { FileReadTool } from '../tools/FileReadTool/FileReadTool.js'
import { FILE_READ_TOOL_NAME } from '../tools/FileReadTool/prompt.js'
import { FileWriteTool } from '../tools/FileWriteTool/FileWriteTool.js'
import { FILE_WRITE_TOOL_NAME } from '../tools/FileWriteTool/prompt.js'
import { GlobTool } from '../tools/GlobTool/GlobTool.js'
import { GLOB_TOOL_NAME } from '../tools/GlobTool/prompt.js'
import { GrepTool } from '../tools/GrepTool/GrepTool.js'
import { GREP_TOOL_NAME } from '../tools/GrepTool/prompt.js'
import type { HookCallback } from '../types/hooks.js'
import {
detectSessionFileType,
detectSessionPatternType,
isAutoMemFile,
memoryScopeForPath,
} from './memoryFileDetection.js'
/* eslint-disable @typescript-eslint/no-require-imports */
const teamMemPaths = feature('TEAMMEM')
? (require('../memdir/teamMemPaths.js') as typeof import('../memdir/teamMemPaths.js'))
: null
const teamMemWatcher = feature('TEAMMEM')
? (require('../services/teamMemorySync/watcher.js') as typeof import('../services/teamMemorySync/watcher.js'))
: null
const memoryShapeTelemetry = feature('MEMORY_SHAPE_TELEMETRY')
? (require('../memdir/memoryShapeTelemetry.js') as typeof import('../memdir/memoryShapeTelemetry.js'))
: null
/* eslint-enable @typescript-eslint/no-require-imports */
import { getSubagentLogName } from './agentContext.js'
/**
* Extract the file path from a tool input for memdir detection.
* Covers Read (file_path), Edit (file_path), and Write (file_path).
*/
function getFilePathFromInput(
toolName: string,
toolInput: unknown,
): string | null {
switch (toolName) {
case FILE_READ_TOOL_NAME: {
const parsed = FileReadTool.inputSchema.safeParse(toolInput)
return parsed.success ? parsed.data.file_path : null
}
case FILE_EDIT_TOOL_NAME: {
const parsed = editInputSchema().safeParse(toolInput)
return parsed.success ? parsed.data.file_path : null
}
case FILE_WRITE_TOOL_NAME: {
const parsed = FileWriteTool.inputSchema.safeParse(toolInput)
return parsed.success ? parsed.data.file_path : null
}
default:
return null
}
}
/**
* Extract file type from tool input.
* Returns the detected session file type or null.
*/
function getSessionFileTypeFromInput(
toolName: string,
toolInput: unknown,
): 'session_memory' | 'session_transcript' | null {
switch (toolName) {
case FILE_READ_TOOL_NAME: {
const parsed = FileReadTool.inputSchema.safeParse(toolInput)
if (!parsed.success) return null
return detectSessionFileType(parsed.data.file_path)
}
case GREP_TOOL_NAME: {
const parsed = GrepTool.inputSchema.safeParse(toolInput)
if (!parsed.success) return null
// Check path if provided
if (parsed.data.path) {
const pathType = detectSessionFileType(parsed.data.path)
if (pathType) return pathType
}
// Check glob pattern
if (parsed.data.glob) {
const globType = detectSessionPatternType(parsed.data.glob)
if (globType) return globType
}
return null
}
case GLOB_TOOL_NAME: {
const parsed = GlobTool.inputSchema.safeParse(toolInput)
if (!parsed.success) return null
// Check path if provided
if (parsed.data.path) {
const pathType = detectSessionFileType(parsed.data.path)
if (pathType) return pathType
}
// Check pattern
const patternType = detectSessionPatternType(parsed.data.pattern)
if (patternType) return patternType
return null
}
default:
return null
}
}
/**
* Check if a tool use constitutes a memory file access.
* Detects session memory (via Read/Grep/Glob) and memdir access (via Read/Edit/Write).
* Uses the same conditions as the PostToolUse session file access hooks.
*/
export function isMemoryFileAccess(
toolName: string,
toolInput: unknown,
): boolean {
if (getSessionFileTypeFromInput(toolName, toolInput) === 'session_memory') {
return true
}
const filePath = getFilePathFromInput(toolName, toolInput)
if (
filePath &&
(isAutoMemFile(filePath) ||
(feature('TEAMMEM') && teamMemPaths!.isTeamMemFile(filePath)))
) {
return true
}
return false
}
/**
* PostToolUse callback to log session file access events.
*/
async function handleSessionFileAccess(
input: HookInput,
_toolUseID: string | null,
_signal: AbortSignal | undefined,
): Promise<HookJSONOutput> {
if (input.hook_event_name !== 'PostToolUse') return {}
const fileType = getSessionFileTypeFromInput(
input.tool_name,
input.tool_input,
)
const subagentName = getSubagentLogName()
const subagentProps = subagentName ? { subagent_name: subagentName } : {}
if (fileType === 'session_memory') {
logEvent('tengu_session_memory_accessed', { ...subagentProps })
} else if (fileType === 'session_transcript') {
logEvent('tengu_transcript_accessed', { ...subagentProps })
}
// Memdir access tracking
const filePath = getFilePathFromInput(input.tool_name, input.tool_input)
if (filePath && isAutoMemFile(filePath)) {
logEvent('tengu_memdir_accessed', {
tool: input.tool_name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...subagentProps,
})
switch (input.tool_name) {
case FILE_READ_TOOL_NAME:
logEvent('tengu_memdir_file_read', { ...subagentProps })
break
case FILE_EDIT_TOOL_NAME:
logEvent('tengu_memdir_file_edit', { ...subagentProps })
break
case FILE_WRITE_TOOL_NAME:
logEvent('tengu_memdir_file_write', { ...subagentProps })
break
}
}
// Team memory access tracking
if (feature('TEAMMEM') && filePath && teamMemPaths!.isTeamMemFile(filePath)) {
logEvent('tengu_team_mem_accessed', {
tool: input.tool_name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...subagentProps,
})
switch (input.tool_name) {
case FILE_READ_TOOL_NAME:
logEvent('tengu_team_mem_file_read', { ...subagentProps })
break
case FILE_EDIT_TOOL_NAME:
logEvent('tengu_team_mem_file_edit', { ...subagentProps })
teamMemWatcher?.notifyTeamMemoryWrite()
break
case FILE_WRITE_TOOL_NAME:
logEvent('tengu_team_mem_file_write', { ...subagentProps })
teamMemWatcher?.notifyTeamMemoryWrite()
break
}
}
if (feature('MEMORY_SHAPE_TELEMETRY') && filePath) {
const scope = memoryScopeForPath(filePath)
if (
scope !== null &&
(input.tool_name === FILE_EDIT_TOOL_NAME ||
input.tool_name === FILE_WRITE_TOOL_NAME)
) {
memoryShapeTelemetry!.logMemoryWriteShape(
input.tool_name,
input.tool_input,
filePath,
scope,
)
}
}
return {}
}
/**
* Register session file access tracking hooks.
* Called during CLI initialization.
*/
export function registerSessionFileAccessHooks(): void {
const hook: HookCallback = {
type: 'callback',
callback: handleSessionFileAccess,
timeout: 1, // Very short timeout - just logging
internal: true,
}
registerHookCallbacks({
PostToolUse: [
{ matcher: FILE_READ_TOOL_NAME, hooks: [hook] },
{ matcher: GREP_TOOL_NAME, hooks: [hook] },
{ matcher: GLOB_TOOL_NAME, hooks: [hook] },
{ matcher: FILE_EDIT_TOOL_NAME, hooks: [hook] },
{ matcher: FILE_WRITE_TOOL_NAME, hooks: [hook] },
],
})
}