mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-24 21:41:08 +02:00
fix(x): show chat timestamps in the user's local timezone (#779)
* fix(x): show chat timestamps in the user's local timezone The per-message "Current date and time" context was built with toISOString(), giving the model a UTC time frame with no zone. It then quoted email Date: headers (sent in +0000) in UTC — an email shown as 1:06 PM IST in the inbox was described as 7:36 AM in chat. Use local wall-clock via toLocaleString with an explicit timeZoneName and the IANA zone (matching what the legacy engine already did) so the model converts offset-bearing timestamps to local time. * fix(x): convert email timestamps to local time for the assistant The "now" fix gave the model a local time frame, but email dates still reached it as raw RFC 2822 Date: headers (marketing mail is sent in +0000), so it kept quoting them in UTC — a 1:06 PM IST email described as 7:36 AM in chat. Add formatEmailDateLocal() and apply it at the three model-facing serialization points: the read-view email tool, the thread-summary line, and the gmail-sync knowledge markdown (**Date:**). The structured date field on snapshots/messages is left raw, since the renderer parses it with new Date() and formats it client-side. * fix(x): generalize local-time formatting for model-facing timestamps Move formatEmailDateLocal out of sync_gmail into a shared formatTimestampForModel() util — email is just the first source of external timestamps; Slack/calendar serialization can now reuse the same one-line call instead of growing per-source formatters. Also add a system-prompt instruction to always express times in the user's local timezone, as a fallback for raw dates inside email bodies, web content, and third-party tool output that pre-conversion can't reach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f8f5c8614f
commit
15d484980d
7 changed files with 67 additions and 17 deletions
|
|
@ -3054,7 +3054,18 @@ function App() {
|
||||||
...(chatMaxModelCalls !== undefined ? { maxModelCalls: chatMaxModelCalls } : {}),
|
...(chatMaxModelCalls !== undefined ? { maxModelCalls: chatMaxModelCalls } : {}),
|
||||||
}
|
}
|
||||||
const userMessageContextFor = (middlePane: Awaited<ReturnType<typeof buildMiddlePaneContext>>) => ({
|
const userMessageContextFor = (middlePane: Awaited<ReturnType<typeof buildMiddlePaneContext>>) => ({
|
||||||
currentDateTime: new Date().toISOString(),
|
// Local wall-clock with explicit timezone, never toISOString: the model
|
||||||
|
// adopts this as its time frame, so a UTC "now" makes it quote email
|
||||||
|
// timestamps (which carry their own offsets) in UTC instead of local.
|
||||||
|
currentDateTime: `${new Date().toLocaleString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short',
|
||||||
|
})} (${Intl.DateTimeFormat().resolvedOptions().timeZone})`,
|
||||||
middlePane: middlePane ?? { kind: 'empty' as const },
|
middlePane: middlePane ?? { kind: 'empty' as const },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { classifyThread, getUserEmail, type EmailCategory } from './classify_thr
|
||||||
import { recordImportanceCorrection } from './email_importance_feedback.js';
|
import { recordImportanceCorrection } from './email_importance_feedback.js';
|
||||||
import { recordCategoryCorrection } from './email_category_feedback.js';
|
import { recordCategoryCorrection } from './email_category_feedback.js';
|
||||||
import { notifyIfEnabled } from '../application/notification/notifier.js';
|
import { notifyIfEnabled } from '../application/notification/notifier.js';
|
||||||
|
import { formatTimestampForModel } from '@x/shared/dist/time.js';
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const SYNC_DIR = path.join(WorkDir, 'gmail_sync');
|
const SYNC_DIR = path.join(WorkDir, 'gmail_sync');
|
||||||
|
|
@ -1157,7 +1158,7 @@ async function parseThreadSnapshot(
|
||||||
const earlier = visibleMessages.slice(0, -1);
|
const earlier = visibleMessages.slice(0, -1);
|
||||||
const earlierSummary = earlier
|
const earlierSummary = earlier
|
||||||
.map((msg) => {
|
.map((msg) => {
|
||||||
const date = msg.date ? ` (${msg.date})` : '';
|
const date = msg.date ? ` (${formatTimestampForModel(msg.date)})` : '';
|
||||||
const body = msg.body.replace(/\s+/g, ' ').slice(0, 500).trim();
|
const body = msg.body.replace(/\s+/g, ' ').slice(0, 500).trim();
|
||||||
return `${msg.from}${date}: ${body}`;
|
return `${msg.from}${date}: ${body}`;
|
||||||
})
|
})
|
||||||
|
|
@ -1330,7 +1331,7 @@ async function processThread(auth: OAuth2Client, threadId: string, syncDir: stri
|
||||||
const date = headers.find(h => h.name === 'Date')?.value || 'Unknown';
|
const date = headers.find(h => h.name === 'Date')?.value || 'Unknown';
|
||||||
|
|
||||||
mdContent += `### From: ${from}\n`;
|
mdContent += `### From: ${from}\n`;
|
||||||
mdContent += `**Date:** ${date}\n\n`;
|
mdContent += `**Date:** ${formatTimestampForModel(date)}\n\n`;
|
||||||
|
|
||||||
if (msg.payload) {
|
if (msg.payload) {
|
||||||
const body = getBody(msg.payload);
|
const body = getBody(msg.payload);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ exports[`composeSystemInstructions golden bytes > agent notes 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ exports[`composeSystemInstructions golden bytes > base: no modes active 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ exports[`composeSystemInstructions golden bytes > coach mode 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ exports[`composeSystemInstructions golden bytes > code mode claude with cwd 1`]
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ exports[`composeSystemInstructions golden bytes > code mode codex without cwd 1`
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ exports[`composeSystemInstructions golden bytes > everything on 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -285,7 +285,7 @@ exports[`composeSystemInstructions golden bytes > search enabled 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -305,7 +305,7 @@ exports[`composeSystemInstructions golden bytes > video mode 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -346,7 +346,7 @@ exports[`composeSystemInstructions golden bytes > voice input 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -368,7 +368,7 @@ exports[`composeSystemInstructions golden bytes > voice output full 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -430,7 +430,7 @@ exports[`composeSystemInstructions golden bytes > voice output summary 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
@ -495,7 +495,7 @@ exports[`composeSystemInstructions golden bytes > work directory 1`] = `
|
||||||
# Hidden User Context
|
# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const PROMPT_CAPABILITIES = [
|
||||||
const USER_CONTEXT_SYSTEM_INSTRUCTIONS = `# Hidden User Context
|
const USER_CONTEXT_SYSTEM_INSTRUCTIONS = `# Hidden User Context
|
||||||
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
User messages may include a hidden "# User Context" section before "# User Message". Treat it as runtime metadata captured when that specific user message was sent. The actual user-authored text starts under "# User Message".
|
||||||
|
|
||||||
Use "Current date and time" for temporal reasoning.
|
Use "Current date and time" for temporal reasoning; it reflects the user's local timezone. Always express dates and times in that local timezone: timestamps inside emails, web content, or tool output may carry other offsets (often UTC) — convert those to local time before repeating them.
|
||||||
|
|
||||||
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
If Middle pane context is present, it reflects what the user had open at the time of that specific message and overrides earlier middle-pane references. If the conversation history references a different note or browser page, the user had since closed or navigated away from it. Do not treat earlier context as current.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { WorkDir } from "../../../config/config.js";
|
||||||
import { RowboatAppManifestSchema } from "@x/shared/dist/rowboat-app.js";
|
import { RowboatAppManifestSchema } from "@x/shared/dist/rowboat-app.js";
|
||||||
import { listApps } from "../../../apps/indexer.js";
|
import { listApps } from "../../../apps/indexer.js";
|
||||||
import { listImportantThreads, searchThreads } from "../../../knowledge/sync_gmail.js";
|
import { listImportantThreads, searchThreads } from "../../../knowledge/sync_gmail.js";
|
||||||
|
import { formatTimestampForModel } from "@x/shared/dist/time.js";
|
||||||
import { listTasks as listBackgroundTasks } from "../../../background-tasks/fileops.js";
|
import { listTasks as listBackgroundTasks } from "../../../background-tasks/fileops.js";
|
||||||
import type { ISessions } from "../../sessions/api.js";
|
import type { ISessions } from "../../sessions/api.js";
|
||||||
import { BuiltinToolsSchema } from "../types.js";
|
import { BuiltinToolsSchema } from "../types.js";
|
||||||
|
|
@ -113,7 +114,9 @@ export const appNavigationTools: z.infer<typeof BuiltinToolsSchema> = {
|
||||||
threadId: t.threadId,
|
threadId: t.threadId,
|
||||||
subject: t.subject ?? '(no subject)',
|
subject: t.subject ?? '(no subject)',
|
||||||
from: t.from ?? '',
|
from: t.from ?? '',
|
||||||
date: t.date ?? '',
|
// Local-timezone string for the model, not the raw
|
||||||
|
// header — otherwise it quotes email times in UTC.
|
||||||
|
date: formatTimestampForModel(t.date),
|
||||||
unread: t.unread ?? false,
|
unread: t.unread ?? false,
|
||||||
summary: t.summary ? t.summary.slice(0, 200) : undefined,
|
summary: t.summary ? t.summary.slice(0, 200) : undefined,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@ export * as notificationSettings from './notification-settings.js';
|
||||||
export * as turnLimits from './turn-limits.js';
|
export * as turnLimits from './turn-limits.js';
|
||||||
export * as codeSessions from './code-sessions.js';
|
export * as codeSessions from './code-sessions.js';
|
||||||
export * as channels from './channels.js';
|
export * as channels from './channels.js';
|
||||||
|
export * as time from './time.js';
|
||||||
export * as rowboatApp from './rowboat-app.js';
|
export * as rowboatApp from './rowboat-app.js';
|
||||||
export { PrefixLogger };
|
export { PrefixLogger };
|
||||||
|
|
|
||||||
34
apps/x/packages/shared/src/time.ts
Normal file
34
apps/x/packages/shared/src/time.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* Format a timestamp into the user's local timezone for any text shown to the
|
||||||
|
* LLM (tool output, knowledge markdown, thread summaries). External sources
|
||||||
|
* carry their own offsets — email `Date:` headers are typically `+0000`, Slack
|
||||||
|
* uses epoch seconds, calendar APIs return UTC ISO strings. Handed to the model
|
||||||
|
* raw, it quotes them verbatim instead of converting — e.g. a 1:06 PM IST email
|
||||||
|
* described as "7:36 AM" in chat. Call this at every model-facing serialization
|
||||||
|
* boundary; the model's tendency to quote timestamps verbatim then produces
|
||||||
|
* correct output. The weekday is included because models are unreliable at
|
||||||
|
* day-of-week arithmetic. Uses the system timezone of the process it runs in.
|
||||||
|
*
|
||||||
|
* NOT for structured `date` fields that code later parses with `new Date(...)`
|
||||||
|
* — those must stay raw parseable values.
|
||||||
|
*
|
||||||
|
* Accepts a raw date string, epoch milliseconds, or a Date. Unparseable
|
||||||
|
* strings (e.g. 'Unknown') are returned unchanged; null/undefined and invalid
|
||||||
|
* Date/number inputs return ''.
|
||||||
|
*/
|
||||||
|
export function formatTimestampForModel(raw: string | number | Date | undefined | null): string {
|
||||||
|
if (raw === undefined || raw === null || raw === '') return '';
|
||||||
|
const parsed = raw instanceof Date ? raw : new Date(raw);
|
||||||
|
if (Number.isNaN(parsed.getTime())) {
|
||||||
|
return typeof raw === 'string' ? raw : '';
|
||||||
|
}
|
||||||
|
return parsed.toLocaleString('en-US', {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short',
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue