惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
MyScale Blog
MyScale Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
Last Week in AI
Last Week in AI
罗磊的独立博客
G
Google Developers Blog
Y
Y Combinator Blog
博客园 - 【当耐特】
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
J
Java Code Geeks
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
美团技术团队
宝玉的分享
宝玉的分享
Jina AI
Jina AI
小众软件
小众软件
T
Tailwind CSS Blog
A
About on SuperTechFans

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(memory-core): cap detached dream narratives (#73287) ...
KeWang0622 · 2026-04-28 · via Recent Commits to openclaw:main

@@ -21,6 +21,7 @@ import {

2121

formatBackfillDiaryDate,

2222

generateAndAppendDreamNarrative,

2323

removeBackfillDiaryEntries,

24+

runDetachedDreamNarrative,

2425

type NarrativePhaseData,

2526

writeBackfillDiaryEntries,

2627

} from "./dreaming-narrative.js";

@@ -1003,3 +1004,117 @@ describe("generateAndAppendDreamNarrative", () => {

10031004

expect(subagent.deleteSession.mock.calls[1]?.[0]?.sessionKey).toBe(secondSessionKey);

10041005

});

10051006

});

1007+1008+

describe("runDetachedDreamNarrative", () => {

1009+

type Deferred<T> = { promise: Promise<T>; resolve: (v: T) => void };

1010+

function deferred<T>(): Deferred<T> {

1011+

let resolve!: (v: T) => void;

1012+

const promise = new Promise<T>((r) => {

1013+

resolve = r;

1014+

});

1015+

return { promise, resolve };

1016+

}

1017+1018+

function createBlockingSubagent() {

1019+

const runDeferreds: Array<Deferred<{ runId: string }>> = [];

1020+

const subagent = {

1021+

run: vi.fn(() => {

1022+

const d = deferred<{ runId: string }>();

1023+

runDeferreds.push(d);

1024+

return d.promise;

1025+

}),

1026+

// Resolve the rest of the pipeline as a no-op so a single resolve()

1027+

// on a deferred unblocks the slot for the queued task.

1028+

waitForRun: vi.fn().mockResolvedValue({ status: "timeout" }),

1029+

getSessionMessages: vi.fn().mockResolvedValue({ messages: [] }),

1030+

deleteSession: vi.fn().mockResolvedValue(undefined),

1031+

};

1032+

return { subagent, runDeferreds };

1033+

}

1034+1035+

function createMockLogger() {

1036+

return { info: vi.fn(), warn: vi.fn(), error: vi.fn() };

1037+

}

1038+1039+

async function drainMicrotasks(rounds = 30): Promise<void> {

1040+

for (let i = 0; i < rounds; i += 1) {

1041+

await Promise.resolve();

1042+

}

1043+

}

1044+1045+

it("caps the number of in-flight detached narratives at 3", async () => {

1046+

const { subagent, runDeferreds } = createBlockingSubagent();

1047+

const workspaceDir = await createTempWorkspace("openclaw-dreaming-detach-");

1048+

const logger = createMockLogger();

1049+1050+

for (let i = 0; i < 5; i += 1) {

1051+

runDetachedDreamNarrative({

1052+

subagent,

1053+

workspaceDir,

1054+

data: { phase: "light", snippets: [`fragment-${i}`] },

1055+

nowMs: Date.parse("2026-04-28T03:00:00Z"),

1056+

logger,

1057+

});

1058+

}

1059+1060+

await drainMicrotasks();

1061+1062+

// Only the first 3 should have reached subagent.run; the rest are queued.

1063+

expect(subagent.run).toHaveBeenCalledTimes(3);

1064+1065+

// Drain the rest so module-level concurrency state does not leak into

1066+

// subsequent tests. The mock subagent creates a new deferred every time

1067+

// queued tasks acquire a slot, so loop until no new deferreds appear.

1068+

for (let iter = 0; iter < 10; iter += 1) {

1069+

const before = runDeferreds.length;

1070+

for (const d of runDeferreds) {

1071+

d.resolve({ runId: "drain" });

1072+

}

1073+

if (before >= 5) {

1074+

break;

1075+

}

1076+

await vi.waitFor(() => {

1077+

expect(runDeferreds.length).toBeGreaterThan(before);

1078+

});

1079+

}

1080+

for (const d of runDeferreds) {

1081+

d.resolve({ runId: "drain" });

1082+

}

1083+

await vi.waitFor(() => {

1084+

expect(subagent.deleteSession).toHaveBeenCalledTimes(5);

1085+

});

1086+

expect(subagent.run).toHaveBeenCalledTimes(5);

1087+

expect(subagent.waitForRun).toHaveBeenCalledTimes(5);

1088+

});

1089+1090+

it("swallows underlying narrative errors instead of leaving an unhandled rejection", async () => {

1091+

const error = new Error("boom");

1092+

const subagent = {

1093+

run: vi.fn().mockRejectedValue(error),

1094+

waitForRun: vi.fn().mockResolvedValue({ status: "ok" }),

1095+

getSessionMessages: vi.fn().mockResolvedValue({ messages: [] }),

1096+

deleteSession: vi.fn().mockResolvedValue(undefined),

1097+

};

1098+

const logger = createMockLogger();

1099+

const workspaceDir = await createTempWorkspace("openclaw-dreaming-detach-");

1100+

const unhandled = vi.fn();

1101+

process.on("unhandledRejection", unhandled);

1102+1103+

try {

1104+

runDetachedDreamNarrative({

1105+

subagent,

1106+

workspaceDir,

1107+

data: { phase: "light", snippets: ["fragment"] },

1108+

nowMs: Date.parse("2026-04-28T03:00:00Z"),

1109+

logger,

1110+

});

1111+1112+

await drainMicrotasks();

1113+1114+

expect(subagent.run).toHaveBeenCalledOnce();

1115+

expect(unhandled).not.toHaveBeenCalled();

1116+

} finally {

1117+

process.off("unhandledRejection", unhandled);

1118+

}

1119+

});

1120+

});