mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 20:41:33 +00:00
* fix(memory): close previous embedding provider before replacement * fix(memory): increase embedding worker close grace period for slow hosts * fix(memory): clear this.provider after close in resetProviderInitializationForRetry Co-authored-by: Sanjay Santhanam <notifications@github.com> * fix(memory): serialize embedding provider replacement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): unify provider transition lifecycle Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): join worker shutdown lifecycle Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): block worker restart during close Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain failed provider retirements Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain retirements on manager close Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): make worker close joinable Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): preserve sync before provider retirement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): separate worker exit from disposal errors Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): avoid shutdown admission gap Co-authored-by: yt2102 <yt2102@qq.com> * style(memory): format provider lifecycle fix Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): bound embedding worker termination Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): fail closed on fallback initialization errors Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retry primary after fallback creation failure Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain disconnected embedding workers until exit Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): isolate shared fallback transition failures Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): serialize scoped manager retirement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain failed global manager closes Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): serialize manager admission with teardown Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): serialize outer manager lifecycle Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): close request-scoped embedding providers Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retire qmd managers before replacement Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): support synchronous embedding cleanup Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): isolate manager lifecycle scopes Co-authored-by: yt2102 <yt2102@qq.com> * fix(cli): close request-scoped embedding providers Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): canonicalize manager lifecycle ownership Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retry primary after null fallback result Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): retain failed embedding provider closes Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): lease closable embedding providers Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): drain retained embedding providers Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): join lazy fallback teardown Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): drain embedding providers after HTTP close Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain failed qmd candidate cleanup Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): aggregate retained qmd teardown failures Co-authored-by: yt2102 <yt2102@qq.com> * refactor(memory): keep qmd lifecycle policy unchanged Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain failed worker construction clients Co-authored-by: yt2102 <yt2102@qq.com> * fix(gateway): scope local embedding retirement by provider Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain provider generations before retirement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain admitted operations before teardown Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): preserve provider identity through vector search Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): lease provider generation through embedding Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): lease provider through index publication Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): keep sync harness hooks optional Co-authored-by: yt2102 <yt2102@qq.com> * refactor(memory): own generations in sync lifecycle Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): preserve query runtime across retirement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): pin FTS-only sync generations Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retry failed worker construction cleanup Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain admitted searches before closing Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): close manager and gateway admission races Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): serialize qmd wrapper replacement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): serialize failed qmd retirement Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): satisfy qmd lifecycle type and deadcode gates Co-authored-by: yt2102 <yt2102@qq.com> * test(memory): type qmd lifecycle doubles Co-authored-by: yt2102 <yt2102@qq.com> * style(gateway): clarify created embedding provider Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain availability probes before close Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): preserve cleanup ownership without blocking fallback Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retire lancedb embedding providers Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): preserve generic provider cleanup receiver Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): close lancedb CLI embeddings Co-authored-by: yt2102 <yt2102@qq.com> * test(memory): normalize abort rejection reason Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): retain lancedb provider retirements Co-authored-by: yt2102 <yt2102@qq.com> * fix(memory): drain lancedb embedding uses Co-authored-by: yt2102 <yt2102@qq.com> --------- Co-authored-by: Sanjay Santhanam <notifications@github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { Command } from "commander";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawPluginApi } from "./api.js";
|
|
import type { Embeddings } from "./embeddings.js";
|
|
import type { MemoryDB } from "./lancedb-store.js";
|
|
import { registerMemoryCli } from "./memory-cli.js";
|
|
|
|
function createHarness(params?: { embedError?: unknown; closeError?: Error }) {
|
|
const registerCli = vi.fn();
|
|
const closeError = params?.closeError;
|
|
const close = closeError
|
|
? vi.fn(async () => {
|
|
throw closeError;
|
|
})
|
|
: vi.fn(async () => {});
|
|
const embed =
|
|
params && Object.hasOwn(params, "embedError")
|
|
? vi.fn(async () => {
|
|
throw params.embedError;
|
|
})
|
|
: vi.fn(async () => [0.1, 0.2]);
|
|
const embeddings: Embeddings = {
|
|
embed,
|
|
close,
|
|
};
|
|
const search = vi.fn(async () => []);
|
|
registerMemoryCli(
|
|
{ registerCli } as unknown as OpenClawPluginApi,
|
|
{ search } as unknown as MemoryDB,
|
|
embeddings,
|
|
() => "main",
|
|
undefined,
|
|
);
|
|
const registrar = registerCli.mock.calls[0]?.[0] as
|
|
| ((params: { program: Command }) => void)
|
|
| undefined;
|
|
if (!registrar) {
|
|
throw new Error("expected memory CLI registrar");
|
|
}
|
|
const program = new Command();
|
|
registrar({ program });
|
|
return { close, embed, program, search };
|
|
}
|
|
|
|
describe("memory-lancedb CLI embedding lifecycle", () => {
|
|
it("closes embeddings after search", async () => {
|
|
const harness = createHarness();
|
|
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
try {
|
|
await harness.program.parseAsync(["node", "openclaw", "ltm", "search", "hello"]);
|
|
} finally {
|
|
log.mockRestore();
|
|
}
|
|
|
|
expect(harness.embed).toHaveBeenCalledTimes(1);
|
|
expect(harness.search).toHaveBeenCalledTimes(1);
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("closes embeddings without masking search failure", async () => {
|
|
const harness = createHarness({
|
|
embedError: new Error("embedding failed"),
|
|
closeError: new Error("close failed"),
|
|
});
|
|
|
|
await expect(
|
|
harness.program.parseAsync(["node", "openclaw", "ltm", "search", "hello"]),
|
|
).rejects.toThrow("embedding failed");
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("preserves a falsy search rejection over cleanup failure", async () => {
|
|
const harness = createHarness({
|
|
embedError: null,
|
|
closeError: new Error("close failed"),
|
|
});
|
|
|
|
const rejection = await harness.program
|
|
.parseAsync(["node", "openclaw", "ltm", "search", "hello"])
|
|
.then(
|
|
() => "resolved",
|
|
(err: unknown) => err,
|
|
);
|
|
expect(rejection).toBeNull();
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|