The remember tool rewrote the bot's whole MEMORY.md with each call, so every
new fact erased the earlier ones unless the model re-sent the full document.
- MemoryCommitRequest gains mode "append" (the store joins the note onto the
current content inside the same serializable transaction and still writes a
revision row) and every commit is capped at 16 KB per document.
- remember appends a dated bullet ("- YYYY-MM-DD: ...", the user's local date)
by default and rewrites only with mode "replace"; a full document returns an
error that tells the bot to condense it. Retries of the same tool call stay
single appends through the existing effect record.
- Scripted runtime memory entries can append too; the scripted "remember"
script now appends instead of overwriting the bot's memory.
- When memory does not fit the run's 32 KB budget, the prompt keeps a
document's newest lines instead of its oldest.
- memory.update maps an oversized save to BAD_REQUEST.
- docs/PRODUCT.md records that UserFact is the cross-space user model and the
space MEMORY.md stays space notes (no migration).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
import type { PrismaClient } from "@rakazo/db";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { formatCurrentTimeInstruction, formatLocalDate, loadUserTimeZone } from "./current-time.js";
|
|
|
|
describe("formatCurrentTimeInstruction", () => {
|
|
it("anchors the present moment in UTC with the weekday and no sub-second noise", () => {
|
|
const line = formatCurrentTimeInstruction(null, new Date("2026-09-17T14:55:41.123Z"));
|
|
expect(line).toContain("Current date and time: Thursday, 2026-09-17T14:55:41Z (UTC).");
|
|
expect(line).not.toContain(".123");
|
|
expect(line).toContain("convert it to the user's time zone when one is known");
|
|
});
|
|
|
|
it("states the user's local time with the zone and offset", () => {
|
|
const line = formatCurrentTimeInstruction(
|
|
"Europe/Berlin",
|
|
new Date("2026-09-17T14:55:41.123Z"),
|
|
);
|
|
expect(line).toContain(
|
|
"Current date and time: Thursday, 2026-09-17T16:55:41+02:00 (Europe/Berlin, the user's time zone).",
|
|
);
|
|
expect(line).not.toContain("convert it to the user's time zone");
|
|
});
|
|
|
|
it("uses the local date when the zone is still on the previous day", () => {
|
|
const line = formatCurrentTimeInstruction(
|
|
"America/Los_Angeles",
|
|
new Date("2026-01-01T03:00:00Z"),
|
|
);
|
|
expect(line).toContain("Wednesday, 2025-12-31T19:00:00-08:00 (America/Los_Angeles");
|
|
});
|
|
|
|
it("prints fractional-hour offsets", () => {
|
|
const now = new Date("2026-09-17T14:55:41Z");
|
|
expect(formatCurrentTimeInstruction("Asia/Kolkata", now)).toContain(
|
|
"2026-09-17T20:25:41+05:30 (Asia/Kolkata",
|
|
);
|
|
expect(formatCurrentTimeInstruction("Asia/Kathmandu", now)).toContain(
|
|
"2026-09-17T20:40:41+05:45 (Asia/Kathmandu",
|
|
);
|
|
});
|
|
|
|
it("marks a user who lives in UTC with Z", () => {
|
|
expect(formatCurrentTimeInstruction("UTC", new Date("2026-09-17T14:55:41Z"))).toContain(
|
|
"Thursday, 2026-09-17T14:55:41Z (UTC, the user's time zone).",
|
|
);
|
|
});
|
|
|
|
it("falls back to UTC for an unknown zone", () => {
|
|
expect(
|
|
formatCurrentTimeInstruction("Mars/Olympus", new Date("2026-09-17T14:55:41Z")),
|
|
).toContain("Current date and time: Thursday, 2026-09-17T14:55:41Z (UTC).");
|
|
});
|
|
|
|
it("tells the model not to guess the date from training data or quoted timestamps", () => {
|
|
const line = formatCurrentTimeInstruction(null, new Date("2026-01-01T00:00:00Z"));
|
|
expect(line).toContain("Never infer today's date from your training data");
|
|
expect(line).toContain("write absolute dates");
|
|
});
|
|
|
|
it("defaults to the real clock", () => {
|
|
const before = Date.now();
|
|
const line = formatCurrentTimeInstruction(undefined);
|
|
const match = /(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})Z/.exec(line);
|
|
expect(match).not.toBeNull();
|
|
const stamped = Date.parse(`${match![1]}Z`);
|
|
expect(Math.abs(stamped - before)).toBeLessThan(5_000);
|
|
});
|
|
});
|
|
|
|
describe("loadUserTimeZone", () => {
|
|
const prismaWith = (timeZone: string | null | undefined) => {
|
|
const findUnique = vi.fn(async () => (timeZone === undefined ? null : { timeZone }));
|
|
return {
|
|
prisma: { userProfile: { findUnique } } as unknown as PrismaClient,
|
|
findUnique,
|
|
};
|
|
};
|
|
|
|
it("reads the zone from the user's profile", async () => {
|
|
const { prisma, findUnique } = prismaWith("Europe/Berlin");
|
|
await expect(loadUserTimeZone(prisma, "user-1")).resolves.toBe("Europe/Berlin");
|
|
expect(findUnique).toHaveBeenCalledWith({
|
|
where: { userId: "user-1" },
|
|
select: { timeZone: true },
|
|
});
|
|
});
|
|
|
|
it("returns nothing without a profile, a zone, or a zone the runtime knows", async () => {
|
|
await expect(loadUserTimeZone(prismaWith(undefined).prisma, "user-1")).resolves.toBeUndefined();
|
|
await expect(loadUserTimeZone(prismaWith(null).prisma, "user-1")).resolves.toBeUndefined();
|
|
await expect(
|
|
loadUserTimeZone(prismaWith("Mars/Olympus").prisma, "user-1"),
|
|
).resolves.toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("formatLocalDate", () => {
|
|
it("is the user's local date, or the UTC date without a known zone", () => {
|
|
const now = new Date("2026-01-01T03:00:00Z");
|
|
expect(formatLocalDate("America/Los_Angeles", now)).toBe("2025-12-31");
|
|
expect(formatLocalDate("Not/AZone", now)).toBe("2026-01-01");
|
|
expect(formatLocalDate(null, now)).toBe("2026-01-01");
|
|
});
|
|
});
|