/** * Date/cwd reminder injection. * * The system prompt must stay byte-stable so open-weight chat templates that * render tool schemas *after* the system content keep their prefix cache * (#7404). The per-request date/cwd line used to live at the tail of the * system prompt (`project-prompt.md`), which invalidated the whole tool array * on every directory change or day rollover. It now rides on the first user * turn of each provider request instead: built at request time, deterministic * per `(date, cwd)`, so the bytes are stable for the lifetime of a session/day * and refresh automatically at midnight. Each placement is journaled in the * session ({@link DATE_CWD_REMINDER_CUSTOM_TYPE}), so a resumed session or a * revived subagent sends its earlier turns with the reminders they were sent * with instead of today's. */ import type { Context, Message } from "@oh-my-pi/pi-ai"; import type { SessionEntry } from "./session-entries.js"; /** Renders the reminder text for the given local calendar date and cwd. */ export declare function renderDateCwdReminder(date: string, cwd: string): string; /** Session custom entry recording one reminder an injector placed. */ export declare const DATE_CWD_REMINDER_CUSTOM_TYPE = "date-cwd-reminder"; /** One reminder as sent: prepended to a user turn, or a developer turn placed after a turn. */ export interface DateCwdReminderPlacement { /** Key of the first turn of the conversation the placement belongs to, as that conversation began. */ conversation: string; /** Key of the user turn the reminder prefixes, or of the turn the developer turn follows. */ turn: string; reminder: string; placement: "prefix" | "control"; /** * On the conversation's own first turn: a digest of that turn, which finds the conversation again * when a context hook re-creates its first turn under a new key on every request. */ firstTurn?: string; /** * On a control moved off a per-call message an earlier injector anchored it on: that old anchor. * It replaces the control journaled there, so a resumed session keeps the move instead of * re-deriving it from a request whose shape has since changed. */ movedFrom?: string; } /** Where an injector records the reminders it places, and reads back those an earlier injector placed. */ export interface DateCwdReminderJournal { /** Placements recorded on the session's current branch since its last `/clear`, oldest first. */ load(): readonly DateCwdReminderPlacement[]; record(placement: DateCwdReminderPlacement): void; /** * Names the history the journal reads and writes; it changes when that history is replaced * rather than continued (a switched-in session, a move to another branch of the tree). The * injector then forgets what it sent: that history's turns were sent with its own reminders. */ scope?(): string | undefined; } /** A journal kept as custom entries on the session's branch: fork, branch switch and resume read the right set. */ export declare function createSessionDateCwdReminderJournal(sessionManager: { getBranchView(): readonly SessionEntry[]; appendCustomEntry(customType: string, data?: unknown): string; getSessionId(): string; getLeafId(): string | null; getEntry(id: string): SessionEntry | undefined; }): DateCwdReminderJournal; /** Whether `message` is a developer turn a {@link DateCwdReminderInjector} inserted. */ export declare function isDateCwdReminderControl(message: Message): boolean; /** * Keeps volatile date/cwd reminders append-only across provider requests. * * The first value is attached to the first user turn. A changed value attaches * to a newly appended user turn or a persistent developer turn, leaving every * previously sent message byte-identical. Neither is ever a per-call message * (one a context hook or side request builds for a single provider call): the * next request can leave it out or move it, and the reminder would go with it. * * Turns are recognised by a key, not by object identity: context hooks, secret * obfuscation and conversion-cache resets hand the injector equal but newly * allocated messages on every request, and an identity miss on the first user * turn would read as a new conversation and rewrite the reminder that turn was * first sent with. The key leaves content out, so a turn whose content a later * request rewrites (a newly redacted secret, a clamped image) stays the same turn. * * One injector can see more than one conversation: a compaction or a branch * switch starts another history, and a side request goes through the main * loop's transform. State is therefore held per conversation root, so a request * for one conversation leaves the reminders of the others alone. Each advisor * has an injector of its own: two advisors can start their histories with turns * that share a key, so a root key alone cannot tell them apart. * * With a journal, every placement is recorded, and a conversation this injector * has not served starts from the placements an earlier injector recorded for it * (a resumed session, a revived subagent, a switched-in transcript). */ export declare class DateCwdReminderInjector { #private; constructor(journal?: DateCwdReminderJournal); /** Apply the current reminder while preserving all earlier injected bytes. */ transform(context: Context, date: string, cwd: string): Context; }