diff --git a/CHANGELOG.md b/CHANGELOG.md index 558f67cb3..6c755959c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Docs: https://docs.clawd.bot - Signal: add typing indicators and DM read receipts via signal-cli. - MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. +### Breaking +- **BREAKING:** Envelope timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. + ### Fixes - Config: avoid stack traces for invalid configs and log the config path. - Doctor: warn when gateway.mode is unset with configure/config guidance. diff --git a/docs/concepts/timezone.md b/docs/concepts/timezone.md index 3a6d3a4dd..b8b168cc9 100644 --- a/docs/concepts/timezone.md +++ b/docs/concepts/timezone.md @@ -9,15 +9,15 @@ read_when: Clawdbot standardizes timestamps so the model sees a **single reference time**. -## Message envelopes (UTC by default) +## Message envelopes (local by default) Inbound messages are wrapped in an envelope like: ``` -[Provider ... 2026-01-05T21:26Z] message text +[Provider ... 2026-01-05 16:26 PST] message text ``` -The timestamp in the envelope is **UTC by default**, with minutes precision. +The timestamp in the envelope is **host-local by default**, with minutes precision. You can override this with: @@ -25,7 +25,7 @@ You can override this with: { agents: { defaults: { - envelopeTimezone: "user", // "utc" | "local" | "user" | IANA timezone + envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone envelopeTimestamp: "on", // "on" | "off" envelopeElapsed: "on" // "on" | "off" } @@ -33,6 +33,7 @@ You can override this with: } ``` +- `envelopeTimezone: "utc"` uses UTC. - `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to host timezone). - Use an explicit IANA timezone (e.g., `"Europe/Vienna"`) for a fixed offset. - `envelopeTimestamp: "off"` removes absolute timestamps from envelope headers. @@ -40,10 +41,10 @@ You can override this with: ### Examples -**UTC (default):** +**Local (default):** ``` -[Signal Alice +1555 2026-01-18T05:19Z] hello +[Signal Alice +1555 2026-01-18 00:19 PST] hello ``` **Fixed timezone:** diff --git a/docs/date-time.md b/docs/date-time.md index 99da67630..97383ef38 100644 --- a/docs/date-time.md +++ b/docs/date-time.md @@ -7,18 +7,18 @@ read_when: # Date & Time -Clawdbot defaults to **UTC for transport timestamps** and **user-local time only in the system prompt**. +Clawdbot defaults to **host-local time for transport timestamps** and **user-local time only in the system prompt**. Provider timestamps are preserved so tools keep their native semantics. -## Message envelopes (UTC by default) +## Message envelopes (local by default) -Inbound messages are wrapped with a UTC timestamp (minute precision): +Inbound messages are wrapped with a timestamp (minute precision): ``` -[Provider ... 2026-01-05T21:26Z] message text +[Provider ... 2026-01-05 16:26 PST] message text ``` -This envelope timestamp is **UTC by default**, regardless of the host timezone. +This envelope timestamp is **host-local by default**, regardless of the provider timezone. You can override this behavior: @@ -26,7 +26,7 @@ You can override this behavior: { agents: { defaults: { - envelopeTimezone: "utc", // "utc" | "local" | "user" | IANA timezone + envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone envelopeTimestamp: "on", // "on" | "off" envelopeElapsed: "on" // "on" | "off" } @@ -34,6 +34,7 @@ You can override this behavior: } ``` +- `envelopeTimezone: "utc"` uses UTC. - `envelopeTimezone: "local"` uses the host timezone. - `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to host timezone). - Use an explicit IANA timezone (e.g., `"America/Chicago"`) for a fixed zone. @@ -42,10 +43,10 @@ You can override this behavior: ### Examples -**UTC (default):** +**Local (default):** ``` -[WhatsApp +1555 2026-01-18T05:19Z] hello +[WhatsApp +1555 2026-01-18 00:19 PST] hello ``` **User timezone:** diff --git a/src/auto-reply/envelope.test.ts b/src/auto-reply/envelope.test.ts index d811fbd2f..7860ecb49 100644 --- a/src/auto-reply/envelope.test.ts +++ b/src/auto-reply/envelope.test.ts @@ -18,6 +18,7 @@ describe("formatAgentEnvelope", () => { host: "mac-mini", ip: "10.0.0.5", timestamp: ts, + envelope: { timezone: "utc" }, body: "hello", }); @@ -26,7 +27,7 @@ describe("formatAgentEnvelope", () => { expect(body).toBe("[WebChat user1 mac-mini 10.0.0.5 2025-01-02T03:04Z] hello"); }); - it("formats timestamps in UTC regardless of local timezone", () => { + it("formats timestamps in local timezone by default", () => { const originalTz = process.env.TZ; process.env.TZ = "America/Los_Angeles"; @@ -39,10 +40,10 @@ describe("formatAgentEnvelope", () => { process.env.TZ = originalTz; - expect(body).toBe("[WebChat 2025-01-02T03:04Z] hello"); + expect(body).toMatch(/\[WebChat 2025-01-01 19:04 [^\]]+\] hello/); }); - it("formats timestamps in local timezone when configured", () => { + it("formats timestamps in UTC when configured", () => { const originalTz = process.env.TZ; process.env.TZ = "America/Los_Angeles"; @@ -50,13 +51,13 @@ describe("formatAgentEnvelope", () => { const body = formatAgentEnvelope({ channel: "WebChat", timestamp: ts, - envelope: { timezone: "local" }, + envelope: { timezone: "utc" }, body: "hello", }); process.env.TZ = originalTz; - expect(body).toMatch(/\[WebChat 2025-01-01 19:04 [^\]]+\] hello/); + expect(body).toBe("[WebChat 2025-01-02T03:04Z] hello"); }); it("formats timestamps in user timezone when configured", () => { diff --git a/src/auto-reply/envelope.ts b/src/auto-reply/envelope.ts index 513be2aa4..53622d5e5 100644 --- a/src/auto-reply/envelope.ts +++ b/src/auto-reply/envelope.ts @@ -16,7 +16,7 @@ export type AgentEnvelopeParams = { export type EnvelopeFormatOptions = { /** - * "utc" (default), "local", "user", or an explicit IANA timezone string. + * "local" (default), "utc", "user", or an explicit IANA timezone string. */ timezone?: string; /** @@ -59,7 +59,7 @@ function normalizeEnvelopeOptions(options?: EnvelopeFormatOptions): NormalizedEn const includeTimestamp = options?.includeTimestamp !== false; const includeElapsed = options?.includeElapsed !== false; return { - timezone: options?.timezone?.trim() || "utc", + timezone: options?.timezone?.trim() || "local", includeTimestamp, includeElapsed, userTimezone: options?.userTimezone, @@ -77,7 +77,7 @@ function resolveExplicitTimezone(value: string): string | undefined { function resolveEnvelopeTimezone(options: NormalizedEnvelopeOptions): ResolvedEnvelopeTimezone { const trimmed = options.timezone?.trim(); - if (!trimmed) return { mode: "utc" }; + if (!trimmed) return { mode: "local" }; const lowered = trimmed.toLowerCase(); if (lowered === "utc" || lowered === "gmt") return { mode: "utc" }; if (lowered === "local" || lowered === "host") return { mode: "local" };