Skip to content

Commit d649548

Browse files
committed
fix(active-memory): bound recall cache clocks
1 parent 5adc681 commit d649548

2 files changed

Lines changed: 73 additions & 8 deletions

File tree

extensions/active-memory/index.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4130,6 +4130,50 @@ describe("active-memory plugin", () => {
41304130
expect(cached?.summary).toBe("memory 1");
41314131
});
41324132

4133+
it("drops cached active-memory results when the current clock is not a valid date timestamp", () => {
4134+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_700_000_000_000);
4135+
const cacheKey = testing.buildCacheKey({
4136+
agentId: "main",
4137+
sessionKey: "agent:main:invalid-clock-cache",
4138+
query: "cache invalid clock prompt",
4139+
});
4140+
testing.setCachedResult(
4141+
cacheKey,
4142+
{
4143+
status: "ok",
4144+
elapsedMs: 1,
4145+
rawReply: "memory",
4146+
summary: "memory",
4147+
},
4148+
15_000,
4149+
);
4150+
4151+
nowSpy.mockReturnValue(Number.NaN);
4152+
4153+
expect(testing.getCachedResult(cacheKey)).toBeUndefined();
4154+
});
4155+
4156+
it("does not cache active-memory results when the expiry timestamp would exceed the valid date range", () => {
4157+
vi.spyOn(Date, "now").mockReturnValue(8_640_000_000_000_000);
4158+
const cacheKey = testing.buildCacheKey({
4159+
agentId: "main",
4160+
sessionKey: "agent:main:overflow-cache",
4161+
query: "cache overflow prompt",
4162+
});
4163+
testing.setCachedResult(
4164+
cacheKey,
4165+
{
4166+
status: "ok",
4167+
elapsedMs: 1,
4168+
rawReply: "memory",
4169+
summary: "memory",
4170+
},
4171+
15_000,
4172+
);
4173+
4174+
expect(testing.getCachedResult(cacheKey)).toBeUndefined();
4175+
});
4176+
41334177
it("skips recall after consecutive timeouts when circuit breaker trips (#74054)", async () => {
41344178
const CONFIGURED_TIMEOUT_MS = 25;
41354179
testing.setMinimumTimeoutMsForTests(1);

extensions/active-memory/index.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
} from "openclaw/plugin-sdk/agent-runtime";
1414
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
1515
import { closeActiveMemorySearchManager } from "openclaw/plugin-sdk/memory-host-search";
16-
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
16+
import {
17+
asDateTimestampMs,
18+
parseStrictPositiveInteger,
19+
resolveExpiresAtMsFromDurationMs,
20+
} from "openclaw/plugin-sdk/number-runtime";
1721
import {
1822
resolveLivePluginConfigObject,
1923
resolvePluginConfigObject,
@@ -1360,27 +1364,40 @@ function getCachedResult(cacheKey: string): ActiveRecallResult | undefined {
13601364
if (!cached) {
13611365
return undefined;
13621366
}
1363-
if (cached.expiresAt <= Date.now()) {
1367+
const now = asDateTimestampMs(Date.now());
1368+
if (
1369+
now === undefined ||
1370+
asDateTimestampMs(cached.expiresAt) === undefined ||
1371+
cached.expiresAt <= now
1372+
) {
13641373
activeRecallCache.delete(cacheKey);
13651374
return undefined;
13661375
}
13671376
return cached.result;
13681377
}
13691378

13701379
function setCachedResult(cacheKey: string, result: ActiveRecallResult, ttlMs: number): void {
1371-
const now = Date.now();
1380+
const rawNow = Date.now();
1381+
const now = asDateTimestampMs(rawNow);
13721382
if (
13731383
activeRecallCache.size >= DEFAULT_MAX_CACHE_ENTRIES ||
1374-
now - lastActiveRecallCacheSweepAt >= CACHE_SWEEP_INTERVAL_MS
1384+
(now !== undefined && now - lastActiveRecallCacheSweepAt >= CACHE_SWEEP_INTERVAL_MS)
13751385
) {
13761386
sweepExpiredCacheEntries(now);
1377-
lastActiveRecallCacheSweepAt = now;
1387+
if (now !== undefined) {
1388+
lastActiveRecallCacheSweepAt = now;
1389+
}
1390+
}
1391+
const expiresAt = resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs: rawNow });
1392+
if (expiresAt === undefined) {
1393+
activeRecallCache.delete(cacheKey);
1394+
return;
13781395
}
13791396
if (activeRecallCache.has(cacheKey)) {
13801397
activeRecallCache.delete(cacheKey);
13811398
}
13821399
activeRecallCache.set(cacheKey, {
1383-
expiresAt: now + ttlMs,
1400+
expiresAt,
13841401
result,
13851402
});
13861403
while (activeRecallCache.size > DEFAULT_MAX_CACHE_ENTRIES) {
@@ -1392,9 +1409,13 @@ function setCachedResult(cacheKey: string, result: ActiveRecallResult, ttlMs: nu
13921409
}
13931410
}
13941411

1395-
function sweepExpiredCacheEntries(now = Date.now()): void {
1412+
function sweepExpiredCacheEntries(now = asDateTimestampMs(Date.now())): void {
1413+
if (now === undefined) {
1414+
activeRecallCache.clear();
1415+
return;
1416+
}
13961417
for (const [cacheKey, cached] of activeRecallCache.entries()) {
1397-
if (cached.expiresAt <= now) {
1418+
if (asDateTimestampMs(cached.expiresAt) === undefined || cached.expiresAt <= now) {
13981419
activeRecallCache.delete(cacheKey);
13991420
}
14001421
}

0 commit comments

Comments
 (0)