mirror of
https://github.com/sanbuphy/claude-code-source-code.git
synced 2026-04-04 20:14:55 +08:00
v2.1.88 反编译源码
从 npm 包 @anthropic-ai/claude-code 2.1.88 版本提取的反编译源码 包含 src/ 目录下的 TypeScript 源文件及 vendor/ 原生模块源码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
477
src/setup.ts
Normal file
477
src/setup.ts
Normal file
@@ -0,0 +1,477 @@
|
||||
/* eslint-disable custom-rules/no-process-exit */
|
||||
|
||||
import { feature } from 'bun:bundle'
|
||||
import chalk from 'chalk'
|
||||
import {
|
||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
logEvent,
|
||||
} from 'src/services/analytics/index.js'
|
||||
import { getCwd } from 'src/utils/cwd.js'
|
||||
import { checkForReleaseNotes } from 'src/utils/releaseNotes.js'
|
||||
import { setCwd } from 'src/utils/Shell.js'
|
||||
import { initSinks } from 'src/utils/sinks.js'
|
||||
import {
|
||||
getIsNonInteractiveSession,
|
||||
getProjectRoot,
|
||||
getSessionId,
|
||||
setOriginalCwd,
|
||||
setProjectRoot,
|
||||
switchSession,
|
||||
} from './bootstrap/state.js'
|
||||
import { getCommands } from './commands.js'
|
||||
import { initSessionMemory } from './services/SessionMemory/sessionMemory.js'
|
||||
import { asSessionId } from './types/ids.js'
|
||||
import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js'
|
||||
import { checkAndRestoreTerminalBackup } from './utils/appleTerminalBackup.js'
|
||||
import { prefetchApiKeyFromApiKeyHelperIfSafe } from './utils/auth.js'
|
||||
import { clearMemoryFileCaches } from './utils/claudemd.js'
|
||||
import { getCurrentProjectConfig, getGlobalConfig } from './utils/config.js'
|
||||
import { logForDiagnosticsNoPII } from './utils/diagLogs.js'
|
||||
import { env } from './utils/env.js'
|
||||
import { envDynamic } from './utils/envDynamic.js'
|
||||
import { isBareMode, isEnvTruthy } from './utils/envUtils.js'
|
||||
import { errorMessage } from './utils/errors.js'
|
||||
import { findCanonicalGitRoot, findGitRoot, getIsGit } from './utils/git.js'
|
||||
import { initializeFileChangedWatcher } from './utils/hooks/fileChangedWatcher.js'
|
||||
import {
|
||||
captureHooksConfigSnapshot,
|
||||
updateHooksConfigSnapshot,
|
||||
} from './utils/hooks/hooksConfigSnapshot.js'
|
||||
import { hasWorktreeCreateHook } from './utils/hooks.js'
|
||||
import { checkAndRestoreITerm2Backup } from './utils/iTermBackup.js'
|
||||
import { logError } from './utils/log.js'
|
||||
import { getRecentActivity } from './utils/logoV2Utils.js'
|
||||
import { lockCurrentVersion } from './utils/nativeInstaller/index.js'
|
||||
import type { PermissionMode } from './utils/permissions/PermissionMode.js'
|
||||
import { getPlanSlug } from './utils/plans.js'
|
||||
import { saveWorktreeState } from './utils/sessionStorage.js'
|
||||
import { profileCheckpoint } from './utils/startupProfiler.js'
|
||||
import {
|
||||
createTmuxSessionForWorktree,
|
||||
createWorktreeForSession,
|
||||
generateTmuxSessionName,
|
||||
worktreeBranchName,
|
||||
} from './utils/worktree.js'
|
||||
|
||||
export async function setup(
|
||||
cwd: string,
|
||||
permissionMode: PermissionMode,
|
||||
allowDangerouslySkipPermissions: boolean,
|
||||
worktreeEnabled: boolean,
|
||||
worktreeName: string | undefined,
|
||||
tmuxEnabled: boolean,
|
||||
customSessionId?: string | null,
|
||||
worktreePRNumber?: number,
|
||||
messagingSocketPath?: string,
|
||||
): Promise<void> {
|
||||
logForDiagnosticsNoPII('info', 'setup_started')
|
||||
|
||||
// Check for Node.js version < 18
|
||||
const nodeVersion = process.version.match(/^v(\d+)\./)?.[1]
|
||||
if (!nodeVersion || parseInt(nodeVersion) < 18) {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
chalk.bold.red(
|
||||
'Error: Claude Code requires Node.js version 18 or higher.',
|
||||
),
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Set custom session ID if provided
|
||||
if (customSessionId) {
|
||||
switchSession(asSessionId(customSessionId))
|
||||
}
|
||||
|
||||
// --bare / SIMPLE: skip UDS messaging server and teammate snapshot.
|
||||
// Scripted calls don't receive injected messages and don't use swarm teammates.
|
||||
// Explicit --messaging-socket-path is the escape hatch (per #23222 gate pattern).
|
||||
if (!isBareMode() || messagingSocketPath !== undefined) {
|
||||
// Start UDS messaging server (Mac/Linux only).
|
||||
// Enabled by default for ants — creates a socket in tmpdir if no
|
||||
// --messaging-socket-path is passed. Awaited so the server is bound
|
||||
// and $CLAUDE_CODE_MESSAGING_SOCKET is exported before any hook
|
||||
// (SessionStart in particular) can spawn and snapshot process.env.
|
||||
if (feature('UDS_INBOX')) {
|
||||
const m = await import('./utils/udsMessaging.js')
|
||||
await m.startUdsMessaging(
|
||||
messagingSocketPath ?? m.getDefaultUdsSocketPath(),
|
||||
{ isExplicit: messagingSocketPath !== undefined },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Teammate snapshot — SIMPLE-only gate (no escape hatch, swarm not used in bare)
|
||||
if (!isBareMode() && isAgentSwarmsEnabled()) {
|
||||
const { captureTeammateModeSnapshot } = await import(
|
||||
'./utils/swarm/backends/teammateModeSnapshot.js'
|
||||
)
|
||||
captureTeammateModeSnapshot()
|
||||
}
|
||||
|
||||
// Terminal backup restoration — interactive only. Print mode doesn't
|
||||
// interact with terminal settings; the next interactive session will
|
||||
// detect and restore any interrupted setup.
|
||||
if (!getIsNonInteractiveSession()) {
|
||||
// iTerm2 backup check only when swarms enabled
|
||||
if (isAgentSwarmsEnabled()) {
|
||||
const restoredIterm2Backup = await checkAndRestoreITerm2Backup()
|
||||
if (restoredIterm2Backup.status === 'restored') {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Detected an interrupted iTerm2 setup. Your original settings have been restored. You may need to restart iTerm2 for the changes to take effect.',
|
||||
),
|
||||
)
|
||||
} else if (restoredIterm2Backup.status === 'failed') {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Failed to restore iTerm2 settings. Please manually restore your original settings with: defaults import com.googlecode.iterm2 ${restoredIterm2Backup.backupPath}.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check and restore Terminal.app backup if setup was interrupted
|
||||
try {
|
||||
const restoredTerminalBackup = await checkAndRestoreTerminalBackup()
|
||||
if (restoredTerminalBackup.status === 'restored') {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Detected an interrupted Terminal.app setup. Your original settings have been restored. You may need to restart Terminal.app for the changes to take effect.',
|
||||
),
|
||||
)
|
||||
} else if (restoredTerminalBackup.status === 'failed') {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Failed to restore Terminal.app settings. Please manually restore your original settings with: defaults import com.apple.Terminal ${restoredTerminalBackup.backupPath}.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
// Log but don't crash if Terminal.app backup restoration fails
|
||||
logError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: setCwd() must be called before any other code that depends on the cwd
|
||||
setCwd(cwd)
|
||||
|
||||
// Capture hooks configuration snapshot to avoid hidden hook modifications.
|
||||
// IMPORTANT: Must be called AFTER setCwd() so hooks are loaded from the correct directory
|
||||
const hooksStart = Date.now()
|
||||
captureHooksConfigSnapshot()
|
||||
logForDiagnosticsNoPII('info', 'setup_hooks_captured', {
|
||||
duration_ms: Date.now() - hooksStart,
|
||||
})
|
||||
|
||||
// Initialize FileChanged hook watcher — sync, reads hook config snapshot
|
||||
initializeFileChangedWatcher(cwd)
|
||||
|
||||
// Handle worktree creation if requested
|
||||
// IMPORTANT: this must be called befiore getCommands(), otherwise /eject won't be available.
|
||||
if (worktreeEnabled) {
|
||||
// Mirrors bridgeMain.ts: hook-configured sessions can proceed without git
|
||||
// so createWorktreeForSession() can delegate to the hook (non-git VCS).
|
||||
const hasHook = hasWorktreeCreateHook()
|
||||
const inGit = await getIsGit()
|
||||
if (!hasHook && !inGit) {
|
||||
process.stderr.write(
|
||||
chalk.red(
|
||||
`Error: Can only use --worktree in a git repository, but ${chalk.bold(cwd)} is not a git repository. ` +
|
||||
`Configure a WorktreeCreate hook in settings.json to use --worktree with other VCS systems.\n`,
|
||||
),
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const slug = worktreePRNumber
|
||||
? `pr-${worktreePRNumber}`
|
||||
: (worktreeName ?? getPlanSlug())
|
||||
|
||||
// Git preamble runs whenever we're in a git repo — even if a hook is
|
||||
// configured — so --tmux keeps working for git users who also have a
|
||||
// WorktreeCreate hook. Only hook-only (non-git) mode skips it.
|
||||
let tmuxSessionName: string | undefined
|
||||
if (inGit) {
|
||||
// Resolve to main repo root (handles being invoked from within a worktree).
|
||||
// findCanonicalGitRoot is sync/filesystem-only/memoized; the underlying
|
||||
// findGitRoot cache was already warmed by getIsGit() above, so this is ~free.
|
||||
const mainRepoRoot = findCanonicalGitRoot(getCwd())
|
||||
if (!mainRepoRoot) {
|
||||
process.stderr.write(
|
||||
chalk.red(
|
||||
`Error: Could not determine the main git repository root.\n`,
|
||||
),
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// If we're inside a worktree, switch to the main repo for worktree creation
|
||||
if (mainRepoRoot !== (findGitRoot(getCwd()) ?? getCwd())) {
|
||||
logForDiagnosticsNoPII('info', 'worktree_resolved_to_main_repo')
|
||||
process.chdir(mainRepoRoot)
|
||||
setCwd(mainRepoRoot)
|
||||
}
|
||||
|
||||
tmuxSessionName = tmuxEnabled
|
||||
? generateTmuxSessionName(mainRepoRoot, worktreeBranchName(slug))
|
||||
: undefined
|
||||
} else {
|
||||
// Non-git hook mode: no canonical root to resolve, so name the tmux
|
||||
// session from cwd — generateTmuxSessionName only basenames the path.
|
||||
tmuxSessionName = tmuxEnabled
|
||||
? generateTmuxSessionName(getCwd(), worktreeBranchName(slug))
|
||||
: undefined
|
||||
}
|
||||
|
||||
let worktreeSession: Awaited<ReturnType<typeof createWorktreeForSession>>
|
||||
try {
|
||||
worktreeSession = await createWorktreeForSession(
|
||||
getSessionId(),
|
||||
slug,
|
||||
tmuxSessionName,
|
||||
worktreePRNumber ? { prNumber: worktreePRNumber } : undefined,
|
||||
)
|
||||
} catch (error) {
|
||||
process.stderr.write(
|
||||
chalk.red(`Error creating worktree: ${errorMessage(error)}\n`),
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
logEvent('tengu_worktree_created', { tmux_enabled: tmuxEnabled })
|
||||
|
||||
// Create tmux session for the worktree if enabled
|
||||
if (tmuxEnabled && tmuxSessionName) {
|
||||
const tmuxResult = await createTmuxSessionForWorktree(
|
||||
tmuxSessionName,
|
||||
worktreeSession.worktreePath,
|
||||
)
|
||||
if (tmuxResult.created) {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Created tmux session: ${chalk.bold(tmuxSessionName)}\nTo attach: ${chalk.bold(`tmux attach -t ${tmuxSessionName}`)}`,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
`Warning: Failed to create tmux session: ${tmuxResult.error}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
process.chdir(worktreeSession.worktreePath)
|
||||
setCwd(worktreeSession.worktreePath)
|
||||
setOriginalCwd(getCwd())
|
||||
// --worktree means the worktree IS the session's project, so skills/hooks/
|
||||
// cron/etc. should resolve here. (EnterWorktreeTool mid-session does NOT
|
||||
// touch projectRoot — that's a throwaway worktree, project stays stable.)
|
||||
setProjectRoot(getCwd())
|
||||
saveWorktreeState(worktreeSession)
|
||||
// Clear memory files cache since originalCwd has changed
|
||||
clearMemoryFileCaches()
|
||||
// Settings cache was populated in init() (via applySafeConfigEnvironmentVariables)
|
||||
// and again at captureHooksConfigSnapshot() above, both from the original dir's
|
||||
// .claude/settings.json. Re-read from the worktree and re-capture hooks.
|
||||
updateHooksConfigSnapshot()
|
||||
}
|
||||
|
||||
// Background jobs - only critical registrations that must happen before first query
|
||||
logForDiagnosticsNoPII('info', 'setup_background_jobs_starting')
|
||||
// Bundled skills/plugins are registered in main.tsx before the parallel
|
||||
// getCommands() kick — see comment there. Moved out of setup() because
|
||||
// the await points above (startUdsMessaging, ~20ms) meant getCommands()
|
||||
// raced ahead and memoized an empty bundledSkills list.
|
||||
if (!isBareMode()) {
|
||||
initSessionMemory() // Synchronous - registers hook, gate check happens lazily
|
||||
if (feature('CONTEXT_COLLAPSE')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
;(
|
||||
require('./services/contextCollapse/index.js') as typeof import('./services/contextCollapse/index.js')
|
||||
).initContextCollapse()
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
}
|
||||
}
|
||||
void lockCurrentVersion() // Lock current version to prevent deletion by other processes
|
||||
logForDiagnosticsNoPII('info', 'setup_background_jobs_launched')
|
||||
|
||||
profileCheckpoint('setup_before_prefetch')
|
||||
// Pre-fetch promises - only items needed before render
|
||||
logForDiagnosticsNoPII('info', 'setup_prefetch_starting')
|
||||
// When CLAUDE_CODE_SYNC_PLUGIN_INSTALL is set, skip all plugin prefetch.
|
||||
// The sync install path in print.ts calls refreshPluginState() after
|
||||
// installing, which reloads commands, hooks, and agents. Prefetching here
|
||||
// races with the install (concurrent copyPluginToVersionedCache / cachePlugin
|
||||
// on the same directories), and the hot-reload handler fires clearPluginCache()
|
||||
// mid-install when policySettings arrives.
|
||||
const skipPluginPrefetch =
|
||||
(getIsNonInteractiveSession() &&
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_SYNC_PLUGIN_INSTALL)) ||
|
||||
// --bare: loadPluginHooks → loadAllPlugins is filesystem work that's
|
||||
// wasted when executeHooks early-returns under --bare anyway.
|
||||
isBareMode()
|
||||
if (!skipPluginPrefetch) {
|
||||
void getCommands(getProjectRoot())
|
||||
}
|
||||
void import('./utils/plugins/loadPluginHooks.js').then(m => {
|
||||
if (!skipPluginPrefetch) {
|
||||
void m.loadPluginHooks() // Pre-load plugin hooks (consumed by processSessionStartHooks before render)
|
||||
m.setupPluginHookHotReload() // Set up hot reload for plugin hooks when settings change
|
||||
}
|
||||
})
|
||||
// --bare: skip attribution hook install + repo classification +
|
||||
// session-file-access analytics + team memory watcher. These are background
|
||||
// bookkeeping for commit attribution + usage metrics — scripted calls don't
|
||||
// commit code, and the 49ms attribution hook stat check (measured) is pure
|
||||
// overhead. NOT an early-return: the --dangerously-skip-permissions safety
|
||||
// gate, tengu_started beacon, and apiKeyHelper prefetch below must still run.
|
||||
if (!isBareMode()) {
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
// Prime repo classification cache for auto-undercover mode. Default is
|
||||
// undercover ON until proven internal; if this resolves to internal, clear
|
||||
// the prompt cache so the next turn picks up the OFF state.
|
||||
void import('./utils/commitAttribution.js').then(async m => {
|
||||
if (await m.isInternalModelRepo()) {
|
||||
const { clearSystemPromptSections } = await import(
|
||||
'./constants/systemPromptSections.js'
|
||||
)
|
||||
clearSystemPromptSections()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (feature('COMMIT_ATTRIBUTION')) {
|
||||
// Dynamic import to enable dead code elimination (module contains excluded strings).
|
||||
// Defer to next tick so the git subprocess spawn runs after first render
|
||||
// rather than during the setup() microtask window.
|
||||
setImmediate(() => {
|
||||
void import('./utils/attributionHooks.js').then(
|
||||
({ registerAttributionHooks }) => {
|
||||
registerAttributionHooks() // Register attribution tracking hooks (ant-only feature)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
void import('./utils/sessionFileAccessHooks.js').then(m =>
|
||||
m.registerSessionFileAccessHooks(),
|
||||
) // Register session file access analytics hooks
|
||||
if (feature('TEAMMEM')) {
|
||||
void import('./services/teamMemorySync/watcher.js').then(m =>
|
||||
m.startTeamMemoryWatcher(),
|
||||
) // Start team memory sync watcher
|
||||
}
|
||||
}
|
||||
initSinks() // Attach error log + analytics sinks and drain queued events
|
||||
|
||||
// Session-success-rate denominator. Emit immediately after the analytics
|
||||
// sink is attached — before any parsing, fetching, or I/O that could throw.
|
||||
// inc-3694 (P0 CHANGELOG crash) threw at checkForReleaseNotes below; every
|
||||
// event after this point was dead. This beacon is the earliest reliable
|
||||
// "process started" signal for release health monitoring.
|
||||
logEvent('tengu_started', {})
|
||||
|
||||
void prefetchApiKeyFromApiKeyHelperIfSafe(getIsNonInteractiveSession()) // Prefetch safely - only executes if trust already confirmed
|
||||
profileCheckpoint('setup_after_prefetch')
|
||||
|
||||
// Pre-fetch data for Logo v2 - await to ensure it's ready before logo renders.
|
||||
// --bare / SIMPLE: skip — release notes are interactive-UI display data,
|
||||
// and getRecentActivity() reads up to 10 session JSONL files.
|
||||
if (!isBareMode()) {
|
||||
const { hasReleaseNotes } = await checkForReleaseNotes(
|
||||
getGlobalConfig().lastReleaseNotesSeen,
|
||||
)
|
||||
if (hasReleaseNotes) {
|
||||
await getRecentActivity()
|
||||
}
|
||||
}
|
||||
|
||||
// If permission mode is set to bypass, verify we're in a safe environment
|
||||
if (
|
||||
permissionMode === 'bypassPermissions' ||
|
||||
allowDangerouslySkipPermissions
|
||||
) {
|
||||
// Check if running as root/sudo on Unix-like systems
|
||||
// Allow root if in a sandbox (e.g., TPU devspaces that require root)
|
||||
if (
|
||||
process.platform !== 'win32' &&
|
||||
typeof process.getuid === 'function' &&
|
||||
process.getuid() === 0 &&
|
||||
process.env.IS_SANDBOX !== '1' &&
|
||||
!isEnvTruthy(process.env.CLAUDE_CODE_BUBBLEWRAP)
|
||||
) {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
`--dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.USER_TYPE === 'ant' &&
|
||||
// Skip for Desktop's local agent mode — same trust model as CCR/BYOC
|
||||
// (trusted Anthropic-managed launcher intentionally pre-approving everything).
|
||||
// Precedent: permissionSetup.ts:861, applySettingsChange.ts:55 (PR #19116)
|
||||
process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent' &&
|
||||
// Same for CCD (Claude Code in Desktop) — apps#29127 passes the flag
|
||||
// unconditionally to unlock mid-session bypass switching
|
||||
process.env.CLAUDE_CODE_ENTRYPOINT !== 'claude-desktop'
|
||||
) {
|
||||
// Only await if permission mode is set to bypass
|
||||
const [isDocker, hasInternet] = await Promise.all([
|
||||
envDynamic.getIsDocker(),
|
||||
env.hasInternetAccess(),
|
||||
])
|
||||
const isBubblewrap = envDynamic.getIsBubblewrapSandbox()
|
||||
const isSandbox = process.env.IS_SANDBOX === '1'
|
||||
const isSandboxed = isDocker || isBubblewrap || isSandbox
|
||||
if (!isSandboxed || hasInternet) {
|
||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||
console.error(
|
||||
`--dangerously-skip-permissions can only be used in Docker/sandbox containers with no internet access but got Docker: ${isDocker}, Bubblewrap: ${isBubblewrap}, IS_SANDBOX: ${isSandbox}, hasInternet: ${hasInternet}`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return
|
||||
}
|
||||
|
||||
// Log tengu_exit event from the last session?
|
||||
const projectConfig = getCurrentProjectConfig()
|
||||
if (
|
||||
projectConfig.lastCost !== undefined &&
|
||||
projectConfig.lastDuration !== undefined
|
||||
) {
|
||||
logEvent('tengu_exit', {
|
||||
last_session_cost: projectConfig.lastCost,
|
||||
last_session_api_duration: projectConfig.lastAPIDuration,
|
||||
last_session_tool_duration: projectConfig.lastToolDuration,
|
||||
last_session_duration: projectConfig.lastDuration,
|
||||
last_session_lines_added: projectConfig.lastLinesAdded,
|
||||
last_session_lines_removed: projectConfig.lastLinesRemoved,
|
||||
last_session_total_input_tokens: projectConfig.lastTotalInputTokens,
|
||||
last_session_total_output_tokens: projectConfig.lastTotalOutputTokens,
|
||||
last_session_total_cache_creation_input_tokens:
|
||||
projectConfig.lastTotalCacheCreationInputTokens,
|
||||
last_session_total_cache_read_input_tokens:
|
||||
projectConfig.lastTotalCacheReadInputTokens,
|
||||
last_session_fps_average: projectConfig.lastFpsAverage,
|
||||
last_session_fps_low_1_pct: projectConfig.lastFpsLow1Pct,
|
||||
last_session_id:
|
||||
projectConfig.lastSessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
...projectConfig.lastSessionMetrics,
|
||||
})
|
||||
// Note: We intentionally don't clear these values after logging.
|
||||
// They're needed for cost restoration when resuming sessions.
|
||||
// The values will be overwritten when the next session exits.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user