refactor: move dynamic editor context from system prompt to user message

Split editor context into static and dynamic parts:

- Static (system prompt, cached): date, timezone, shell
  These rarely change and benefit from prompt caching.

- Dynamic (user message, per-turn): active file, visible files, open tabs
  These change frequently as the user switches files/tabs. Injected as a
  synthetic <environment_details> block on each user message so the model
  always has fresh context.

This matches the pattern used by the VS Code extension, where
getEnvironmentDetails() is called before every agent turn.
This commit is contained in:
Mark IJbema
2026-03-17 15:00:48 +01:00
parent fe6760dc20
commit f6da5ded83
3 changed files with 45 additions and 16 deletions
@@ -6,36 +6,50 @@ export interface EditorContext {
timezone?: string
}
function formatDate(timezone?: string): string[] {
/**
* Build static <env> lines from editor context.
* These rarely change during a session and belong in the system prompt
* so they benefit from prompt caching.
*/
export function staticEnvLines(ctx?: EditorContext): string[] {
const now = new Date()
const lines = [` Today's date: ${now.toDateString()}`]
if (timezone) {
if (ctx?.timezone) {
const offset = -now.getTimezoneOffset()
const sign = offset >= 0 ? "+" : "-"
const hours = Math.floor(Math.abs(offset) / 60)
const mins = Math.abs(offset) % 60
lines.push(` User timezone: ${timezone}, UTC${sign}${hours}:${mins.toString().padStart(2, "0")}`)
lines.push(` User timezone: ${ctx.timezone}, UTC${sign}${hours}:${mins.toString().padStart(2, "0")}`)
}
if (ctx?.shell) {
lines.push(` Default shell: ${ctx.shell}`)
}
return lines
}
/**
* Build additional <env> lines from VS Code editor context.
* Returns an array of pre-formatted ` key: value` strings.
* Build a per-message <environment_details> block from editor context.
* These change frequently (user switches files/tabs) and belong in the
* user message so the model always has fresh context.
* Returns undefined when there is nothing dynamic to report.
*/
export function editorContextEnvLines(ctx?: EditorContext): string[] {
const lines = formatDate(ctx?.timezone)
if (ctx?.shell) {
lines.push(` Default shell: ${ctx.shell}`)
}
export function environmentDetails(ctx?: EditorContext): string | undefined {
const lines: string[] = []
if (ctx?.activeFile) {
lines.push(` Active file: ${ctx.activeFile}`)
lines.push(`Active file: ${ctx.activeFile}`)
}
if (ctx?.visibleFiles?.length) {
lines.push(` Visible files: ${ctx.visibleFiles.join(", ")}`)
lines.push(`Visible files:`)
for (const f of ctx.visibleFiles) {
lines.push(` ${f}`)
}
}
if (ctx?.openTabs?.length) {
lines.push(` Open tabs: ${ctx.openTabs.join(", ")}`)
lines.push(`Open tabs:`)
for (const f of ctx.openTabs) {
lines.push(` ${f}`)
}
}
return lines
if (lines.length === 0) return undefined
return ["<environment_details>", ...lines, "</environment_details>"].join("\n")
}
+15
View File
@@ -47,6 +47,7 @@ import { iife } from "@/util/iife"
import { Shell } from "@/shell/shell"
import { Truncate } from "@/tool/truncation"
import { PlanFollowup } from "@/kilocode/plan-followup" // kilocode_change
import { environmentDetails } from "@/kilocode/editor-context" // kilocode_change
// @ts-ignore
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -1355,6 +1356,20 @@ export namespace SessionPrompt {
}),
).then((x) => x.flat().map(assign))
// kilocode_change start — inject dynamic editor context into user message
const envBlock = environmentDetails(input.editorContext)
if (envBlock) {
parts.push({
id: Identifier.ascending("part"),
messageID: info.id,
sessionID: input.sessionID,
type: "text",
text: envBlock,
synthetic: true,
})
}
// kilocode_change end
await Plugin.trigger(
"chat.message",
{
+2 -2
View File
@@ -13,7 +13,7 @@ import type { Provider } from "@/provider/provider"
// kilocode_change start
import SOUL from "../kilocode/soul.txt"
import { editorContextEnvLines, type EditorContext } from "../kilocode/editor-context"
import { staticEnvLines, type EditorContext } from "../kilocode/editor-context"
// kilocode_change end
export namespace SystemPrompt {
@@ -67,7 +67,7 @@ export namespace SystemPrompt {
` Working directory: ${Instance.directory}`,
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
` Platform: ${process.platform}`,
...editorContextEnvLines(editorContext), // kilocode_change
...staticEnvLines(editorContext), // kilocode_change
`</env>`,
`<directories>`,
` ${