@mnemoverse/mcp-memory-server
Version:
Hosted AI agent memory over MCP that learns from outcomes — feedback re-ranks recall — with shared rooms for multi-agent work; one key or OAuth across Claude Code, Cursor, VS Code & ChatGPT, no infra to run
66 lines • 3.5 kB
JavaScript
/**
* The one place that decides what an offset-less timestamp MEANS.
*
* This module exists because the answer was written down twice and implemented
* once. Both `since` parameter descriptions and core's own schema say a naive
* ISO-8601 value is UTC; `Date.parse("2026-08-08T13:00:00")` says it is local
* time. The gap was closed for the future-watermark note in 0.8.1 (src/scope.ts)
* and left open in the renderer (src/render.ts), so the same wire value decided
* one thing on the way in and another on the way out — a convention two modules
* read differently is not a convention.
*
* It lives on its own rather than in scope.ts because render.ts must not depend
* on scope.ts: the dependency deliberately runs the other way (scope.ts takes
* `safeInline` as an injected parameter precisely to avoid importing the
* renderer), and names.ts is about printing names, not about reading clocks.
*/
/**
* Parse an ISO-8601 instant the way the SERVER does: an offset-less value is
* UTC, not local time. Returns epoch milliseconds, or `null` for anything that
* cannot be read as an instant.
*
* The mismatch this fixes is not cosmetic — it moved dates. West of UTC a
* perfectly sane watermark was declared to be in the future and every clause of
* the future-watermark note was false; east of UTC a genuinely future watermark
* was shifted into the past and the note stayed silent, missing the one case it
* exists for (review, 2026-08-08). On a rendered result line the same reading
* printed `2026-08-01T23:30:00` as `2026-08-02 06:30Z` in America/Los_Angeles:
* a memory dated to a day it was not written, with a `Z` asserting it was UTC.
*
* Date-only values ("2026-08-08") are already parsed as UTC by spec, so only
* date-TIME values without an offset need the Z.
*
* `value` is `unknown` because both call sites read it off the wire, where the
* response types are aspirational: `created_at` is typed as a string and can
* arrive as a number. A value that is not a string is not an ISO-8601 instant,
* so it is `null` here rather than a guess — `new Date(1754082281605)` is a
* perfectly good date for a field whose contract says it is text.
*/
/** An explicit UTC marker or numeric offset at the end of an ISO-8601 value. */
const OFFSET_RE = /(?:Z|[+-]\d{2}:?\d{2})$/i;
export function parseAsUtc(value) {
if (typeof value !== "string")
return null;
const s = value.trim();
const hasOffset = OFFSET_RE.test(s);
const isDateTime = /\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(s);
const t = Date.parse(isDateTime && !hasOffset ? `${s.replace(" ", "T")}Z` : s);
return Number.isNaN(t) ? null : t;
}
/**
* The instant `parseAsUtc` reads, as a string a structured consumer can trust:
* a value that states its own offset (`Z` or `+hh:mm`) is returned exactly as
* sent, so the common core value stays byte-identical; an offset-less value,
* which this package reads as UTC by contract, is re-emitted as the UTC
* ISO-8601 instant the text renders, because a consumer parsing the naive
* string by the ISO-8601 rule would read it as LOCAL time and land on a
* different instant than the text shows (review, 2026-09-23). `null` for
* anything `parseAsUtc` cannot read.
*/
export function utcInstant(value) {
const t = parseAsUtc(value);
if (t === null || typeof value !== "string")
return null;
return OFFSET_RE.test(value.trim()) ? value : new Date(t).toISOString();
}
//# sourceMappingURL=time.js.map