






















@@ -148,6 +148,69 @@ function createCronHarness(
148148};
149149}
150150151+function mockStringMessages(mock: { mock: { calls: unknown[][] } }): string[] {
152+return mock.mock.calls.map((call) => String(call[0] ?? ""));
153+}
154+155+function expectLogContains(mock: { mock: { calls: unknown[][] } }, expected: string): void {
156+expect(mockStringMessages(mock).some((message) => message.includes(expected))).toBe(true);
157+}
158+159+function expectLogNotContains(mock: { mock: { calls: unknown[][] } }, expected: string): void {
160+expect(mockStringMessages(mock).every((message) => !message.includes(expected))).toBe(true);
161+}
162+163+function requireAddCall(harness: { addCalls: CronAddInput[] }, index: number): CronAddInput {
164+const call = harness.addCalls[index];
165+if (!call) {
166+throw new Error(`expected cron add call ${index}`);
167+}
168+return call;
169+}
170+171+function requireUpdateCall(
172+harness: { updateCalls: Array<{ id: string; patch: CronPatch }> },
173+index: number,
174+): { id: string; patch: CronPatch } {
175+const call = harness.updateCalls[index];
176+if (!call) {
177+throw new Error(`expected cron update call ${index}`);
178+}
179+return call;
180+}
181+182+function requireAgentTurnPayload(
183+payload: CronAddInput["payload"],
184+): Extract<CronAddInput["payload"], { kind: "agentTurn" }> {
185+if (payload.kind !== "agentTurn") {
186+throw new Error(`expected agentTurn payload, got ${payload.kind}`);
187+}
188+return payload;
189+}
190+191+function expectCronSchedule(
192+schedule: CronAddInput["schedule"] | CronPatch["schedule"] | undefined,
193+expr: string,
194+tz?: string,
195+): void {
196+expect(schedule?.kind).toBe("cron");
197+expect(schedule?.expr).toBe(expr);
198+expect(schedule?.tz).toBe(tz);
199+}
200+201+async function expectPathMissing(targetPath: string): Promise<void> {
202+try {
203+await fs.access(targetPath);
204+} catch (error) {
205+if (error && typeof error === "object" && "code" in error) {
206+expect(error.code).toBe("ENOENT");
207+return;
208+}
209+throw error;
210+}
211+throw new Error(`expected path to be missing: ${targetPath}`);
212+}
213+151214function getBeforeAgentReplyHandler(
152215onMock: ReturnType<typeof vi.fn>,
153216): (
@@ -412,13 +475,11 @@ describe("short-term dreaming config", () => {
412475},
413476},
414477});
415-expect(resolved).toMatchObject({
416-enabled: true,
417-minScore: constants.DEFAULT_DREAMING_MIN_SCORE,
418-minRecallCount: constants.DEFAULT_DREAMING_MIN_RECALL_COUNT,
419-minUniqueQueries: constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES,
420-recencyHalfLifeDays: constants.DEFAULT_DREAMING_RECENCY_HALF_LIFE_DAYS,
421-});
478+expect(resolved.enabled).toBe(true);
479+expect(resolved.minScore).toBe(constants.DEFAULT_DREAMING_MIN_SCORE);
480+expect(resolved.minRecallCount).toBe(constants.DEFAULT_DREAMING_MIN_RECALL_COUNT);
481+expect(resolved.minUniqueQueries).toBe(constants.DEFAULT_DREAMING_MIN_UNIQUE_QUERIES);
482+expect(resolved.recencyHalfLifeDays).toBe(constants.DEFAULT_DREAMING_RECENCY_HALF_LIFE_DAYS);
422483expect(resolved.maxAgeDays).toBe(30);
423484});
424485@@ -470,24 +531,15 @@ describe("short-term dreaming cron reconciliation", () => {
470531471532expect(result.status).toBe("added");
472533expect(harness.addCalls).toHaveLength(1);
473-expect(harness.addCalls[0]).toMatchObject({
474-name: constants.MANAGED_DREAMING_CRON_NAME,
475-sessionTarget: "isolated",
476-wakeMode: "now",
477-delivery: {
478-mode: "none",
479-},
480-payload: {
481-kind: "agentTurn",
482-message: constants.DREAMING_SYSTEM_EVENT_TEXT,
483-lightContext: true,
484-},
485-schedule: {
486-kind: "cron",
487-expr: "0 1 * * *",
488-tz: "UTC",
489-},
490-});
534+const addCall = requireAddCall(harness, 0);
535+expect(addCall.name).toBe(constants.MANAGED_DREAMING_CRON_NAME);
536+expect(addCall.sessionTarget).toBe("isolated");
537+expect(addCall.wakeMode).toBe("now");
538+expect(addCall.delivery?.mode).toBe("none");
539+const payload = requireAgentTurnPayload(addCall.payload);
540+expect(payload.message).toBe(constants.DREAMING_SYSTEM_EVENT_TEXT);
541+expect(payload.lightContext).toBe(true);
542+expectCronSchedule(addCall.schedule, "0 1 * * *", "UTC");
491543});
492544493545it("updates drifted managed jobs and prunes duplicates", async () => {
@@ -549,19 +601,14 @@ describe("short-term dreaming cron reconciliation", () => {
549601expect(result.removed).toBe(1);
550602expect(harness.removeCalls).toEqual(["job-duplicate"]);
551603expect(harness.updateCalls).toHaveLength(1);
552-expect(harness.updateCalls[0]).toMatchObject({
553-id: "job-primary",
554-patch: {
555-enabled: true,
556-sessionTarget: "isolated",
557-wakeMode: "now",
558-schedule: desired.schedule,
559-delivery: {
560-mode: "none",
561-},
562-payload: desired.payload,
563-},
564-});
604+const updateCall = requireUpdateCall(harness, 0);
605+expect(updateCall.id).toBe("job-primary");
606+expect(updateCall.patch.enabled).toBe(true);
607+expect(updateCall.patch.sessionTarget).toBe("isolated");
608+expect(updateCall.patch.wakeMode).toBe("now");
609+expect(updateCall.patch.schedule).toEqual(desired.schedule);
610+expect(updateCall.patch.delivery?.mode).toBe("none");
611+expect(updateCall.patch.payload).toEqual(desired.payload);
565612});
566613567614it("removes managed dreaming jobs when disabled", async () => {
@@ -772,9 +819,7 @@ describe("short-term dreaming cron reconciliation", () => {
772819});
773820774821expect(result).toEqual({ status: "disabled", removed: 0 });
775-expect(logger.warn).toHaveBeenCalledWith(
776-expect.stringContaining("failed to remove managed dreaming cron job job-managed"),
777-);
822+expectLogContains(logger.warn, "failed to remove managed dreaming cron job job-managed");
778823});
779824});
780825@@ -815,19 +860,10 @@ describe("gateway startup reconciliation", () => {
815860});
816861817862expect(harness.addCalls).toHaveLength(1);
818-expect(harness.addCalls[0]).toMatchObject({
819-schedule: {
820-kind: "cron",
821-expr: "15 4 * * *",
822-tz: "UTC",
823-},
824-delivery: {
825-mode: "none",
826-},
827-});
828-expect(logger.info).toHaveBeenCalledWith(
829-expect.stringContaining("created managed dreaming cron job"),
830-);
863+const addCall = requireAddCall(harness, 0);
864+expectCronSchedule(addCall.schedule, "15 4 * * *", "UTC");
865+expect(addCall.delivery?.mode).toBe("none");
866+expectLogContains(logger.info, "created managed dreaming cron job");
831867} finally {
832868clearInternalHooks();
833869}
@@ -892,11 +928,7 @@ describe("gateway startup reconciliation", () => {
892928);
893929894930expect(harness.addCalls).toHaveLength(1);
895-expect(harness.addCalls[0]?.schedule).toMatchObject({
896-kind: "cron",
897-expr: "30 6 * * *",
898-tz: "America/New_York",
899-});
931+expectCronSchedule(requireAddCall(harness, 0).schedule, "30 6 * * *", "America/New_York");
900932} finally {
901933clearInternalHooks();
902934}
@@ -978,11 +1010,11 @@ describe("gateway startup reconciliation", () => {
97810109791011expect(startupHarness.updateCalls).toHaveLength(0);
9801012expect(reloadedHarness.updateCalls).toHaveLength(1);
981-expect(reloadedHarness.updateCalls[0]?.patch.schedule).toMatchObject({
982-kind: "cron",
983-expr: "45 8 * * *",
984-tz: "America/Los_Angeles",
985-});
1013+expectCronSchedule(
1014+requireUpdateCall(reloadedHarness, 0).patch.schedule,
1015+"45 8 * * *",
1016+"America/Los_Angeles",
1017+);
9861018} finally {
9871019clearInternalHooks();
9881020}
@@ -1039,11 +1071,7 @@ describe("gateway startup reconciliation", () => {
10391071);
1040107210411073expect(harness.addCalls).toHaveLength(2);
1042-expect(harness.addCalls[1]?.schedule).toMatchObject({
1043-kind: "cron",
1044-expr: "0 2 * * *",
1045-tz: "UTC",
1046-});
1074+expectCronSchedule(requireAddCall(harness, 1).schedule, "0 2 * * *", "UTC");
10471075} finally {
10481076clearInternalHooks();
10491077}
@@ -1300,13 +1328,9 @@ describe("gateway startup reconciliation", () => {
13001328getCron: () => undefined,
13011329});
130213301303-expect(logger.warn).not.toHaveBeenCalledWith(
1304-expect.stringContaining("cron service unavailable"),
1305-);
1331+expectLogNotContains(logger.warn, "cron service unavailable");
13061332// The startup-path log should be demoted to debug instead.
1307-expect(logger.debug).toHaveBeenCalledWith(
1308-expect.stringContaining("cron service not yet available at gateway_start"),
1309-);
1333+expectLogContains(logger.debug, "cron service not yet available at gateway_start");
13101334} finally {
13111335clearInternalHooks();
13121336}
@@ -1355,7 +1379,7 @@ describe("gateway startup reconciliation", () => {
13551379{ trigger: "heartbeat", workspaceDir: ".", sessionKey: "agent:main:main:heartbeat" },
13561380);
135713811358-expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("cron service unavailable"));
1382+expectLogContains(logger.warn, "cron service unavailable");
13591383} finally {
13601384clearInternalHooks();
13611385}
@@ -1398,32 +1422,23 @@ describe("gateway startup reconciliation", () => {
13981422});
1399142314001424expect(harness.addCalls).toHaveLength(0);
1401-expect(logger.debug).toHaveBeenCalledWith(
1402-expect.stringContaining("cron service not yet available at gateway_start"),
1403-);
1425+expectLogContains(logger.debug, "cron service not yet available at gateway_start");
1404142614051427await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS);
14061428expect(harness.addCalls).toHaveLength(0);
1407-expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("cron service unavailable"));
1429+expectLogContains(logger.warn, "cron service unavailable");
1408143014091431cronAvailable = true;
14101432await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS);
1411143314121434expect(harness.addCalls).toHaveLength(1);
1413-expect(harness.addCalls[0]).toMatchObject({
1414-name: "Memory Dreaming Promotion",
1415-schedule: {
1416-kind: "cron",
1417-expr: "15 4 * * *",
1418-tz: "UTC",
1419-},
1420-sessionTarget: "isolated",
1421-payload: {
1422-kind: "agentTurn",
1423-message: constants.DREAMING_SYSTEM_EVENT_TEXT,
1424-lightContext: true,
1425-},
1426-});
1435+const addCall = requireAddCall(harness, 0);
1436+expect(addCall.name).toBe("Memory Dreaming Promotion");
1437+expectCronSchedule(addCall.schedule, "15 4 * * *", "UTC");
1438+expect(addCall.sessionTarget).toBe("isolated");
1439+const payload = requireAgentTurnPayload(addCall.payload);
1440+expect(payload.message).toBe(constants.DREAMING_SYSTEM_EVENT_TEXT);
1441+expect(payload.lightContext).toBe(true);
14271442} finally {
14281443vi.useRealTimers();
14291444clearInternalHooks();
@@ -1486,9 +1501,7 @@ describe("gateway startup reconciliation", () => {
14861501await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS);
14871502await vi.advanceTimersByTimeAsync(constants.STARTUP_CRON_RETRY_DELAY_MS);
148815031489-expect(logger.error).toHaveBeenCalledWith(
1490-expect.stringContaining("deferred dreaming cron retry failed"),
1491-);
1504+expectLogContains(logger.error, "deferred dreaming cron retry failed");
14921505expect(harness.listCalls).toBe(1);
14931506expect(harness.addCalls).toHaveLength(0);
14941507} finally {
@@ -2146,9 +2159,7 @@ describe("short-term dreaming trigger", () => {
21462159const dreamsText = await fs.readFile(path.join(workspaceDir, "DREAMS.md"), "utf-8");
21472160expect(dreamsText).toContain("A diary entry.");
21482161});
2149-expect(subagent.run.mock.calls[0]?.[0]).toMatchObject({
2150-model: "anthropic/claude-sonnet-4-6",
2151-});
2162+expect(subagent.run.mock.calls[0]?.[0]?.model).toBe("anthropic/claude-sonnet-4-6");
21522163});
2153216421542165it("skips dreaming promotion cleanly when limit is zero", async () => {
@@ -2179,9 +2190,7 @@ describe("short-term dreaming trigger", () => {
21792190expect(logger.info).toHaveBeenCalledWith(
21802191"memory-core: dreaming promotion skipped because limit=0.",
21812192);
2182-await expect(fs.access(path.join(workspaceDir, "MEMORY.md"))).rejects.toMatchObject({
2183-code: "ENOENT",
2184-});
2193+await expectPathMissing(path.join(workspaceDir, "MEMORY.md"));
21852194});
2186219521872196it("repairs recall artifacts before dreaming promotion runs", async () => {
@@ -2242,9 +2251,7 @@ describe("short-term dreaming trigger", () => {
22422251});
2243225222442253expect(result?.handled).toBe(true);
2245-expect(logger.info).toHaveBeenCalledWith(
2246-expect.stringContaining("normalized recall artifacts before dreaming"),
2247-);
2254+expectLogContains(logger.info, "normalized recall artifacts before dreaming");
22482255const repaired = JSON.parse(await fs.readFile(storePath, "utf-8")) as {
22492256entries: Record<
22502257string,
@@ -2259,9 +2266,10 @@ describe("short-term dreaming trigger", () => {
22592266"2026-04-01",
22602267"2026-04-03",
22612268]);
2262-expect(repaired.entries["memory:memory/2026-04-03.md:1:2"]?.conceptTags).toEqual(
2263-expect.arrayContaining(["glacier", "router", "failover"]),
2264-);
2269+const conceptTags = repaired.entries["memory:memory/2026-04-03.md:1:2"]?.conceptTags ?? [];
2270+expect(conceptTags).toContain("failover");
2271+expect(conceptTags).toContain("glacier");
2272+expect(conceptTags).toContain("router");
22652273});
2266227422672275it("emits detailed run logs when verboseLogging is enabled", async () => {
@@ -2302,15 +2310,9 @@ describe("short-term dreaming trigger", () => {
23022310});
2303231123042312expect(result?.handled).toBe(true);
2305-expect(logger.info).toHaveBeenCalledWith(
2306-expect.stringContaining("memory-core: dreaming verbose enabled"),
2307-);
2308-expect(logger.info).toHaveBeenCalledWith(
2309-expect.stringContaining("memory-core: dreaming candidate details"),
2310-);
2311-expect(logger.info).toHaveBeenCalledWith(
2312-expect.stringContaining("memory-core: dreaming applied details"),
2313-);
2313+expectLogContains(logger.info, "memory-core: dreaming verbose enabled");
2314+expectLogContains(logger.info, "memory-core: dreaming candidate details");
2315+expectLogContains(logger.info, "memory-core: dreaming applied details");
23142316});
2315231723162318it("fans out one dreaming run across configured agent workspaces", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。