diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index da4492fc4..5e49cba05 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -314,6 +314,7 @@ import { resolveFileDisplayMode } from "../utils/file-display-mode"; import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions"; import { normalizeModelContextImages } from "../utils/image-loading"; import { describeAttachedImagesForTextModel } from "../utils/image-vision-fallback"; +import { formatLocalCalendarDate } from "../utils/local-date"; import { generateSessionTitle } from "../utils/title-generator"; import { buildNamedToolChoice, isToolChoiceActive } from "../utils/tool-choice"; import type { AuthStorage } from "./auth-storage"; @@ -6517,7 +6518,7 @@ export class AgentSession { entries.sort(); instructionsSegment = entries.join("\u0006"); } - const date = new Date().toISOString().slice(0, 10); + const date = formatLocalCalendarDate(); return `${nameSegment}\u0003${descriptionSegment}\u0005${registrySegment}\u0007${instructionsSegment}|${date}`; } diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 890e37484..196be0dee 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -24,6 +24,7 @@ import projectPromptTemplate from "./prompts/system/project-prompt.md" with { ty import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" }; import { shortenPath } from "./tools/render-utils"; import { type ActiveRepoContext, resolveActiveRepoContext } from "./utils/active-repo-context"; +import { formatLocalCalendarDate } from "./utils/local-date"; import { normalizePromptPath } from "./utils/prompt-path"; import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree"; @@ -677,7 +678,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): } } - const date = new Date().toISOString().slice(0, 10); + const date = formatLocalCalendarDate(); const dateTime = date; const promptCwd = shortenPath(normalizePromptPath(resolvedCwd)); const activeRepoContextPrompt = renderActiveRepoContextPrompt(activeRepoContext); diff --git a/packages/coding-agent/src/utils/local-date.ts b/packages/coding-agent/src/utils/local-date.ts new file mode 100644 index 000000000..80962f102 --- /dev/null +++ b/packages/coding-agent/src/utils/local-date.ts @@ -0,0 +1,7 @@ +/** formatLocalCalendarDate formats a Date as YYYY-MM-DD in the host local timezone. */ +export function formatLocalCalendarDate(date: Date = new Date()): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} diff --git a/packages/coding-agent/test/agent-session-tool-rebuild-skip.test.ts b/packages/coding-agent/test/agent-session-tool-rebuild-skip.test.ts index 6cefd9c5b..53f795f2c 100644 --- a/packages/coding-agent/test/agent-session-tool-rebuild-skip.test.ts +++ b/packages/coding-agent/test/agent-session-tool-rebuild-skip.test.ts @@ -388,11 +388,14 @@ describe("AgentSession refreshMCPTools rebuild skipping", () => { await session.refreshMCPTools([dynamicTool]); expect(rebuildCount).toBe(baseline + 1); }); - it("rebuilds when the calendar date rolls over between tool-stable MCP refreshes", async () => { - // `buildSystemPrompt` injects today's date into the prompt body. - // A session spanning midnight must not serve yesterday's date after an MCP - // reconnect that happens to bring an identical tool set. - setSystemTime(new Date("2025-01-01T23:59:58Z")); + it("rebuilds when the local calendar date rolls over between tool-stable MCP refreshes", async () => { + // `buildSystemPrompt` injects today's date into the prompt body. A session + // spanning local midnight must not serve yesterday's date after an MCP + // reconnect that happens to bring an identical tool set, even when UTC has + // not rolled over. + const originalTimezone = process.env.TZ; + process.env.TZ = "America/Los_Angeles"; + setSystemTime(new Date("2026-07-01T06:59:58Z")); try { let rebuildCount = 0; const { session } = newSession(async toolNames => { @@ -405,22 +408,27 @@ describe("AgentSession refreshMCPTools rebuild skipping", () => { await session.refreshMCPTools([tool]); expect(rebuildCount).toBe(1); - // Same tools, same day: signature matches, skip. + // Same tools, same local day: signature matches, skip. await session.refreshMCPTools([tool]); expect(rebuildCount).toBe(1); - // Advance past midnight. - setSystemTime(new Date("2025-01-02T00:00:01Z")); + // Advance past local midnight while the UTC date remains 2026-07-01. + setSystemTime(new Date("2026-07-01T07:00:01Z")); - // Same tools, new calendar day: date segment changed, must rebuild. + // Same tools, new local calendar day: date segment changed, must rebuild. await session.refreshMCPTools([tool]); expect(rebuildCount).toBe(2); - // Same tools, same new day: skip again. + // Same tools, same new local day: skip again. await session.refreshMCPTools([tool]); expect(rebuildCount).toBe(2); } finally { - setSystemTime(); // restore real time + setSystemTime(); + if (originalTimezone === undefined) { + delete process.env.TZ; + } else { + process.env.TZ = originalTimezone; + } } }); it("does not rebuild when MCP server instructions change only beyond the 4000-char truncation boundary", async () => { diff --git a/packages/coding-agent/test/system-prompt-model.test.ts b/packages/coding-agent/test/system-prompt-model.test.ts index 8a0eba4f3..3f253052e 100644 --- a/packages/coding-agent/test/system-prompt-model.test.ts +++ b/packages/coding-agent/test/system-prompt-model.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, setSystemTime } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; @@ -49,6 +49,33 @@ describe("system prompt model identifier", () => { expect(systemPrompt.join("\n\n")).toContain("Model: anthropic/claude-opus-4"); }); + it("renders the prompt date from the local timezone rather than UTC", async () => { + const originalTimezone = process.env.TZ; + process.env.TZ = "America/Los_Angeles"; + setSystemTime(new Date("2026-07-01T03:15:00Z")); + try { + const { systemPrompt } = await buildSystemPrompt({ + cwd: tempDir, + contextFiles: [], + skills: [], + rules: [], + toolNames: [], + workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, + }); + const rendered = systemPrompt.join("\n\n"); + + expect(rendered).toContain("Today is 2026-06-30"); + expect(rendered).not.toContain("Today is 2026-07-01"); + } finally { + setSystemTime(); + if (originalTimezone === undefined) { + delete process.env.TZ; + } else { + process.env.TZ = originalTimezone; + } + } + }); + it("omits the model line when no model is provided", async () => { const { systemPrompt } = await buildSystemPrompt({ cwd: tempDir,