@alfe.ai/openclaw-memory-cloud 0.0.37 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +37 -0
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.d.cts +2 -0
  4. package/dist/index.d.ts +2 -60
  5. package/dist/index.js +2 -396
  6. package/dist/plugin.cjs +2 -0
  7. package/dist/plugin.d.cts +59 -0
  8. package/dist/plugin.d.cts.map +1 -0
  9. package/dist/plugin.d.ts +59 -0
  10. package/dist/plugin.d.ts.map +1 -0
  11. package/dist/plugin.js +2 -0
  12. package/dist/plugin2.cjs +1107 -0
  13. package/dist/plugin2.d.cts +2 -0
  14. package/dist/plugin2.d.ts +2 -0
  15. package/dist/plugin2.js +1104 -0
  16. package/dist/plugin2.js.map +1 -0
  17. package/openclaw.plugin.json +6 -5
  18. package/package.json +27 -7
  19. package/.turbo/turbo-build.log +0 -4
  20. package/CHANGELOG.md +0 -309
  21. package/dist/auto-capture.d.ts +0 -45
  22. package/dist/auto-capture.d.ts.map +0 -1
  23. package/dist/auto-capture.js +0 -101
  24. package/dist/auto-capture.js.map +0 -1
  25. package/dist/auto-recall.d.ts +0 -22
  26. package/dist/auto-recall.d.ts.map +0 -1
  27. package/dist/auto-recall.js +0 -63
  28. package/dist/auto-recall.js.map +0 -1
  29. package/dist/formatter.d.ts +0 -7
  30. package/dist/formatter.d.ts.map +0 -1
  31. package/dist/formatter.js +0 -27
  32. package/dist/formatter.js.map +0 -1
  33. package/dist/index.d.ts.map +0 -1
  34. package/dist/index.js.map +0 -1
  35. package/dist/ingest-epoch.d.ts +0 -38
  36. package/dist/ingest-epoch.d.ts.map +0 -1
  37. package/dist/ingest-epoch.js +0 -66
  38. package/dist/ingest-epoch.js.map +0 -1
  39. package/dist/session-backfill.d.ts +0 -54
  40. package/dist/session-backfill.d.ts.map +0 -1
  41. package/dist/session-backfill.js +0 -192
  42. package/dist/session-backfill.js.map +0 -1
  43. package/dist/types.d.ts +0 -113
  44. package/dist/types.d.ts.map +0 -1
  45. package/dist/types.js +0 -2
  46. package/dist/types.js.map +0 -1
  47. package/src/__tests__/auto-capture.test.ts +0 -115
  48. package/src/__tests__/ingest-epoch.test.ts +0 -67
  49. package/src/__tests__/session-backfill.test.ts +0 -289
  50. package/src/auto-capture.ts +0 -108
  51. package/src/auto-recall.ts +0 -66
  52. package/src/formatter.ts +0 -30
  53. package/src/index.ts +0 -464
  54. package/src/ingest-epoch.ts +0 -78
  55. package/src/session-backfill.ts +0 -220
  56. package/src/types.ts +0 -93
  57. package/sst-env.d.ts +0 -10
  58. package/tsconfig.json +0 -20
@@ -1,192 +0,0 @@
1
- /**
2
- * Session backfill — one-time ingestion of pre-existing chat history into
3
- * cloud memory, run fire-and-forget at plugin startup (see index.ts).
4
- *
5
- * Reads the openclaw-chat plugin's on-disk session files directly:
6
- * ~/.alfe/sessions/chat/{sessionId}.json
7
- * The write side is packages/openclaw-chat/src/session-store.ts — that path
8
- * and file shape are a read contract; keep the two in sync. The chat plugin
9
- * is not a package dependency because its session-store functions aren't
10
- * part of its public entry and the plugin isn't guaranteed to be installed
11
- * alongside memory. Like the session store itself, this assumes one agent
12
- * per machine (`alfe remove` deletes ~/.alfe, so files never outlive the
13
- * agent they belong to).
14
- *
15
- * No-re-sync guarantees (in concert with services/memory):
16
- * 1. Runs only while the server-side `sessionsBackfillSyncedAt` flag is
17
- * unset; the flag survives integration uninstall/reinstall.
18
- * 2. Only messages with timestamp < cutoffMs are ingested — for agents that
19
- * already had memory installed, everything after `bootstrapSyncedAt` was
20
- * already captured live by AutoCapture.
21
- * 3. sessionKey = file sessionId and index = file array position, so the
22
- * ingest consumer's per-session `lastProcessedIndex` high-water mark makes
23
- * a crash-and-retry replay free (filtered server-side before any cost).
24
- *
25
- * Each session is sent as exactly ONE memoryIngest call (one SQS message,
26
- * windowed to the most recent messages that fit the caps below). Splitting a
27
- * session across multiple SQS messages would race the shared per-session
28
- * high-water mark on the standard (non-FIFO) ingest queue: a transiently
29
- * failed early chunk redelivered after a later chunk succeeds gets filtered
30
- * out and silently lost.
31
- */
32
- import { readdir, readFile } from "node:fs/promises";
33
- import { join } from "node:path";
34
- import { homedir } from "node:os";
35
- const DEFAULT_SESSIONS_DIR = join(homedir(), ".alfe", "sessions", "chat");
36
- // Server-side zod caps on /agent/memory/ingest (services/memory
37
- // api-agents/ingest/post.ts) — exceeding any of these is a deterministic 400,
38
- // which would put the startup retry loop into a permanent failure.
39
- const MAX_MESSAGES_PER_SESSION = 500;
40
- const MAX_SESSION_KEY_CHARS = 256;
41
- const MAX_MESSAGE_INDEX = 1_000_000;
42
- const MAX_CONTENT_CHARS = 100_000;
43
- /** One ingest call becomes one SQS SendMessage (256KB hard limit). */
44
- const MAX_BATCH_BYTES = 180_000;
45
- export async function runSessionsBackfill(client, config, logger, opts) {
46
- const sessionsDir = opts.sessionsDir ?? DEFAULT_SESSIONS_DIR;
47
- let fileNames;
48
- try {
49
- fileNames = (await readdir(sessionsDir)).filter((name) => name.endsWith(".json"));
50
- }
51
- catch (err) {
52
- if (err.code === "ENOENT") {
53
- // No sessions directory — nothing to backfill, ever.
54
- logger.debug("sessions backfill: no sessions directory, marking complete");
55
- try {
56
- await client.memoryBootstrapStatusMark("sessions");
57
- }
58
- catch { /* retry next start */ }
59
- }
60
- else {
61
- // Transient (EACCES/EMFILE/...) — don't forfeit the backfill.
62
- logger.warn("sessions backfill: sessions directory unreadable; will retry on next startup", { err: String(err) });
63
- }
64
- return;
65
- }
66
- const sessions = [];
67
- for (const name of fileNames) {
68
- const parsed = await parseSessionFile(join(sessionsDir, name), config.captureMaxChars, opts.cutoffMs);
69
- if (parsed)
70
- sessions.push(parsed);
71
- }
72
- // Most recent non-empty sessions first, capped. Filter BEFORE capping:
73
- // on a retrofit agent the newest sessions are often entirely post-install
74
- // (emptied by the cutoff) and must not occupy the cap slots.
75
- const eligible = sessions
76
- .filter((s) => s.messages.length > 0)
77
- .sort((a, b) => b.lastMessageMs - a.lastMessageMs)
78
- .slice(0, config.backfillMaxSessions);
79
- if (eligible.length === 0) {
80
- logger.debug("sessions backfill: no eligible sessions, marking complete");
81
- try {
82
- await client.memoryBootstrapStatusMark("sessions");
83
- }
84
- catch { /* retry next start */ }
85
- return;
86
- }
87
- logger.info("sessions backfill: ingesting sessions", {
88
- sessionCount: eligible.length,
89
- messageCount: eligible.reduce((n, s) => n + s.messages.length, 0),
90
- });
91
- for (const session of eligible) {
92
- const metadata = {
93
- ...(session.channel ? { channelId: session.channel } : {}),
94
- ...(session.userId ? { userId: session.userId } : {}),
95
- };
96
- try {
97
- await client.memoryIngest(session.sessionId, session.messages, metadata);
98
- }
99
- catch (err) {
100
- // Abort without marking — next startup replays, and the server-side
101
- // lastProcessedIndex filter discards already-processed sessions free.
102
- logger.warn("sessions backfill: ingest failed; will retry on next startup", {
103
- sessionId: session.sessionId,
104
- err: String(err),
105
- });
106
- return;
107
- }
108
- }
109
- try {
110
- await client.memoryBootstrapStatusMark("sessions");
111
- logger.info("sessions backfill: complete", { sessionCount: eligible.length });
112
- }
113
- catch (err) {
114
- logger.warn("sessions backfill: mark failed; will retry on next startup", { err: String(err) });
115
- }
116
- }
117
- /**
118
- * Tolerant parse of a single session file. Returns null for corrupt or
119
- * shape-mismatched files (skipped, never fatal). Message `index` is the
120
- * original array position in the file — stable because the store is
121
- * append-only — so retries resume idempotently server-side.
122
- */
123
- async function parseSessionFile(filePath, captureMaxChars, cutoffMs) {
124
- let raw;
125
- try {
126
- raw = JSON.parse(await readFile(filePath, "utf8"));
127
- }
128
- catch {
129
- return null;
130
- }
131
- if (typeof raw !== "object" || raw === null)
132
- return null;
133
- const data = raw;
134
- if (typeof data.sessionId !== "string" || data.sessionId.length === 0)
135
- return null;
136
- if (data.sessionId.length > MAX_SESSION_KEY_CHARS)
137
- return null;
138
- if (!Array.isArray(data.messages))
139
- return null;
140
- const maxContentChars = Math.min(captureMaxChars, MAX_CONTENT_CHARS);
141
- let lastMessageMs = 0;
142
- const filtered = [];
143
- for (const [index, entry] of data.messages.entries()) {
144
- if (index > MAX_MESSAGE_INDEX)
145
- break;
146
- if (typeof entry !== "object" || entry === null)
147
- continue;
148
- const msg = entry;
149
- if (msg.role !== "user" && msg.role !== "assistant")
150
- continue;
151
- if (typeof msg.content !== "string" || msg.content.trim().length === 0)
152
- continue;
153
- if (typeof msg.timestamp !== "number")
154
- continue;
155
- lastMessageMs = Math.max(lastMessageMs, msg.timestamp);
156
- if (msg.timestamp >= cutoffMs)
157
- continue;
158
- filtered.push({
159
- role: msg.role,
160
- content: msg.content.length > maxContentChars ? msg.content.slice(0, maxContentChars) : msg.content,
161
- index,
162
- timestamp: new Date(msg.timestamp).toISOString(),
163
- });
164
- }
165
- return {
166
- sessionId: data.sessionId,
167
- channel: typeof data.channel === "string" ? data.channel : undefined,
168
- userId: typeof data.userId === "string" ? data.userId : undefined,
169
- messages: recentWindow(filtered),
170
- lastMessageMs,
171
- };
172
- }
173
- /**
174
- * The most recent contiguous run of messages fitting both ingest caps
175
- * (message count and serialized bytes). Walks backwards from the newest
176
- * message and stops at the first one that would overflow the byte budget.
177
- */
178
- function recentWindow(messages) {
179
- const window = [];
180
- let bytes = 0;
181
- for (let i = messages.length - 1; i >= 0; i--) {
182
- if (window.length >= MAX_MESSAGES_PER_SESSION)
183
- break;
184
- const msgBytes = Buffer.byteLength(JSON.stringify(messages[i])) + 1;
185
- if (bytes + msgBytes > MAX_BATCH_BYTES)
186
- break;
187
- window.unshift(messages[i]);
188
- bytes += msgBytes;
189
- }
190
- return window;
191
- }
192
- //# sourceMappingURL=session-backfill.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-backfill.js","sourceRoot":"","sources":["../src/session-backfill.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts DELETED
@@ -1,113 +0,0 @@
1
- export interface MemoryCloudConfig {
2
- autoCapture: boolean;
3
- autoRecall: boolean;
4
- captureMaxChars: number;
5
- idleFlushSeconds: number;
6
- backfillSessions: boolean;
7
- backfillMaxSessions: number;
8
- }
9
- export interface MemoryMessage {
10
- role: string;
11
- content: string;
12
- index: number;
13
- timestamp?: string;
14
- }
15
- export interface SessionMetadata {
16
- channelId?: string;
17
- userId?: string;
18
- userName?: string;
19
- }
20
- export interface MemorySearchResult {
21
- facts: {
22
- subject: string;
23
- predicate: string;
24
- object: string;
25
- since: string;
26
- confidence: number;
27
- }[];
28
- memories: {
29
- id: string;
30
- text: string;
31
- topic: string;
32
- subtopic: string;
33
- tag: string;
34
- importance: number;
35
- timestamp: number;
36
- score: number;
37
- }[];
38
- }
39
- export interface MemoryContext {
40
- tier: number;
41
- facts: {
42
- subject: string;
43
- predicate: string;
44
- object: string;
45
- since: string;
46
- }[];
47
- memories: {
48
- text: string;
49
- topic: string;
50
- subtopic: string;
51
- score: number;
52
- }[];
53
- tokenEstimate: number;
54
- formatted: string;
55
- }
56
- /**
57
- * Interface for memory API calls. Satisfied by AgentApiClient so the plugin
58
- * doesn't need a hard dependency on the agent-api-client package.
59
- */
60
- export interface MemoryApi {
61
- memorySearch(query: string, opts?: {
62
- limit?: number;
63
- topic?: string;
64
- subtopic?: string;
65
- tag?: string;
66
- includeKnowledge?: boolean;
67
- }): Promise<MemorySearchResult>;
68
- memoryStore(text: string, opts?: {
69
- topic?: string;
70
- subtopic?: string;
71
- tag?: string;
72
- importance?: number;
73
- }): Promise<{
74
- memoryId: string;
75
- }>;
76
- memoryIngest(sessionKey: string, messages: MemoryMessage[], metadata?: SessionMetadata, ingestEpoch?: number): Promise<{
77
- queued: boolean;
78
- messageCount: number;
79
- }>;
80
- memoryLoadContext(tier?: number, topicHint?: string): Promise<{
81
- formatted: string;
82
- [key: string]: unknown;
83
- }>;
84
- memoryLookupEntity(subject: string): Promise<{
85
- subject: string;
86
- triples: {
87
- tripleId: string;
88
- predicate: string;
89
- object: string;
90
- validFrom: string;
91
- validTo?: string;
92
- confidence: number;
93
- }[];
94
- }>;
95
- memoryNavigate(): Promise<{
96
- topics: {
97
- name: string;
98
- tripleCount: number;
99
- subtopics: string[];
100
- }[];
101
- cursor: string | null;
102
- }>;
103
- memoryDelete(memoryId: string): Promise<{
104
- deleted: boolean;
105
- }>;
106
- memoryStats(): Promise<{
107
- vectorCount: number;
108
- tripleCount: number;
109
- storageEstimateBytes: number;
110
- lastIngestionAt?: string;
111
- }>;
112
- }
113
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { AutoCapture } from "../auto-capture.js";
3
- import type { MemoryApi, MemoryCloudConfig, MemoryMessage, SessionMetadata } from "../types.js";
4
-
5
- /**
6
- * Restart-drop regression (Defect 1).
7
- *
8
- * `AutoCapture.messageIndex` is process-local and resets to 0 on every daemon
9
- * restart, but the memory service's per-session `lastProcessedIndex` high-water
10
- * mark survives. Before the fix, a `index > lastProcessedIndex` filter dropped
11
- * every post-restart capture (`storedCount: 0`) until the local counter climbed
12
- * back past the old max. The fix: each flush carries a per-boot `ingestEpoch`,
13
- * and the server resets the mark when it sees a strictly-newer epoch.
14
- *
15
- * This drives two AutoCapture instances (a restart == a fresh instance with the
16
- * counter back at 0) against a fake server that applies the real epoch rule,
17
- * and asserts the post-restart captures survive.
18
- */
19
-
20
- const config: MemoryCloudConfig = {
21
- autoCapture: true,
22
- autoRecall: true,
23
- captureMaxChars: 500,
24
- // Huge idle window so the debounce timer never fires; we flush explicitly.
25
- idleFlushSeconds: 3600,
26
- backfillSessions: false,
27
- backfillMaxSessions: 0,
28
- };
29
-
30
- const logger = { debug: vi.fn(), warn: vi.fn() };
31
-
32
- /** Minimal in-memory replica of the server's per-session epoch-fenced watermark. */
33
- function createFakeServer() {
34
- const marks = new Map<string, { lastIndex: number; epoch?: number }>();
35
- const stored = new Map<string, MemoryMessage[]>();
36
-
37
- const client: Pick<MemoryApi, "memoryIngest"> = {
38
- async memoryIngest(sessionKey: string, messages: MemoryMessage[], _metadata?: SessionMetadata, ingestEpoch?: number) {
39
- const prev = marks.get(sessionKey);
40
- const storedEpoch = prev?.epoch;
41
- const storedIndex = prev?.lastIndex ?? -1;
42
-
43
- let lastIndex = storedIndex;
44
- let nextEpoch = storedEpoch;
45
- if (ingestEpoch !== undefined) {
46
- if (storedEpoch === undefined || ingestEpoch > storedEpoch) {
47
- lastIndex = -1; // new/newer boot → reset
48
- nextEpoch = ingestEpoch;
49
- } else if (ingestEpoch < storedEpoch) {
50
- return { queued: true, messageCount: 0 }; // stale boot → skip
51
- }
52
- }
53
-
54
- const survivors = messages.filter((m) => m.index > lastIndex);
55
- if (survivors.length > 0) {
56
- stored.set(sessionKey, [...(stored.get(sessionKey) ?? []), ...survivors]);
57
- marks.set(sessionKey, { lastIndex: Math.max(...survivors.map((m) => m.index)), epoch: nextEpoch });
58
- }
59
- return { queued: true, messageCount: survivors.length };
60
- },
61
- };
62
-
63
- return { client: client as MemoryApi, storedFor: (k: string) => stored.get(k) ?? [] };
64
- }
65
-
66
- const SESSION = "conv:web:idn_1";
67
-
68
- describe("AutoCapture restart-drop fix", () => {
69
- it("does NOT drop post-restart captures against a surviving high-water mark", async () => {
70
- const server = createFakeServer();
71
-
72
- // ── Boot 1: capture three messages (indices 0,1,2), advancing the mark ──
73
- const boot1 = new AutoCapture(server.client, config, logger, 1000);
74
- boot1.setSession(SESSION);
75
- boot1.trackMessage("user", "hello");
76
- boot1.trackMessage("assistant", "hi there");
77
- boot1.trackMessage("user", "how are you");
78
- await boot1.flush();
79
- boot1.destroy();
80
- expect(server.storedFor(SESSION)).toHaveLength(3);
81
-
82
- // ── Restart: a fresh instance, message counter back at 0, higher epoch ──
83
- const boot2 = new AutoCapture(server.client, config, logger, 2000);
84
- boot2.setSession(SESSION);
85
- boot2.trackMessage("user", "still here?");
86
- boot2.trackMessage("assistant", "yep");
87
- await boot2.flush();
88
- boot2.destroy();
89
-
90
- // The two post-restart messages must NOT be silently dropped.
91
- expect(server.storedFor(SESSION)).toHaveLength(5);
92
- });
93
-
94
- it("REGRESSION GUARD: without a newer epoch the same messages WOULD be dropped", async () => {
95
- const server = createFakeServer();
96
-
97
- const boot1 = new AutoCapture(server.client, config, logger, 1000);
98
- boot1.setSession(SESSION);
99
- boot1.trackMessage("user", "hello");
100
- boot1.trackMessage("assistant", "hi there");
101
- boot1.trackMessage("user", "how are you");
102
- await boot1.flush();
103
-
104
- // Simulate the OLD behaviour: a restart that reuses the same epoch (no
105
- // reset signal). The post-restart index-0.. messages fall under the stale
106
- // watermark and are dropped — exactly the bug the epoch fixes.
107
- const boot2 = new AutoCapture(server.client, config, logger, 1000);
108
- boot2.setSession(SESSION);
109
- boot2.trackMessage("user", "still here?");
110
- boot2.trackMessage("assistant", "yep");
111
- await boot2.flush();
112
-
113
- expect(server.storedFor(SESSION)).toHaveLength(3);
114
- });
115
- });
@@ -1,67 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { mkdtempSync, rmSync, readFileSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { resolveIngestEpoch } from "../ingest-epoch.js";
6
-
7
- // The client-side guard that keeps the server's stale-boot branch a rare edge
8
- // rather than a whole-process outage: the per-boot epoch must be STRICTLY
9
- // monotonic across restarts even if the wall clock steps backward. Otherwise a
10
- // backward clock makes every flush look older than the stored epoch and the
11
- // server silently acks (drops) all of them for the life of the process.
12
-
13
- describe("resolveIngestEpoch", () => {
14
- let dir: string;
15
- let epochFile: string;
16
-
17
- beforeEach(() => {
18
- dir = mkdtempSync(join(tmpdir(), "ingest-epoch-"));
19
- epochFile = join(dir, "memory", "ingest-epoch.json");
20
- });
21
- afterEach(() => rmSync(dir, { recursive: true, force: true }));
22
-
23
- it("first boot with no state file returns now and persists it", () => {
24
- const epoch = resolveIngestEpoch({ epochFile, now: 1000 });
25
- expect(epoch).toBe(1000);
26
- expect(JSON.parse(readFileSync(epochFile, "utf8"))).toEqual({ epoch: 1000 });
27
- });
28
-
29
- it("BACKWARD CLOCK across a restart still yields a strictly-greater epoch", () => {
30
- // Boot 1 at a high wall-clock time.
31
- const first = resolveIngestEpoch({ epochFile, now: 5_000_000 });
32
- expect(first).toBe(5_000_000);
33
-
34
- // Restart with the clock stepped BACKWARD (NTP correction / pre-sync boot).
35
- const second = resolveIngestEpoch({ epochFile, now: 4_000_000 });
36
-
37
- // Must be strictly greater than the stored epoch, NOT the (smaller) now —
38
- // so the server sees a newer boot and resets the watermark instead of
39
- // classifying every flush as stale and dropping it.
40
- expect(second).toBe(5_000_001);
41
- expect(second).toBeGreaterThan(first);
42
- expect(JSON.parse(readFileSync(epochFile, "utf8"))).toEqual({ epoch: 5_000_001 });
43
- });
44
-
45
- it("forward clock uses the real time (monotonicity doesn't peg it low)", () => {
46
- resolveIngestEpoch({ epochFile, now: 1000 });
47
- const next = resolveIngestEpoch({ epochFile, now: 9000 });
48
- expect(next).toBe(9000);
49
- });
50
-
51
- it("repeated restarts stay strictly increasing even under a stuck/rewound clock", () => {
52
- let last = 0;
53
- for (const now of [1000, 1000, 999, 500, 1000]) {
54
- const epoch = resolveIngestEpoch({ epochFile, now });
55
- expect(epoch).toBeGreaterThan(last);
56
- last = epoch;
57
- }
58
- });
59
-
60
- it("a corrupt state file is treated as no prior epoch (falls back to now)", () => {
61
- resolveIngestEpoch({ epochFile, now: 1000 });
62
- // Clobber with garbage.
63
- writeFileSync(epochFile, "{ not json");
64
- const epoch = resolveIngestEpoch({ epochFile, now: 7000 });
65
- expect(epoch).toBe(7000);
66
- });
67
- });