Skip to content

Commit 978ba93

Browse files
committed
Merge remote-tracking branch 'origin/main' into vertical-settings-tabs
2 parents 6182e67 + 9732958 commit 978ba93

30 files changed

+1273
-588
lines changed

.changeset/pretty-peaches-bake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Split Cline.getEnvironmentDetails out into a standalone function

src/core/Cline.ts

Lines changed: 5 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,11 @@ import {
3333
import { getApiMetrics } from "../shared/getApiMetrics"
3434
import { HistoryItem } from "../shared/HistoryItem"
3535
import { 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"
3937
import { ToolParamName, ToolResponse, DiffStrategy } from "../shared/tools"
4038

4139
// services
4240
import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
43-
import { listFiles } from "../services/glob/list-files"
4441
import { BrowserSession } from "../services/browser/BrowserSession"
4542
import { McpHub } from "../services/mcp/McpHub"
4643
import { McpServerManager } from "../services/mcp/McpServerManager"
@@ -51,12 +48,11 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../servi
5148
import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
5249
import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
5350
import { RooTerminalProcess } from "../integrations/terminal/types"
54-
import { Terminal } from "../integrations/terminal/Terminal"
5551
import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
5652

5753
// utils
5854
import { calculateApiCostAnthropic } from "../utils/cost"
59-
import { arePathsEqual, getWorkspacePath } from "../utils/path"
55+
import { getWorkspacePath } from "../utils/path"
6056

6157
// tools
6258
import { fetchInstructionsTool } from "./tools/fetchInstructionsTool"
@@ -91,6 +87,7 @@ import { ClineProvider } from "./webview/ClineProvider"
9187
import { validateToolUse } from "./mode-validator"
9288
import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace"
9389
import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "./task-persistence"
90+
import { getEnvironmentDetails } from "./environment/getEnvironmentDetails"
9491

9592
type 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

Comments
 (0)