@@ -33,14 +33,11 @@ import {
3333import { getApiMetrics } from "../shared/getApiMetrics"
3434import { HistoryItem } from "../shared/HistoryItem"
3535import { ClineAskResponse } from "../shared/WebviewMessage"
36- import { defaultModeSlug , getModeBySlug , getFullModeDetails , isToolAllowedForMode } from "../shared/modes"
37- import { EXPERIMENT_IDS , experiments as Experiments , ExperimentId } from "../shared/experiments"
38- import { formatLanguage } from "../shared/language"
36+ import { defaultModeSlug , getModeBySlug } from "../shared/modes"
3937import { ToolParamName , ToolResponse , DiffStrategy } from "../shared/tools"
4038
4139// services
4240import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
43- import { listFiles } from "../services/glob/list-files"
4441import { BrowserSession } from "../services/browser/BrowserSession"
4542import { McpHub } from "../services/mcp/McpHub"
4643import { McpServerManager } from "../services/mcp/McpServerManager"
@@ -51,12 +48,11 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../servi
5148import { DIFF_VIEW_URI_SCHEME , DiffViewProvider } from "../integrations/editor/DiffViewProvider"
5249import { findToolName , formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
5350import { RooTerminalProcess } from "../integrations/terminal/types"
54- import { Terminal } from "../integrations/terminal/Terminal"
5551import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
5652
5753// utils
5854import { calculateApiCostAnthropic } from "../utils/cost"
59- import { arePathsEqual , getWorkspacePath } from "../utils/path"
55+ import { getWorkspacePath } from "../utils/path"
6056
6157// tools
6258import { fetchInstructionsTool } from "./tools/fetchInstructionsTool"
@@ -91,6 +87,7 @@ import { ClineProvider } from "./webview/ClineProvider"
9187import { validateToolUse } from "./mode-validator"
9288import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace"
9389import { readApiMessages , saveApiMessages , readTaskMessages , saveTaskMessages , taskMetadata } from "./task-persistence"
90+ import { getEnvironmentDetails } from "./environment/getEnvironmentDetails"
9491
9592type UserContent = Array < Anthropic . Messages . ContentBlockParam >
9693
@@ -145,7 +142,7 @@ export class Cline extends EventEmitter<ClineEvents> {
145142 private promptCacheKey : string
146143
147144 rooIgnoreController ?: RooIgnoreController
148- private fileContextTracker : FileContextTracker
145+ fileContextTracker : FileContextTracker
149146 private urlContentFetcher : UrlContentFetcher
150147 browserSession : BrowserSession
151148 didEditFile : boolean = false
@@ -1995,261 +1992,11 @@ export class Cline extends EventEmitter<ClineEvents> {
19951992 } ) ,
19961993 )
19971994
1998- const environmentDetails = await this . getEnvironmentDetails ( includeFileDetails )
1995+ const environmentDetails = await getEnvironmentDetails ( this , includeFileDetails )
19991996
20001997 return [ parsedUserContent , environmentDetails ]
20011998 }
20021999
2003- async getEnvironmentDetails ( includeFileDetails : boolean = false ) {
2004- let details = ""
2005-
2006- const { terminalOutputLineLimit = 500 , maxWorkspaceFiles = 200 } =
2007- ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2008-
2009- // It could be useful for cline to know if the user went from one or no file to another between messages, so we always include this context
2010- details += "\n\n# VSCode Visible Files"
2011-
2012- const visibleFilePaths = vscode . window . visibleTextEditors
2013- ?. map ( ( editor ) => editor . document ?. uri ?. fsPath )
2014- . filter ( Boolean )
2015- . map ( ( absolutePath ) => path . relative ( this . cwd , absolutePath ) )
2016- . slice ( 0 , maxWorkspaceFiles )
2017-
2018- // Filter paths through rooIgnoreController
2019- const allowedVisibleFiles = this . rooIgnoreController
2020- ? this . rooIgnoreController . filterPaths ( visibleFilePaths )
2021- : visibleFilePaths . map ( ( p ) => p . toPosix ( ) ) . join ( "\n" )
2022-
2023- if ( allowedVisibleFiles ) {
2024- details += `\n${ allowedVisibleFiles } `
2025- } else {
2026- details += "\n(No visible files)"
2027- }
2028-
2029- details += "\n\n# VSCode Open Tabs"
2030- const { maxOpenTabsContext } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2031- const maxTabs = maxOpenTabsContext ?? 20
2032- const openTabPaths = vscode . window . tabGroups . all
2033- . flatMap ( ( group ) => group . tabs )
2034- . map ( ( tab ) => ( tab . input as vscode . TabInputText ) ?. uri ?. fsPath )
2035- . filter ( Boolean )
2036- . map ( ( absolutePath ) => path . relative ( this . cwd , absolutePath ) . toPosix ( ) )
2037- . slice ( 0 , maxTabs )
2038-
2039- // Filter paths through rooIgnoreController
2040- const allowedOpenTabs = this . rooIgnoreController
2041- ? this . rooIgnoreController . filterPaths ( openTabPaths )
2042- : openTabPaths . map ( ( p ) => p . toPosix ( ) ) . join ( "\n" )
2043-
2044- if ( allowedOpenTabs ) {
2045- details += `\n${ allowedOpenTabs } `
2046- } else {
2047- details += "\n(No open tabs)"
2048- }
2049-
2050- // Get task-specific and background terminals.
2051- const busyTerminals = [
2052- ...TerminalRegistry . getTerminals ( true , this . taskId ) ,
2053- ...TerminalRegistry . getBackgroundTerminals ( true ) ,
2054- ]
2055-
2056- const inactiveTerminals = [
2057- ...TerminalRegistry . getTerminals ( false , this . taskId ) ,
2058- ...TerminalRegistry . getBackgroundTerminals ( false ) ,
2059- ]
2060-
2061- if ( busyTerminals . length > 0 ) {
2062- if ( this . didEditFile ) {
2063- await delay ( 300 ) // Delay after saving file to let terminals catch up.
2064- }
2065-
2066- // Wait for terminals to cool down.
2067- await pWaitFor ( ( ) => busyTerminals . every ( ( t ) => ! TerminalRegistry . isProcessHot ( t . id ) ) , {
2068- interval : 100 ,
2069- timeout : 5_000 ,
2070- } ) . catch ( ( ) => { } )
2071- }
2072-
2073- // Reset, this lets us know when to wait for saved files to update terminals.
2074- this . didEditFile = false
2075-
2076- // Waiting for updated diagnostics lets terminal output be the most
2077- // up-to-date possible.
2078- let terminalDetails = ""
2079-
2080- if ( busyTerminals . length > 0 ) {
2081- // Terminals are cool, let's retrieve their output.
2082- terminalDetails += "\n\n# Actively Running Terminals"
2083-
2084- for ( const busyTerminal of busyTerminals ) {
2085- terminalDetails += `\n## Original command: \`${ busyTerminal . getLastCommand ( ) } \``
2086- let newOutput = TerminalRegistry . getUnretrievedOutput ( busyTerminal . id )
2087-
2088- if ( newOutput ) {
2089- newOutput = Terminal . compressTerminalOutput ( newOutput , terminalOutputLineLimit )
2090- terminalDetails += `\n### New Output\n${ newOutput } `
2091- }
2092- }
2093- }
2094-
2095- // First check if any inactive terminals in this task have completed
2096- // processes with output.
2097- const terminalsWithOutput = inactiveTerminals . filter ( ( terminal ) => {
2098- const completedProcesses = terminal . getProcessesWithOutput ( )
2099- return completedProcesses . length > 0
2100- } )
2101-
2102- // Only add the header if there are terminals with output.
2103- if ( terminalsWithOutput . length > 0 ) {
2104- terminalDetails += "\n\n# Inactive Terminals with Completed Process Output"
2105-
2106- // Process each terminal with output.
2107- for ( const inactiveTerminal of terminalsWithOutput ) {
2108- let terminalOutputs : string [ ] = [ ]
2109-
2110- // Get output from completed processes queue.
2111- const completedProcesses = inactiveTerminal . getProcessesWithOutput ( )
2112-
2113- for ( const process of completedProcesses ) {
2114- let output = process . getUnretrievedOutput ( )
2115-
2116- if ( output ) {
2117- output = Terminal . compressTerminalOutput ( output , terminalOutputLineLimit )
2118- terminalOutputs . push ( `Command: \`${ process . command } \`\n${ output } ` )
2119- }
2120- }
2121-
2122- // Clean the queue after retrieving output.
2123- inactiveTerminal . cleanCompletedProcessQueue ( )
2124-
2125- // Add this terminal's outputs to the details.
2126- if ( terminalOutputs . length > 0 ) {
2127- terminalDetails += `\n## Terminal ${ inactiveTerminal . id } `
2128- terminalOutputs . forEach ( ( output ) => {
2129- terminalDetails += `\n### New Output\n${ output } `
2130- } )
2131- }
2132- }
2133- }
2134-
2135- // console.log(`[Cline#getEnvironmentDetails] terminalDetails: ${terminalDetails}`)
2136-
2137- // Add recently modified files section.
2138- const recentlyModifiedFiles = this . fileContextTracker . getAndClearRecentlyModifiedFiles ( )
2139-
2140- if ( recentlyModifiedFiles . length > 0 ) {
2141- details +=
2142- "\n\n# Recently Modified Files\nThese files have been modified since you last accessed them (file was just edited so you may need to re-read it before editing):"
2143- for ( const filePath of recentlyModifiedFiles ) {
2144- details += `\n${ filePath } `
2145- }
2146- }
2147-
2148- if ( terminalDetails ) {
2149- details += terminalDetails
2150- }
2151-
2152- // Add current time information with timezone.
2153- const now = new Date ( )
2154-
2155- const formatter = new Intl . DateTimeFormat ( undefined , {
2156- year : "numeric" ,
2157- month : "numeric" ,
2158- day : "numeric" ,
2159- hour : "numeric" ,
2160- minute : "numeric" ,
2161- second : "numeric" ,
2162- hour12 : true ,
2163- } )
2164-
2165- const timeZone = formatter . resolvedOptions ( ) . timeZone
2166- const timeZoneOffset = - now . getTimezoneOffset ( ) / 60 // Convert to hours and invert sign to match conventional notation
2167- const timeZoneOffsetHours = Math . floor ( Math . abs ( timeZoneOffset ) )
2168- const timeZoneOffsetMinutes = Math . abs ( Math . round ( ( Math . abs ( timeZoneOffset ) - timeZoneOffsetHours ) * 60 ) )
2169- const timeZoneOffsetStr = `${ timeZoneOffset >= 0 ? "+" : "-" } ${ timeZoneOffsetHours } :${ timeZoneOffsetMinutes . toString ( ) . padStart ( 2 , "0" ) } `
2170- details += `\n\n# Current Time\n${ formatter . format ( now ) } (${ timeZone } , UTC${ timeZoneOffsetStr } )`
2171-
2172- // Add context tokens information.
2173- const { contextTokens, totalCost } = getApiMetrics ( this . clineMessages )
2174- const modelInfo = this . api . getModel ( ) . info
2175- const contextWindow = modelInfo . contextWindow
2176-
2177- const contextPercentage =
2178- contextTokens && contextWindow ? Math . round ( ( contextTokens / contextWindow ) * 100 ) : undefined
2179-
2180- details += `\n\n# Current Context Size (Tokens)\n${ contextTokens ? `${ contextTokens . toLocaleString ( ) } (${ contextPercentage } %)` : "(Not available)" } `
2181- details += `\n\n# Current Cost\n${ totalCost !== null ? `$${ totalCost . toFixed ( 2 ) } ` : "(Not available)" } `
2182-
2183- // Add current mode and any mode-specific warnings.
2184- const {
2185- mode,
2186- customModes,
2187- apiModelId,
2188- customModePrompts,
2189- experiments = { } as Record < ExperimentId , boolean > ,
2190- customInstructions : globalCustomInstructions ,
2191- language,
2192- } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2193-
2194- const currentMode = mode ?? defaultModeSlug
2195-
2196- const modeDetails = await getFullModeDetails ( currentMode , customModes , customModePrompts , {
2197- cwd : this . cwd ,
2198- globalCustomInstructions,
2199- language : language ?? formatLanguage ( vscode . env . language ) ,
2200- } )
2201-
2202- details += `\n\n# Current Mode\n`
2203- details += `<slug>${ currentMode } </slug>\n`
2204- details += `<name>${ modeDetails . name } </name>\n`
2205- details += `<model>${ apiModelId } </model>\n`
2206-
2207- if ( Experiments . isEnabled ( experiments ?? { } , EXPERIMENT_IDS . POWER_STEERING ) ) {
2208- details += `<role>${ modeDetails . roleDefinition } </role>\n`
2209-
2210- if ( modeDetails . customInstructions ) {
2211- details += `<custom_instructions>${ modeDetails . customInstructions } </custom_instructions>\n`
2212- }
2213- }
2214-
2215- // Add warning if not in code mode.
2216- if (
2217- ! isToolAllowedForMode ( "write_to_file" , currentMode , customModes ?? [ ] , { apply_diff : this . diffEnabled } ) &&
2218- ! isToolAllowedForMode ( "apply_diff" , currentMode , customModes ?? [ ] , { apply_diff : this . diffEnabled } )
2219- ) {
2220- const currentModeName = getModeBySlug ( currentMode , customModes ) ?. name ?? currentMode
2221- const defaultModeName = getModeBySlug ( defaultModeSlug , customModes ) ?. name ?? defaultModeSlug
2222- details += `\n\nNOTE: You are currently in '${ currentModeName } ' mode, which does not allow write operations. To write files, the user will need to switch to a mode that supports file writing, such as '${ defaultModeName } ' mode.`
2223- }
2224-
2225- if ( includeFileDetails ) {
2226- details += `\n\n# Current Workspace Directory (${ this . cwd . toPosix ( ) } ) Files\n`
2227- const isDesktop = arePathsEqual ( this . cwd , path . join ( os . homedir ( ) , "Desktop" ) )
2228-
2229- if ( isDesktop ) {
2230- // Don't want to immediately access desktop since it would show
2231- // permission popup.
2232- details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
2233- } else {
2234- const maxFiles = maxWorkspaceFiles ?? 200
2235- const [ files , didHitLimit ] = await listFiles ( this . cwd , true , maxFiles )
2236- const { showRooIgnoredFiles = true } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2237-
2238- const result = formatResponse . formatFilesList (
2239- this . cwd ,
2240- files ,
2241- didHitLimit ,
2242- this . rooIgnoreController ,
2243- showRooIgnoredFiles ,
2244- )
2245-
2246- details += result
2247- }
2248- }
2249-
2250- return `<environment_details>\n${ details . trim ( ) } \n</environment_details>`
2251- }
2252-
22532000 // Checkpoints
22542001
22552002 private getCheckpointService ( ) {
0 commit comments