Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
8.1 kB · 250 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251/** * 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')) : nullconst teamMemWatcher = feature('TEAMMEM') ? (require('../services/teamMemorySync/watcher.js') as typeof import('../services/teamMemorySync/watcher.js')) : nullconst 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] }, ], })}