evOo-local/packages/adapters/src/current-time.test.ts
Claude 4875753d9c
feat(memory): remember appends dated notes instead of replacing memory
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
2026-10-03 13:10:25 +00:00

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");
});
});