mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
refactor: Split environment details into modular components
Converted monolithic environment details implementation into separate context modules. Each module handles a specific aspect of the environment (VSCode, terminal, files, etc.). Replaced reminder.ts with integrated todo implementation and standardized XML attributes. Signed-off-by: Eric Wheeler <roo-code@z.ewheeler.org>
This commit is contained in:
parent
2f4d833ebc
commit
426614047d
8 changed files with 324 additions and 288 deletions
13
src/core/environment/context/file.ts
Normal file
13
src/core/environment/context/file.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type { Task } from "../../task/Task"
|
||||
|
||||
export function getFileContext(cline: Task) {
|
||||
const recentlyModifiedFiles = cline.fileContextTracker.getAndClearRecentlyModifiedFiles()
|
||||
if (recentlyModifiedFiles.length > 0) {
|
||||
return {
|
||||
recentlyModified: {
|
||||
file: recentlyModifiedFiles.map((p) => ({ "@path": p })),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
65
src/core/environment/context/metadata.ts
Normal file
65
src/core/environment/context/metadata.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import * as vscode from "vscode"
|
||||
|
||||
import type { ExperimentId } from "@roo-code/types"
|
||||
|
||||
import { EXPERIMENT_IDS, experiments as Experiments } from "../../../shared/experiments"
|
||||
import { formatLanguage } from "../../../shared/language"
|
||||
import { defaultModeSlug, getFullModeDetails } from "../../../shared/modes"
|
||||
import { getApiMetrics } from "../../../shared/getApiMetrics"
|
||||
import type { Task } from "../../task/Task"
|
||||
|
||||
export async function getMetadataContext(cline: Task) {
|
||||
const state = await cline.providerRef.deref()?.getState()
|
||||
const {
|
||||
mode,
|
||||
customModes,
|
||||
customModePrompts,
|
||||
experiments = {} as Record<ExperimentId, boolean>,
|
||||
customInstructions: globalCustomInstructions,
|
||||
language,
|
||||
} = state ?? {}
|
||||
|
||||
// High-Frequency Metadata
|
||||
const now = new Date()
|
||||
const timeZoneOffset = -now.getTimezoneOffset()
|
||||
const offsetHours = Math.floor(Math.abs(timeZoneOffset) / 60)
|
||||
const offsetMinutes = Math.abs(timeZoneOffset) % 60
|
||||
const offsetSign = timeZoneOffset >= 0 ? "+" : "-"
|
||||
const offsetString = `${offsetSign}${offsetHours
|
||||
.toString()
|
||||
.padStart(2, "0")}:${offsetMinutes.toString().padStart(2, "0")}`
|
||||
const isoDateWithOffset = now.toISOString().replace(/Z$/, offsetString)
|
||||
const time = {
|
||||
"@v": isoDateWithOffset,
|
||||
}
|
||||
|
||||
const { totalCost } = getApiMetrics(cline.clineMessages)
|
||||
const cost = {
|
||||
"@t": totalCost !== null ? totalCost.toFixed(2) : "0.00",
|
||||
"@c": "USD",
|
||||
"#text": "Must form responses to minimize cost growth",
|
||||
}
|
||||
|
||||
// Low-Frequency Metadata
|
||||
const currentMode = mode ?? defaultModeSlug
|
||||
const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, {
|
||||
cwd: cline.cwd,
|
||||
globalCustomInstructions,
|
||||
language: language ?? formatLanguage(vscode.env.language),
|
||||
})
|
||||
|
||||
const { id: modelId } = cline.api.getModel()
|
||||
const modeInfo = {
|
||||
"@slug": currentMode,
|
||||
"@name": modeDetails.name,
|
||||
"@model": modelId,
|
||||
...(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING) && {
|
||||
"@role": modeDetails.roleDefinition,
|
||||
...(modeDetails.customInstructions && {
|
||||
"@customInstructions": modeDetails.customInstructions,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
|
||||
return { time, cost, mode: modeInfo }
|
||||
}
|
||||
76
src/core/environment/context/terminal.ts
Normal file
76
src/core/environment/context/terminal.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import pWaitFor from "p-wait-for"
|
||||
import delay from "delay"
|
||||
|
||||
import { TerminalRegistry } from "../../../integrations/terminal/TerminalRegistry"
|
||||
import { Terminal } from "../../../integrations/terminal/Terminal"
|
||||
import type { Task } from "../../task/Task"
|
||||
|
||||
export async function getTerminalContext(cline: Task) {
|
||||
const state = await cline.providerRef.deref()?.getState()
|
||||
const { terminalOutputLineLimit = 500 } = state ?? {}
|
||||
const terminalsData: any[] = []
|
||||
const busyTerminals = [
|
||||
...TerminalRegistry.getTerminals(true, cline.taskId),
|
||||
...TerminalRegistry.getBackgroundTerminals(true),
|
||||
]
|
||||
const inactiveTerminals = [
|
||||
...TerminalRegistry.getTerminals(false, cline.taskId),
|
||||
...TerminalRegistry.getBackgroundTerminals(false),
|
||||
]
|
||||
|
||||
// Wait for terminals to cool down if needed
|
||||
if (busyTerminals.length > 0 && cline.didEditFile) {
|
||||
await delay(300) // Delay after saving file to let terminals catch up
|
||||
await pWaitFor(() => busyTerminals.every((t) => !TerminalRegistry.isProcessHot(t.id)), {
|
||||
interval: 100,
|
||||
timeout: 5_000,
|
||||
}).catch(() => {})
|
||||
}
|
||||
cline.didEditFile = false
|
||||
|
||||
// Process active terminals
|
||||
busyTerminals.forEach((terminal) => {
|
||||
const cwd = terminal.getCurrentWorkingDirectory()
|
||||
const command = terminal.getLastCommand()
|
||||
let output = TerminalRegistry.getUnretrievedOutput(terminal.id)
|
||||
|
||||
const terminalData: any = {
|
||||
"@id": terminal.id.toString(),
|
||||
"@status": "Active",
|
||||
"@cwd": cwd,
|
||||
"@command": command,
|
||||
}
|
||||
|
||||
if (output) {
|
||||
terminalData["#cdata"] = Terminal.compressTerminalOutput(output, terminalOutputLineLimit)
|
||||
}
|
||||
|
||||
terminalsData.push(terminalData)
|
||||
})
|
||||
|
||||
// Process inactive terminals with output
|
||||
inactiveTerminals
|
||||
.filter((t) => t.getProcessesWithOutput().length > 0)
|
||||
.forEach((terminal) => {
|
||||
const cwd = terminal.getCurrentWorkingDirectory()
|
||||
const processes = terminal.getProcessesWithOutput()
|
||||
|
||||
processes.forEach((process) => {
|
||||
const output = process.getUnretrievedOutput()
|
||||
|
||||
if (output) {
|
||||
terminalsData.push({
|
||||
"@id": terminal.id.toString(),
|
||||
"@status": "Inactive",
|
||||
"@cwd": cwd,
|
||||
"@command": process.command,
|
||||
"#cdata": Terminal.compressTerminalOutput(output, terminalOutputLineLimit),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
terminal.cleanCompletedProcessQueue()
|
||||
})
|
||||
|
||||
return terminalsData.length > 0 ? { terminal: terminalsData } : undefined
|
||||
}
|
||||
31
src/core/environment/context/todo.ts
Normal file
31
src/core/environment/context/todo.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import type { Task } from "../../task/Task"
|
||||
|
||||
export function getTodoContext(cline: Task) {
|
||||
if (cline.todoList && cline.todoList.length > 0) {
|
||||
const todoLines = cline.todoList
|
||||
.map((todo) => {
|
||||
let statusPrefix = "[ ]" // pending
|
||||
if (todo.status === "in_progress") statusPrefix = "[-]"
|
||||
else if (todo.status === "completed") statusPrefix = "[x]"
|
||||
return `${statusPrefix} ${todo.content}`
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
const todoText = [
|
||||
todoLines,
|
||||
"IMPORTANT: When task status changes, remember to call the `update_todo_list` tool to update your progress.",
|
||||
].join("\n")
|
||||
|
||||
return {
|
||||
todo: {
|
||||
"#text": todoText,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
todo: {
|
||||
"#text":
|
||||
"You have not created a todo list yet. Create one with `update_todo_list` if your task is complicated or involves multiple steps.",
|
||||
},
|
||||
}
|
||||
}
|
||||
50
src/core/environment/context/vscode.ts
Normal file
50
src/core/environment/context/vscode.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { Task } from "../../task/Task"
|
||||
|
||||
export async function getVscodeEditorContext(cline: Task) {
|
||||
const state = await cline.providerRef.deref()?.getState()
|
||||
const { maxWorkspaceFiles = 200 } = state ?? {}
|
||||
|
||||
// Get visible files in the editor
|
||||
const visibleFilePaths = vscode.window.visibleTextEditors
|
||||
?.map((editor) => editor.document?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.map((absolutePath) => path.relative(cline.cwd, absolutePath))
|
||||
.slice(0, maxWorkspaceFiles)
|
||||
|
||||
const allowedVisibleFiles = cline.rooIgnoreController
|
||||
? cline.rooIgnoreController.filterPaths(visibleFilePaths)
|
||||
: visibleFilePaths.map((p) => p.toPosix())
|
||||
|
||||
const visibleFiles =
|
||||
allowedVisibleFiles?.length > 0
|
||||
? {
|
||||
file: allowedVisibleFiles.map((p) => ({ "@path": p })),
|
||||
}
|
||||
: undefined
|
||||
|
||||
// Get open tabs (high-frequency data - using compact representation)
|
||||
const { maxOpenTabsContext } = state ?? {}
|
||||
const maxTabs = maxOpenTabsContext ?? 20
|
||||
const openTabPaths = vscode.window.tabGroups.all
|
||||
.flatMap((group) => group.tabs)
|
||||
.map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.map((absolutePath) => path.relative(cline.cwd, absolutePath).toPosix())
|
||||
.slice(0, maxTabs)
|
||||
|
||||
const allowedOpenTabs = cline.rooIgnoreController
|
||||
? cline.rooIgnoreController.filterPaths(openTabPaths)
|
||||
: openTabPaths
|
||||
|
||||
const openTabs =
|
||||
allowedOpenTabs?.length > 0
|
||||
? {
|
||||
t: allowedOpenTabs.map((p) => ({ "@p": p })),
|
||||
}
|
||||
: undefined
|
||||
|
||||
return { visibleFiles, openTabs }
|
||||
}
|
||||
56
src/core/environment/context/workspace.ts
Normal file
56
src/core/environment/context/workspace.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import path from "path"
|
||||
import os from "os"
|
||||
|
||||
import { listFiles } from "../../../services/glob/list-files"
|
||||
import { arePathsEqual } from "../../../utils/path"
|
||||
import { formatResponse } from "../../prompts/responses"
|
||||
import type { Task } from "../../task/Task"
|
||||
|
||||
export async function getWorkspaceContext(cline: Task, includeFileDetails: boolean) {
|
||||
if (!includeFileDetails) {
|
||||
return {}
|
||||
}
|
||||
const state = await cline.providerRef.deref()?.getState()
|
||||
const { maxWorkspaceFiles = 200, showRooIgnoredFiles = true } = state ?? {}
|
||||
|
||||
const isDesktop = arePathsEqual(cline.cwd, path.join(os.homedir(), "Desktop"))
|
||||
const workspaceData: any = { "@directory": cline.cwd.toPosix() }
|
||||
|
||||
if (isDesktop) {
|
||||
workspaceData.note = "Desktop files not shown automatically. Use list_files to explore if needed."
|
||||
} else if (maxWorkspaceFiles === 0) {
|
||||
workspaceData.note = "Workspace files context disabled. Use list_files to explore if needed."
|
||||
} else {
|
||||
const [files, didHitLimit] = await listFiles(cline.cwd, true, maxWorkspaceFiles)
|
||||
const formattedFilesList = formatResponse.formatFilesList(
|
||||
cline.cwd,
|
||||
files,
|
||||
didHitLimit,
|
||||
cline.rooIgnoreController,
|
||||
showRooIgnoredFiles,
|
||||
)
|
||||
|
||||
if (formattedFilesList && formattedFilesList !== "No files found.") {
|
||||
const fileLines = formattedFilesList.split("\n").filter((line) => line.trim() !== "")
|
||||
const fileObjects: any[] = []
|
||||
const dirObjects: any[] = []
|
||||
|
||||
fileLines.forEach((line) => {
|
||||
if (line.endsWith("/")) {
|
||||
dirObjects.push({ "@path": line })
|
||||
} else if (!line.includes("File list truncated")) {
|
||||
fileObjects.push({ "@path": line })
|
||||
}
|
||||
})
|
||||
|
||||
if (fileObjects.length > 0) workspaceData.file = fileObjects
|
||||
if (dirObjects.length > 0) workspaceData.directory = dirObjects
|
||||
if (didHitLimit) {
|
||||
workspaceData.note =
|
||||
"File list truncated. Use list_files on specific subdirectories if you need to explore further."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { workspace: workspaceData }
|
||||
}
|
||||
|
|
@ -1,259 +1,42 @@
|
|||
import path from "path"
|
||||
import os from "os"
|
||||
|
||||
import * as vscode from "vscode"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import delay from "delay"
|
||||
|
||||
import type { ExperimentId } from "@roo-code/types"
|
||||
|
||||
import { EXPERIMENT_IDS, experiments as Experiments } from "../../shared/experiments"
|
||||
import { formatLanguage } from "../../shared/language"
|
||||
import { defaultModeSlug, getFullModeDetails, getModeBySlug, isToolAllowedForMode } from "../../shared/modes"
|
||||
import { getApiMetrics } from "../../shared/getApiMetrics"
|
||||
import { listFiles } from "../../services/glob/list-files"
|
||||
import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
|
||||
import { Terminal } from "../../integrations/terminal/Terminal"
|
||||
import { arePathsEqual } from "../../utils/path"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { XMLBuilder } from "fast-xml-parser"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { formatReminderSection } from "./reminder"
|
||||
import { getVscodeEditorContext } from "./context/vscode"
|
||||
import { getTerminalContext } from "./context/terminal"
|
||||
import { getFileContext } from "./context/file"
|
||||
import { getMetadataContext } from "./context/metadata"
|
||||
import { getWorkspaceContext } from "./context/workspace"
|
||||
import { getTodoContext } from "./context/todo"
|
||||
|
||||
export async function getEnvironmentDetails(cline: Task, includeFileDetails: boolean = false) {
|
||||
let details = ""
|
||||
export async function getEnvironmentDetails(task: Task, includeFileDetails: boolean = false) {
|
||||
const [vscodeContext, terminalContext, fileContext, metadataContext, workspaceContext, todoContext] =
|
||||
await Promise.all([
|
||||
getVscodeEditorContext(task),
|
||||
getTerminalContext(task),
|
||||
getFileContext(task),
|
||||
getMetadataContext(task),
|
||||
getWorkspaceContext(task, includeFileDetails),
|
||||
getTodoContext(task),
|
||||
])
|
||||
|
||||
const clineProvider = cline.providerRef.deref()
|
||||
const state = await clineProvider?.getState()
|
||||
const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = state ?? {}
|
||||
|
||||
// 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.
|
||||
details += "\n\n# VSCode Visible Files"
|
||||
|
||||
const visibleFilePaths = vscode.window.visibleTextEditors
|
||||
?.map((editor) => editor.document?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.map((absolutePath) => path.relative(cline.cwd, absolutePath))
|
||||
.slice(0, maxWorkspaceFiles)
|
||||
|
||||
// Filter paths through rooIgnoreController
|
||||
const allowedVisibleFiles = cline.rooIgnoreController
|
||||
? cline.rooIgnoreController.filterPaths(visibleFilePaths)
|
||||
: visibleFilePaths.map((p) => p.toPosix()).join("\n")
|
||||
|
||||
if (allowedVisibleFiles) {
|
||||
details += `\n${allowedVisibleFiles}`
|
||||
} else {
|
||||
details += "\n(No visible files)"
|
||||
const envDetails = {
|
||||
...vscodeContext,
|
||||
...terminalContext,
|
||||
...fileContext,
|
||||
...metadataContext,
|
||||
...workspaceContext,
|
||||
...todoContext,
|
||||
}
|
||||
|
||||
details += "\n\n# VSCode Open Tabs"
|
||||
const { maxOpenTabsContext } = state ?? {}
|
||||
const maxTabs = maxOpenTabsContext ?? 20
|
||||
const openTabPaths = vscode.window.tabGroups.all
|
||||
.flatMap((group) => group.tabs)
|
||||
.map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.map((absolutePath) => path.relative(cline.cwd, absolutePath).toPosix())
|
||||
.slice(0, maxTabs)
|
||||
|
||||
// Filter paths through rooIgnoreController
|
||||
const allowedOpenTabs = cline.rooIgnoreController
|
||||
? cline.rooIgnoreController.filterPaths(openTabPaths)
|
||||
: openTabPaths.map((p) => p.toPosix()).join("\n")
|
||||
|
||||
if (allowedOpenTabs) {
|
||||
details += `\n${allowedOpenTabs}`
|
||||
} else {
|
||||
details += "\n(No open tabs)"
|
||||
}
|
||||
|
||||
// Get task-specific and background terminals.
|
||||
const busyTerminals = [
|
||||
...TerminalRegistry.getTerminals(true, cline.taskId),
|
||||
...TerminalRegistry.getBackgroundTerminals(true),
|
||||
]
|
||||
|
||||
const inactiveTerminals = [
|
||||
...TerminalRegistry.getTerminals(false, cline.taskId),
|
||||
...TerminalRegistry.getBackgroundTerminals(false),
|
||||
]
|
||||
|
||||
if (busyTerminals.length > 0) {
|
||||
if (cline.didEditFile) {
|
||||
await delay(300) // Delay after saving file to let terminals catch up.
|
||||
}
|
||||
|
||||
// Wait for terminals to cool down.
|
||||
await pWaitFor(() => busyTerminals.every((t) => !TerminalRegistry.isProcessHot(t.id)), {
|
||||
interval: 100,
|
||||
timeout: 5_000,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// Reset, this lets us know when to wait for saved files to update terminals.
|
||||
cline.didEditFile = false
|
||||
|
||||
// Waiting for updated diagnostics lets terminal output be the most
|
||||
// up-to-date possible.
|
||||
let terminalDetails = ""
|
||||
|
||||
if (busyTerminals.length > 0) {
|
||||
// Terminals are cool, let's retrieve their output.
|
||||
terminalDetails += "\n\n# Actively Running Terminals"
|
||||
|
||||
for (const busyTerminal of busyTerminals) {
|
||||
const cwd = busyTerminal.getCurrentWorkingDirectory()
|
||||
terminalDetails += `\n## Terminal ${busyTerminal.id} (Active)`
|
||||
terminalDetails += `\n### Working Directory: \`${cwd}\``
|
||||
terminalDetails += `\n### Original command: \`${busyTerminal.getLastCommand()}\``
|
||||
let newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id)
|
||||
|
||||
if (newOutput) {
|
||||
newOutput = Terminal.compressTerminalOutput(newOutput, terminalOutputLineLimit)
|
||||
terminalDetails += `\n### New Output\n${newOutput}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First check if any inactive terminals in this task have completed
|
||||
// processes with output.
|
||||
const terminalsWithOutput = inactiveTerminals.filter((terminal) => {
|
||||
const completedProcesses = terminal.getProcessesWithOutput()
|
||||
return completedProcesses.length > 0
|
||||
const builder = new XMLBuilder({
|
||||
format: true, // Enable pretty printing
|
||||
indentBy: " ", // Use two spaces for indentation
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@",
|
||||
suppressEmptyNode: true, // Ensures tags with no children are excluded
|
||||
cdataPropName: "#cdata",
|
||||
textNodeName: "#text",
|
||||
})
|
||||
|
||||
// Only add the header if there are terminals with output.
|
||||
if (terminalsWithOutput.length > 0) {
|
||||
terminalDetails += "\n\n# Inactive Terminals with Completed Process Output"
|
||||
|
||||
// Process each terminal with output.
|
||||
for (const inactiveTerminal of terminalsWithOutput) {
|
||||
let terminalOutputs: string[] = []
|
||||
|
||||
// Get output from completed processes queue.
|
||||
const completedProcesses = inactiveTerminal.getProcessesWithOutput()
|
||||
|
||||
for (const process of completedProcesses) {
|
||||
let output = process.getUnretrievedOutput()
|
||||
|
||||
if (output) {
|
||||
output = Terminal.compressTerminalOutput(output, terminalOutputLineLimit)
|
||||
terminalOutputs.push(`Command: \`${process.command}\`\n${output}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the queue after retrieving output.
|
||||
inactiveTerminal.cleanCompletedProcessQueue()
|
||||
|
||||
// Add this terminal's outputs to the details.
|
||||
if (terminalOutputs.length > 0) {
|
||||
const cwd = inactiveTerminal.getCurrentWorkingDirectory()
|
||||
terminalDetails += `\n## Terminal ${inactiveTerminal.id} (Inactive)`
|
||||
terminalDetails += `\n### Working Directory: \`${cwd}\``
|
||||
terminalOutputs.forEach((output) => {
|
||||
terminalDetails += `\n### New Output\n${output}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(`[Task#getEnvironmentDetails] terminalDetails: ${terminalDetails}`)
|
||||
|
||||
// Add recently modified files section.
|
||||
const recentlyModifiedFiles = cline.fileContextTracker.getAndClearRecentlyModifiedFiles()
|
||||
|
||||
if (recentlyModifiedFiles.length > 0) {
|
||||
details +=
|
||||
"\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):"
|
||||
for (const filePath of recentlyModifiedFiles) {
|
||||
details += `\n${filePath}`
|
||||
}
|
||||
}
|
||||
|
||||
if (terminalDetails) {
|
||||
details += terminalDetails
|
||||
}
|
||||
|
||||
// Add current time information with timezone.
|
||||
const now = new Date()
|
||||
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation
|
||||
const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset))
|
||||
const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60))
|
||||
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}`
|
||||
details += `\n\n# Current Time\nCurrent time in ISO 8601 UTC format: ${now.toISOString()}\nUser time zone: ${timeZone}, UTC${timeZoneOffsetStr}`
|
||||
|
||||
// Add context tokens information.
|
||||
const { contextTokens, totalCost } = getApiMetrics(cline.clineMessages)
|
||||
const { id: modelId } = cline.api.getModel()
|
||||
|
||||
details += `\n\n# Current Cost\n${totalCost !== null ? `$${totalCost.toFixed(2)}` : "(Not available)"}`
|
||||
|
||||
// Add current mode and any mode-specific warnings.
|
||||
const {
|
||||
mode,
|
||||
customModes,
|
||||
customModePrompts,
|
||||
experiments = {} as Record<ExperimentId, boolean>,
|
||||
customInstructions: globalCustomInstructions,
|
||||
language,
|
||||
} = state ?? {}
|
||||
|
||||
const currentMode = mode ?? defaultModeSlug
|
||||
|
||||
const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, {
|
||||
cwd: cline.cwd,
|
||||
globalCustomInstructions,
|
||||
language: language ?? formatLanguage(vscode.env.language),
|
||||
})
|
||||
|
||||
details += `\n\n# Current Mode\n`
|
||||
details += `<slug>${currentMode}</slug>\n`
|
||||
details += `<name>${modeDetails.name}</name>\n`
|
||||
details += `<model>${modelId}</model>\n`
|
||||
|
||||
if (Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)) {
|
||||
details += `<role>${modeDetails.roleDefinition}</role>\n`
|
||||
|
||||
if (modeDetails.customInstructions) {
|
||||
details += `<custom_instructions>${modeDetails.customInstructions}</custom_instructions>\n`
|
||||
}
|
||||
}
|
||||
|
||||
if (includeFileDetails) {
|
||||
details += `\n\n# Current Workspace Directory (${cline.cwd.toPosix()}) Files\n`
|
||||
const isDesktop = arePathsEqual(cline.cwd, path.join(os.homedir(), "Desktop"))
|
||||
|
||||
if (isDesktop) {
|
||||
// Don't want to immediately access desktop since it would show
|
||||
// permission popup.
|
||||
details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
|
||||
} else {
|
||||
const maxFiles = maxWorkspaceFiles ?? 200
|
||||
|
||||
// Early return for limit of 0
|
||||
if (maxFiles === 0) {
|
||||
details += "(Workspace files context disabled. Use list_files to explore if needed.)"
|
||||
} else {
|
||||
const [files, didHitLimit] = await listFiles(cline.cwd, true, maxFiles)
|
||||
const { showRooIgnoredFiles = true } = state ?? {}
|
||||
|
||||
const result = formatResponse.formatFilesList(
|
||||
cline.cwd,
|
||||
files,
|
||||
didHitLimit,
|
||||
cline.rooIgnoreController,
|
||||
showRooIgnoredFiles,
|
||||
)
|
||||
|
||||
details += result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reminderSection = formatReminderSection(cline.todoList)
|
||||
return `<environment_details>\n${details.trim()}\n${reminderSection}\n</environment_details>`
|
||||
return builder.build({ environment_details: envDetails })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { TodoItem, TodoStatus } from "@roo-code/types"
|
||||
|
||||
/**
|
||||
* Format the reminders section as a markdown block in English, with basic instructions.
|
||||
*/
|
||||
export function formatReminderSection(todoList?: TodoItem[]): string {
|
||||
if (!todoList || todoList.length === 0) {
|
||||
return "You have not created a todo list yet. Create one with `update_todo_list` if your task is complicated or involves multiple steps."
|
||||
}
|
||||
const statusMap: Record<TodoStatus, string> = {
|
||||
pending: "Pending",
|
||||
in_progress: "In Progress",
|
||||
completed: "Completed",
|
||||
}
|
||||
const lines: string[] = [
|
||||
"====",
|
||||
"",
|
||||
"REMINDERS",
|
||||
"",
|
||||
"Below is your current list of reminders for this task. Keep them updated as you progress.",
|
||||
"",
|
||||
]
|
||||
|
||||
lines.push("| # | Content | Status |")
|
||||
lines.push("|---|---------|--------|")
|
||||
todoList.forEach((item, idx) => {
|
||||
const escapedContent = item.content.replace(/\\/g, "\\\\").replace(/\|/g, "\\|")
|
||||
lines.push(`| ${idx + 1} | ${escapedContent} | ${statusMap[item.status] || item.status} |`)
|
||||
})
|
||||
lines.push("")
|
||||
|
||||
lines.push(
|
||||
"",
|
||||
"IMPORTANT: When task status changes, remember to call the `update_todo_list` tool to update your progress.",
|
||||
"",
|
||||
)
|
||||
return lines.join("\n")
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue