diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 99aefe46b..af9cf2c31 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -969,6 +969,24 @@ export namespace SessionPrompt { await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) + // altimate_change start — upstream_fix: carry the date in the trailing user message + // so it stays out of the cache-controlled system prefix (see session/system.ts). + const lastUserForDate = msgs.findLast((m) => m.info.role === "user") + if (lastUserForDate) { + lastUserForDate.parts = [ + ...lastUserForDate.parts, + { + type: "text" as const, + id: PartID.ascending(), + sessionID, + messageID: lastUserForDate.info.id, + text: `\n\n${SystemPrompt.currentDate()}`, + synthetic: true, + }, + ] + } + // altimate_change end + // Build system prompt, adding structured output instruction if needed const skills = await SystemPrompt.skills(agent) // altimate_change start - unified context-aware injection for memory + training diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 475d595c1..f468d4b64 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -75,7 +75,12 @@ export namespace SystemPrompt { ` Workspace root folder: ${Instance.worktree}`, ` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`, ` Platform: ${process.platform}`, - ` Today's date: ${new Date().toDateString()}`, + // altimate_change start — upstream_fix: move volatile date out of cached system prefix + // The date changes daily but environment() is the first entry in the system[] + // array, which applyCaching() marks with cacheControl. A session crossing midnight + // within the cache TTL would otherwise invalidate the whole system prefix. The date + // is appended to the trailing user message instead (see session/prompt.ts). + // altimate_change end ``, ``, ` ${ @@ -91,6 +96,12 @@ export namespace SystemPrompt { ] } + // altimate_change start — upstream_fix: date carried outside the cached system prefix + export function currentDate() { + return `Today's date is ${new Date().toDateString()}.` + } + // altimate_change end + export async function skills(agent: Agent.Info) { if (PermissionNext.disabled(["skill"], agent.permission).has("skill")) return diff --git a/packages/opencode/test/session/system.test.ts b/packages/opencode/test/session/system.test.ts index a6568b947..802eda4a3 100644 --- a/packages/opencode/test/session/system.test.ts +++ b/packages/opencode/test/session/system.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect, setSystemTime, test } from "bun:test" import path from "path" import { Agent } from "../../src/agent/agent" import { Instance } from "../../src/project/instance" @@ -160,4 +160,27 @@ description: ${description} process.env.OPENCODE_TEST_HOME = home } }) + + test("environment() keeps the volatile date out of the cached system prefix", async () => { + // Freeze the clock so the captured `today` and the `new Date()` inside + // currentDate() read the same instant — otherwise the assertion can race + // across midnight. + setSystemTime(new Date("2026-06-17T12:00:00.000Z")) + try { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const today = new Date().toDateString() + const [env] = await SystemPrompt.environment(makeModel({ apiId: "claude-3-7-sonnet" })) + expect(env).toMatch(//) + expect(env).not.toMatch(/Today's date/) + expect(env).not.toContain(today) + expect(SystemPrompt.currentDate()).toContain(today) + }, + }) + } finally { + setSystemTime() + } + }) })