@@ -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
@@ -1021,7 +1018,7 @@ export class Cline extends EventEmitter<ClineEvents> {
10211018 )
10221019
10231020 const parsedUserContent = await this . parseUserContent ( userContent )
1024- const environmentDetails = await this . getEnvironmentDetails ( includeFileDetails )
1021+ const environmentDetails = await getEnvironmentDetails ( this , includeFileDetails )
10251022
10261023 // Add environment details as its own text block, separate from tool
10271024 // results.
@@ -2077,258 +2074,6 @@ export class Cline extends EventEmitter<ClineEvents> {
20772074 )
20782075 }
20792076
2080- // Environment
2081-
2082- public async getEnvironmentDetails ( includeFileDetails : boolean = false ) {
2083- let details = ""
2084-
2085- const { terminalOutputLineLimit = 500 , maxWorkspaceFiles = 200 } =
2086- ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2087-
2088- // 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
2089- details += "\n\n# VSCode Visible Files"
2090-
2091- const visibleFilePaths = vscode . window . visibleTextEditors
2092- ?. map ( ( editor ) => editor . document ?. uri ?. fsPath )
2093- . filter ( Boolean )
2094- . map ( ( absolutePath ) => path . relative ( this . cwd , absolutePath ) )
2095- . slice ( 0 , maxWorkspaceFiles )
2096-
2097- // Filter paths through rooIgnoreController
2098- const allowedVisibleFiles = this . rooIgnoreController
2099- ? this . rooIgnoreController . filterPaths ( visibleFilePaths )
2100- : visibleFilePaths . map ( ( p ) => p . toPosix ( ) ) . join ( "\n" )
2101-
2102- if ( allowedVisibleFiles ) {
2103- details += `\n${ allowedVisibleFiles } `
2104- } else {
2105- details += "\n(No visible files)"
2106- }
2107-
2108- details += "\n\n# VSCode Open Tabs"
2109- const { maxOpenTabsContext } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2110- const maxTabs = maxOpenTabsContext ?? 20
2111- const openTabPaths = vscode . window . tabGroups . all
2112- . flatMap ( ( group ) => group . tabs )
2113- . map ( ( tab ) => ( tab . input as vscode . TabInputText ) ?. uri ?. fsPath )
2114- . filter ( Boolean )
2115- . map ( ( absolutePath ) => path . relative ( this . cwd , absolutePath ) . toPosix ( ) )
2116- . slice ( 0 , maxTabs )
2117-
2118- // Filter paths through rooIgnoreController
2119- const allowedOpenTabs = this . rooIgnoreController
2120- ? this . rooIgnoreController . filterPaths ( openTabPaths )
2121- : openTabPaths . map ( ( p ) => p . toPosix ( ) ) . join ( "\n" )
2122-
2123- if ( allowedOpenTabs ) {
2124- details += `\n${ allowedOpenTabs } `
2125- } else {
2126- details += "\n(No open tabs)"
2127- }
2128-
2129- // Get task-specific and background terminals.
2130- const busyTerminals = [
2131- ...TerminalRegistry . getTerminals ( true , this . taskId ) ,
2132- ...TerminalRegistry . getBackgroundTerminals ( true ) ,
2133- ]
2134-
2135- const inactiveTerminals = [
2136- ...TerminalRegistry . getTerminals ( false , this . taskId ) ,
2137- ...TerminalRegistry . getBackgroundTerminals ( false ) ,
2138- ]
2139-
2140- if ( busyTerminals . length > 0 ) {
2141- if ( this . didEditFile ) {
2142- await delay ( 300 ) // Delay after saving file to let terminals catch up.
2143- }
2144-
2145- // Wait for terminals to cool down.
2146- await pWaitFor ( ( ) => busyTerminals . every ( ( t ) => ! TerminalRegistry . isProcessHot ( t . id ) ) , {
2147- interval : 100 ,
2148- timeout : 5_000 ,
2149- } ) . catch ( ( ) => { } )
2150- }
2151-
2152- // Reset, this lets us know when to wait for saved files to update terminals.
2153- this . didEditFile = false
2154-
2155- // Waiting for updated diagnostics lets terminal output be the most
2156- // up-to-date possible.
2157- let terminalDetails = ""
2158-
2159- if ( busyTerminals . length > 0 ) {
2160- // Terminals are cool, let's retrieve their output.
2161- terminalDetails += "\n\n# Actively Running Terminals"
2162-
2163- for ( const busyTerminal of busyTerminals ) {
2164- terminalDetails += `\n## Original command: \`${ busyTerminal . getLastCommand ( ) } \``
2165- let newOutput = TerminalRegistry . getUnretrievedOutput ( busyTerminal . id )
2166-
2167- if ( newOutput ) {
2168- newOutput = Terminal . compressTerminalOutput ( newOutput , terminalOutputLineLimit )
2169- terminalDetails += `\n### New Output\n${ newOutput } `
2170- }
2171- }
2172- }
2173-
2174- // First check if any inactive terminals in this task have completed
2175- // processes with output.
2176- const terminalsWithOutput = inactiveTerminals . filter ( ( terminal ) => {
2177- const completedProcesses = terminal . getProcessesWithOutput ( )
2178- return completedProcesses . length > 0
2179- } )
2180-
2181- // Only add the header if there are terminals with output.
2182- if ( terminalsWithOutput . length > 0 ) {
2183- terminalDetails += "\n\n# Inactive Terminals with Completed Process Output"
2184-
2185- // Process each terminal with output.
2186- for ( const inactiveTerminal of terminalsWithOutput ) {
2187- let terminalOutputs : string [ ] = [ ]
2188-
2189- // Get output from completed processes queue.
2190- const completedProcesses = inactiveTerminal . getProcessesWithOutput ( )
2191-
2192- for ( const process of completedProcesses ) {
2193- let output = process . getUnretrievedOutput ( )
2194-
2195- if ( output ) {
2196- output = Terminal . compressTerminalOutput ( output , terminalOutputLineLimit )
2197- terminalOutputs . push ( `Command: \`${ process . command } \`\n${ output } ` )
2198- }
2199- }
2200-
2201- // Clean the queue after retrieving output.
2202- inactiveTerminal . cleanCompletedProcessQueue ( )
2203-
2204- // Add this terminal's outputs to the details.
2205- if ( terminalOutputs . length > 0 ) {
2206- terminalDetails += `\n## Terminal ${ inactiveTerminal . id } `
2207- terminalOutputs . forEach ( ( output ) => {
2208- terminalDetails += `\n### New Output\n${ output } `
2209- } )
2210- }
2211- }
2212- }
2213-
2214- // console.log(`[Cline#getEnvironmentDetails] terminalDetails: ${terminalDetails}`)
2215-
2216- // Add recently modified files section.
2217- const recentlyModifiedFiles = this . fileContextTracker . getAndClearRecentlyModifiedFiles ( )
2218-
2219- if ( recentlyModifiedFiles . length > 0 ) {
2220- details +=
2221- "\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):"
2222- for ( const filePath of recentlyModifiedFiles ) {
2223- details += `\n${ filePath } `
2224- }
2225- }
2226-
2227- if ( terminalDetails ) {
2228- details += terminalDetails
2229- }
2230-
2231- // Add current time information with timezone.
2232- const now = new Date ( )
2233-
2234- const formatter = new Intl . DateTimeFormat ( undefined , {
2235- year : "numeric" ,
2236- month : "numeric" ,
2237- day : "numeric" ,
2238- hour : "numeric" ,
2239- minute : "numeric" ,
2240- second : "numeric" ,
2241- hour12 : true ,
2242- } )
2243-
2244- const timeZone = formatter . resolvedOptions ( ) . timeZone
2245- const timeZoneOffset = - now . getTimezoneOffset ( ) / 60 // Convert to hours and invert sign to match conventional notation
2246- const timeZoneOffsetHours = Math . floor ( Math . abs ( timeZoneOffset ) )
2247- const timeZoneOffsetMinutes = Math . abs ( Math . round ( ( Math . abs ( timeZoneOffset ) - timeZoneOffsetHours ) * 60 ) )
2248- const timeZoneOffsetStr = `${ timeZoneOffset >= 0 ? "+" : "-" } ${ timeZoneOffsetHours } :${ timeZoneOffsetMinutes . toString ( ) . padStart ( 2 , "0" ) } `
2249- details += `\n\n# Current Time\n${ formatter . format ( now ) } (${ timeZone } , UTC${ timeZoneOffsetStr } )`
2250-
2251- // Add context tokens information.
2252- const { contextTokens, totalCost } = getApiMetrics ( this . clineMessages )
2253- const modelInfo = this . api . getModel ( ) . info
2254- const contextWindow = modelInfo . contextWindow
2255-
2256- const contextPercentage =
2257- contextTokens && contextWindow ? Math . round ( ( contextTokens / contextWindow ) * 100 ) : undefined
2258-
2259- details += `\n\n# Current Context Size (Tokens)\n${ contextTokens ? `${ contextTokens . toLocaleString ( ) } (${ contextPercentage } %)` : "(Not available)" } `
2260- details += `\n\n# Current Cost\n${ totalCost !== null ? `$${ totalCost . toFixed ( 2 ) } ` : "(Not available)" } `
2261-
2262- // Add current mode and any mode-specific warnings.
2263- const {
2264- mode,
2265- customModes,
2266- apiModelId,
2267- customModePrompts,
2268- experiments = { } as Record < ExperimentId , boolean > ,
2269- customInstructions : globalCustomInstructions ,
2270- language,
2271- } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2272-
2273- const currentMode = mode ?? defaultModeSlug
2274-
2275- const modeDetails = await getFullModeDetails ( currentMode , customModes , customModePrompts , {
2276- cwd : this . cwd ,
2277- globalCustomInstructions,
2278- language : language ?? formatLanguage ( vscode . env . language ) ,
2279- } )
2280-
2281- details += `\n\n# Current Mode\n`
2282- details += `<slug>${ currentMode } </slug>\n`
2283- details += `<name>${ modeDetails . name } </name>\n`
2284- details += `<model>${ apiModelId } </model>\n`
2285-
2286- if ( Experiments . isEnabled ( experiments ?? { } , EXPERIMENT_IDS . POWER_STEERING ) ) {
2287- details += `<role>${ modeDetails . roleDefinition } </role>\n`
2288-
2289- if ( modeDetails . customInstructions ) {
2290- details += `<custom_instructions>${ modeDetails . customInstructions } </custom_instructions>\n`
2291- }
2292- }
2293-
2294- // Add warning if not in code mode.
2295- if (
2296- ! isToolAllowedForMode ( "write_to_file" , currentMode , customModes ?? [ ] , { apply_diff : this . diffEnabled } ) &&
2297- ! isToolAllowedForMode ( "apply_diff" , currentMode , customModes ?? [ ] , { apply_diff : this . diffEnabled } )
2298- ) {
2299- const currentModeName = getModeBySlug ( currentMode , customModes ) ?. name ?? currentMode
2300- const defaultModeName = getModeBySlug ( defaultModeSlug , customModes ) ?. name ?? defaultModeSlug
2301- 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.`
2302- }
2303-
2304- if ( includeFileDetails ) {
2305- details += `\n\n# Current Workspace Directory (${ this . cwd . toPosix ( ) } ) Files\n`
2306- const isDesktop = arePathsEqual ( this . cwd , path . join ( os . homedir ( ) , "Desktop" ) )
2307-
2308- if ( isDesktop ) {
2309- // Don't want to immediately access desktop since it would show
2310- // permission popup.
2311- details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
2312- } else {
2313- const maxFiles = maxWorkspaceFiles ?? 200
2314- const [ files , didHitLimit ] = await listFiles ( this . cwd , true , maxFiles )
2315- const { showRooIgnoredFiles = true } = ( await this . providerRef . deref ( ) ?. getState ( ) ) ?? { }
2316-
2317- const result = formatResponse . formatFilesList (
2318- this . cwd ,
2319- files ,
2320- didHitLimit ,
2321- this . rooIgnoreController ,
2322- showRooIgnoredFiles ,
2323- )
2324-
2325- details += result
2326- }
2327- }
2328-
2329- return `<environment_details>\n${ details . trim ( ) } \n</environment_details>`
2330- }
2331-
23322077 // Checkpoints
23332078
23342079 private getCheckpointService ( ) {
0 commit comments