Runs dropped the bot's name, title and description whenever custom instructions existed, always stated the time in UTC, and knew the user only through free-form durable memory. Groups with a lead, like the main conversation, still got the peer-group lines that forbid handing work back. And every group follow-up run failed during setup: the inbound handoff lookup selected a `bot` relation that Message does not have, which Prisma rejects. - Instructions now open with the bot's identity, then its own instructions, a main-agent role paragraph for the main bot, the current time in the user's time zone with its offset (UTC when unknown), and a `<user>` block built from loadUserContext: profile fields and the active facts visible in the run's space, escaped, clipped and framed as data rather than instructions. Runs that reply in a shared messaging channel get only the name, zone, locale, language and style, matching how those runs already leave out durable memory. - run_subagent helpers receive the identity, time and user block through a new optional AgentRunRequest.subagentContext instead of nothing. - Groups with an active lead get lead and specialist lines and matching inbound handoff instructions. Members may hand a stage back to the lead and the lead may delegate the next stage; the bounce rules still apply between members. Ordinary groups keep today's lines. - The teammate directory is rendered only when the run has message_bot, which group runs never have, and tool guidance names a tool only when the run has it. - The auto-review judge, history compaction and the team chat judge read the time in the user's zone, and schedule_create defaults to it. - The inbound handoff sender is read from the handoff block and looked up among the user's bots; a Postgres test covers the follow-up run, the prompt it receives, and handing the result back to the lead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
import type { PrismaClient } from "@rakazo/db";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { formatCurrentTimeInstruction, 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();
|
|
});
|
|
});
|