Filehigh importancesource

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

  • isMemoryFileAccess
  • registerSessionFileAccessHooks

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.

Open parent directory

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] },
    ],
  })
}