Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/coding-agent/src/session/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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}`;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/coding-agent/src/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions packages/coding-agent/src/utils/local-date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** formatLocalCalendarDate formats a Date as YYYY-MM-DD in the host local timezone. */
Comment thread
paralin marked this conversation as resolved.
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}`;
}
30 changes: 19 additions & 11 deletions packages/coding-agent/test/agent-session-tool-rebuild-skip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment thread
paralin marked this conversation as resolved.
Outdated
setSystemTime(new Date("2026-07-01T06:59:58Z"));
try {
let rebuildCount = 0;
const { session } = newSession(async toolNames => {
Expand All @@ -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 () => {
Expand Down
29 changes: 28 additions & 1 deletion packages/coding-agent/test/system-prompt-model.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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";
Comment thread
paralin marked this conversation as resolved.
Outdated
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,
Expand Down