Files
openclaw/src/gateway/server-methods/agent-timestamp.ts
T
Peter Steinberger edecdbd05e refactor(config): config-surface reduction tranche 3 — product consolidations (review request) (#111527)
* refactor(config): consolidate media model lists

* refactor(config): unify memory configuration

* refactor(config): consolidate TTS ownership

* refactor(config): move typing policy to agents

* refactor(config): retire product-level config surfaces

* refactor(config): share scoped tool policy type

* chore(config): refresh generated baselines

* fix(config): honor agent typing overrides

* fix(config): migrate sibling config consumers

* refactor(infra): keep base64url decoder private

* fix(config): strip invalid legacy TTS values

* chore(config): refresh rebased baseline hash

* fix(doctor): route legacy messages.tts.realtime voice to talk during tts move

* refactor(config): polish final layout names

* refactor(config): freeze retired tuning defaults

* feat(config): add fast mode default symmetry

* refactor(config): key agent entries by id

* docs(config): update final layout reference

* test(config): cover final layout migrations

* chore(config): refresh final layout baselines

* fix(config): align final layout runtime readers

* fix(config): align remaining readers

* fix(config): stabilize final layout migrations

* fix(config): finalize config projection proof

* fix(config): address final layout review

* docs(release): preserve historical config names

* fix(config): complete keyed agent migration

* fix(config): close final migration gaps

* fix(config): finish full-branch review

* fix(config): complete runtime secret detection

* fix(config): close final review findings

* fix(config): finish canonical docs and heartbeat migration

* fix(config): integrate latest main after rebase

* refactor(env): isolate test-only controls

* refactor(env): isolate build and development controls

* refactor(env): collapse process identity indirection

* refactor(env): remove duplicate config and temp aliases

* docs(env): define the operator-facing allowlist

* ci(env): ratchet production variable count

* fix(env): remove stale provider helper import

* fix(env): make ratchet sorting explicit

* test(env): keep test seam in dead-code audit

* test(env): cover ratchet growth and boundary; document surface budgets

* docs(config): document tier-eval consolidations

* docs(config): clarify speech preference ownership

* test(memory): align retired tuning fixtures

* refactor(memory): freeze engine heuristics

* refactor(config): apply tier-eval tranche

* refactor(tts): move persona shaping to providers

* refactor(compaction): move prompt policy to providers

* test(config): align hookified prompt fixtures

* chore(deadcode): classify test-only exports

* chore(github): remove unused spawn helper

* chore(deadcode): classify queue diagnostics

* chore(deadcode): remove unused lane snapshot export

* chore(plugin-sdk): ratchet consolidated surface

* fix(config): integrate latest main after rebase
2026-07-21 20:28:43 -07:00

111 lines
3.9 KiB
TypeScript

// Agent timestamp injection adds compact local-time context to direct gateway
// agent messages without double-stamping channel envelopes or cron prompts.
import { resolveUserTimezone } from "../../agents/date-time.js";
import type { OpenClawConfig } from "../../config/types.js";
import { formatZonedTimestamp } from "../../infra/format-time/format-datetime.ts";
/**
* Cron jobs inject "Current time: ..." into their messages.
* Skip injection for those.
*/
const CRON_TIME_MARKER = "Current time: ";
/**
* Matches a leading `[... YYYY-MM-DD HH:MM ...]` envelope — either from
* channel plugins or from a previous injection. Uses the same YYYY-MM-DD
* HH:MM format as {@link formatZonedTimestamp}, so detection stays in sync
* with the formatting.
*/
const TIMESTAMP_ENVELOPE_PATTERN = /^\[.*\d{4}-\d{2}-\d{2} \d{2}:\d{2}/;
interface TimestampInjectionOptions {
timezone?: string;
now?: Date;
includeTimestamp?: boolean;
}
/**
* Build a `[DOW YYYY-MM-DD HH:MM TZ] ` prefix string from an explicit date.
*
* Returns undefined if formatting fails (malformed timezone etc.).
* Does NOT guard against TIMESTAMP_ENVELOPE_PATTERN or CRON_TIME_MARKER —
* callers that need those guards should use {@link injectTimestamp} instead.
*
* This is the primitive used by the persistence path to stamp each stored
* message with ITS OWN arrival timestamp (not the current wall-clock time),
* so historical messages carry a stable, immutable prefix.
*/
export function buildTimestampPrefix(
date: Date,
opts?: Pick<TimestampInjectionOptions, "timezone">,
): string | undefined {
const timezone = opts?.timezone ?? "UTC";
const formatted = formatZonedTimestamp(date, { timeZone: timezone });
if (!formatted) {
return undefined;
}
// 3-letter DOW: small models (8B) can't reliably derive day-of-week from
// a date, and may treat a bare "Wed" as a typo. Costs ~1 token.
const dow = new Intl.DateTimeFormat("en-US", { timeZone: timezone, weekday: "short" }).format(
date,
);
return `[${dow} ${formatted}] `;
}
/**
* Injects a compact timestamp prefix into a message if one isn't already
* present. Uses the same `YYYY-MM-DD HH:MM TZ` format as channel envelope
* timestamps ({@link formatZonedTimestamp}), keeping token cost low (~7
* tokens) and format consistent across all agent contexts.
*
* NOTE: The standard user-turn path no longer calls this. Per-message stamps
* are now applied once at the LLM boundary (normalizeMessagesForLlmBoundary)
* from each message's own timestamp, so storage stays bare and the current and
* historical sends are byte-identical — eliminating the prompt-cache bust
* described in issue #3658. This helper is retained only for any remaining
* non-user-turn callers and as the shared prefix primitive's wrapper.
*
* Channel messages (Discord, Telegram, etc.) already have timestamps via
* envelope formatting and take a separate code path — they never reach
* these handlers, so there is no double-stamping risk. The detection
* pattern is a safety net for edge cases.
*
* @see https://github.com/openclaw/openclaw/issues/3658
*/
export function injectTimestamp(message: string, opts?: TimestampInjectionOptions): string {
if (opts?.includeTimestamp === false) {
return message;
}
if (!message.trim()) {
return message;
}
// Already has an envelope or injected timestamp
if (TIMESTAMP_ENVELOPE_PATTERN.test(message)) {
return message;
}
// Already has a cron-injected timestamp
if (message.includes(CRON_TIME_MARKER)) {
return message;
}
const now = opts?.now ?? new Date();
const prefix = buildTimestampPrefix(now, opts);
if (!prefix) {
return message;
}
return `${prefix}${message}`;
}
/**
* Build TimestampInjectionOptions from an OpenClawConfig.
*/
export function timestampOptsFromConfig(cfg: OpenClawConfig): TimestampInjectionOptions {
return {
timezone: resolveUserTimezone(cfg.agents?.defaults?.userTimezone),
includeTimestamp: true,
};
}