Some checks failed
CI/CD / lint (pull_request) Successful in 1m16s
CI/CD / test (pull_request) Successful in 1m26s
CI/CD / typecheck (pull_request) Successful in 3m25s
CI/CD / smoke (pull_request) Failing after 1m57s
CI/CD / build (pull_request) Successful in 5m9s
CI/CD / publish (pull_request) Has been skipped
Models consuming projects have no idea what day it is. Three always-on layers from mcplocal plus one from mcpd: - initialize instructions and the begin_session briefing carry '[context] now: <date/time> — project: <name>' (frozen at session start by design); - a get_current_time virtual tool (ungated catalogs only, same pattern as read_prompts); - every tools/call result gets a trailing '[context] now: …' content item at the endpoint send site — the only chokepoint that also covers virtual tools, gate intercepts and failure results, and the layer that keeps a days-old chat session current; - mcpd agent/project chat system blocks append the same line each turn. Time renders as UTC plus an optional local timezone from MCPLOCAL_AMBIENT_TZ (IANA name; both services read it; invalid or unset means UTC only). ~15 tokens per stamp, minute precision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { describe, it, expect, afterEach } from 'vitest';
|
|
import {
|
|
AMBIENT_TZ_ENV,
|
|
ambientTz,
|
|
formatAmbientNow,
|
|
ambientContextLine,
|
|
formatCurrentTimeResult,
|
|
} from '@mcpctl/shared';
|
|
|
|
const SUMMER = new Date('2026-07-01T09:30:00Z'); // London = BST, +01:00
|
|
const WINTER = new Date('2026-01-15T09:30:00Z'); // London = GMT, +00:00
|
|
|
|
afterEach(() => {
|
|
delete process.env[AMBIENT_TZ_ENV];
|
|
});
|
|
|
|
describe('ambientTz', () => {
|
|
it('returns undefined when unset', () => {
|
|
delete process.env[AMBIENT_TZ_ENV];
|
|
expect(ambientTz()).toBeUndefined();
|
|
});
|
|
|
|
it('returns a valid IANA name', () => {
|
|
process.env[AMBIENT_TZ_ENV] = 'Europe/London';
|
|
expect(ambientTz()).toBe('Europe/London');
|
|
});
|
|
|
|
it('falls back to undefined on an invalid name without throwing', () => {
|
|
process.env[AMBIENT_TZ_ENV] = 'Not/AZone';
|
|
expect(ambientTz()).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('formatAmbientNow', () => {
|
|
it('renders UTC-only when no tz', () => {
|
|
expect(formatAmbientNow(SUMMER, undefined)).toBe('Wed 2026-07-01 09:30 UTC');
|
|
});
|
|
|
|
it('renders local + UTC in summer (BST +01:00)', () => {
|
|
expect(formatAmbientNow(SUMMER, 'Europe/London'))
|
|
.toBe('Wed 2026-07-01 10:30 +01:00 Europe/London (09:30 UTC)');
|
|
});
|
|
|
|
it('renders local + UTC in winter (GMT +00:00)', () => {
|
|
expect(formatAmbientNow(WINTER, 'Europe/London'))
|
|
.toBe('Thu 2026-01-15 09:30 +00:00 Europe/London (09:30 UTC)');
|
|
});
|
|
|
|
it('stays short (~15 tokens)', () => {
|
|
expect(formatAmbientNow(SUMMER, 'Europe/London').length).toBeLessThan(70);
|
|
});
|
|
});
|
|
|
|
describe('ambientContextLine', () => {
|
|
it('prefixes with [context] now:', () => {
|
|
delete process.env[AMBIENT_TZ_ENV];
|
|
expect(ambientContextLine(SUMMER)).toBe('[context] now: Wed 2026-07-01 09:30 UTC');
|
|
});
|
|
});
|
|
|
|
describe('formatCurrentTimeResult', () => {
|
|
it('includes UTC ISO and epoch, no local line when tz unset', () => {
|
|
delete process.env[AMBIENT_TZ_ENV];
|
|
const text = formatCurrentTimeResult(SUMMER);
|
|
expect(text).toContain('UTC: 2026-07-01T09:30:00.000Z');
|
|
expect(text).toContain(`Unix epoch seconds: ${Math.floor(SUMMER.getTime() / 1000)}`);
|
|
expect(text).not.toContain('Europe/London');
|
|
});
|
|
|
|
it('includes the local rendering when tz set', () => {
|
|
process.env[AMBIENT_TZ_ENV] = 'Europe/London';
|
|
const text = formatCurrentTimeResult(SUMMER);
|
|
expect(text).toContain('Europe/London: Wed 2026-07-01 10:30 +01:00 Europe/London (09:30 UTC)');
|
|
});
|
|
});
|