fix: default envelope timestamps to local

This commit is contained in:
Peter Steinberger
2026-01-22 04:09:57 +00:00
parent 2fc926ab1c
commit 30a8478e1a
5 changed files with 28 additions and 22 deletions

View File

@@ -13,6 +13,9 @@ Docs: https://docs.clawd.bot
- Signal: add typing indicators and DM read receipts via signal-cli. - Signal: add typing indicators and DM read receipts via signal-cli.
- MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. - 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 dont have to constantly convert.
### Fixes ### Fixes
- Config: avoid stack traces for invalid configs and log the config path. - Config: avoid stack traces for invalid configs and log the config path.
- Doctor: warn when gateway.mode is unset with configure/config guidance. - Doctor: warn when gateway.mode is unset with configure/config guidance.

View File

@@ -9,15 +9,15 @@ read_when:
Clawdbot standardizes timestamps so the model sees a **single reference time**. 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: 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: You can override this with:
@@ -25,7 +25,7 @@ You can override this with:
{ {
agents: { agents: {
defaults: { defaults: {
envelopeTimezone: "user", // "utc" | "local" | "user" | IANA timezone envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
envelopeTimestamp: "on", // "on" | "off" envelopeTimestamp: "on", // "on" | "off"
envelopeElapsed: "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). - `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to host timezone).
- Use an explicit IANA timezone (e.g., `"Europe/Vienna"`) for a fixed offset. - Use an explicit IANA timezone (e.g., `"Europe/Vienna"`) for a fixed offset.
- `envelopeTimestamp: "off"` removes absolute timestamps from envelope headers. - `envelopeTimestamp: "off"` removes absolute timestamps from envelope headers.
@@ -40,10 +41,10 @@ You can override this with:
### Examples ### 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:** **Fixed timezone:**

View File

@@ -7,18 +7,18 @@ read_when:
# Date & Time # 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. 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: You can override this behavior:
@@ -26,7 +26,7 @@ You can override this behavior:
{ {
agents: { agents: {
defaults: { defaults: {
envelopeTimezone: "utc", // "utc" | "local" | "user" | IANA timezone envelopeTimezone: "local", // "utc" | "local" | "user" | IANA timezone
envelopeTimestamp: "on", // "on" | "off" envelopeTimestamp: "on", // "on" | "off"
envelopeElapsed: "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: "local"` uses the host timezone.
- `envelopeTimezone: "user"` uses `agents.defaults.userTimezone` (falls back to 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. - Use an explicit IANA timezone (e.g., `"America/Chicago"`) for a fixed zone.
@@ -42,10 +43,10 @@ You can override this behavior:
### Examples ### Examples
**UTC (default):** **Local (default):**
``` ```
[WhatsApp +1555 2026-01-18T05:19Z] hello [WhatsApp +1555 2026-01-18 00:19 PST] hello
``` ```
**User timezone:** **User timezone:**

View File

@@ -18,6 +18,7 @@ describe("formatAgentEnvelope", () => {
host: "mac-mini", host: "mac-mini",
ip: "10.0.0.5", ip: "10.0.0.5",
timestamp: ts, timestamp: ts,
envelope: { timezone: "utc" },
body: "hello", body: "hello",
}); });
@@ -26,7 +27,7 @@ describe("formatAgentEnvelope", () => {
expect(body).toBe("[WebChat user1 mac-mini 10.0.0.5 2025-01-02T03:04Z] hello"); 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; const originalTz = process.env.TZ;
process.env.TZ = "America/Los_Angeles"; process.env.TZ = "America/Los_Angeles";
@@ -39,10 +40,10 @@ describe("formatAgentEnvelope", () => {
process.env.TZ = originalTz; 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; const originalTz = process.env.TZ;
process.env.TZ = "America/Los_Angeles"; process.env.TZ = "America/Los_Angeles";
@@ -50,13 +51,13 @@ describe("formatAgentEnvelope", () => {
const body = formatAgentEnvelope({ const body = formatAgentEnvelope({
channel: "WebChat", channel: "WebChat",
timestamp: ts, timestamp: ts,
envelope: { timezone: "local" }, envelope: { timezone: "utc" },
body: "hello", body: "hello",
}); });
process.env.TZ = originalTz; 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", () => { it("formats timestamps in user timezone when configured", () => {

View File

@@ -16,7 +16,7 @@ export type AgentEnvelopeParams = {
export type EnvelopeFormatOptions = { 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; timezone?: string;
/** /**
@@ -59,7 +59,7 @@ function normalizeEnvelopeOptions(options?: EnvelopeFormatOptions): NormalizedEn
const includeTimestamp = options?.includeTimestamp !== false; const includeTimestamp = options?.includeTimestamp !== false;
const includeElapsed = options?.includeElapsed !== false; const includeElapsed = options?.includeElapsed !== false;
return { return {
timezone: options?.timezone?.trim() || "utc", timezone: options?.timezone?.trim() || "local",
includeTimestamp, includeTimestamp,
includeElapsed, includeElapsed,
userTimezone: options?.userTimezone, userTimezone: options?.userTimezone,
@@ -77,7 +77,7 @@ function resolveExplicitTimezone(value: string): string | undefined {
function resolveEnvelopeTimezone(options: NormalizedEnvelopeOptions): ResolvedEnvelopeTimezone { function resolveEnvelopeTimezone(options: NormalizedEnvelopeOptions): ResolvedEnvelopeTimezone {
const trimmed = options.timezone?.trim(); const trimmed = options.timezone?.trim();
if (!trimmed) return { mode: "utc" }; if (!trimmed) return { mode: "local" };
const lowered = trimmed.toLowerCase(); const lowered = trimmed.toLowerCase();
if (lowered === "utc" || lowered === "gmt") return { mode: "utc" }; if (lowered === "utc" || lowered === "gmt") return { mode: "utc" };
if (lowered === "local" || lowered === "host") return { mode: "local" }; if (lowered === "local" || lowered === "host") return { mode: "local" };