From b3db4758a3d27bb8f4cf43c8374c5d52c7454420 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 26 Aug 2026 14:08:08 -0700 Subject: [PATCH] test: consolidate fixture setup (#130382) * test(wizard): consolidate finalize fixtures * test(memory-lancedb): deduplicate table fixtures * test(openshell): consolidate manager fixtures * test(memory): consolidate promotion evidence fixtures * test(openshell): preserve remote seed fixture ownership --- .../src/short-term-promotion.test.ts | 588 +++++-------- extensions/memory-lancedb/index.test.ts | 485 +++-------- .../src/backend.exec-workdir.test.ts | 57 +- .../openshell/src/backend.remote-seed.test.ts | 33 +- .../openshell/src/openshell-core.test.ts | 93 +-- .../openshell/src/openshell.test-support.ts | 45 + src/wizard/setup.finalize.test.ts | 780 ++++++------------ 7 files changed, 650 insertions(+), 1431 deletions(-) create mode 100644 extensions/openshell/src/openshell.test-support.ts diff --git a/extensions/memory-core/src/short-term-promotion.test.ts b/extensions/memory-core/src/short-term-promotion.test.ts index 4d24e66f79ac..237e1109fd90 100644 --- a/extensions/memory-core/src/short-term-promotion.test.ts +++ b/extensions/memory-core/src/short-term-promotion.test.ts @@ -39,6 +39,7 @@ import { readLightStagedKeys, removeGroundedShortTermCandidates, repairShortTermPromotionArtifacts, + type ShortTermRecallEntry, } from "./short-term-promotion.js"; import { configureMemoryCoreDreamingStateForTests, @@ -60,6 +61,9 @@ type ApplyAllOptions = Omit< "workspaceDir" | "candidates" | "minScore" | "minRecallCount" | "minUniqueQueries" >; type PromotionCandidate = Awaited>[number]; +type GroundedCandidateFixture = Parameters< + typeof recordGroundedShortTermCandidates +>[0]["items"][number]; type PromotionCandidateFixture = Pick< PromotionCandidate, "key" | "path" | "startLine" | "endLine" | "source" | "snippet" @@ -134,6 +138,42 @@ function promotionCandidateFixture(params: PromotionCandidateFixture): Promotion }; } +function recallStoreEntryFixture( + params: Pick & Partial, +): ShortTermRecallEntry { + return { + startLine: 1, + endLine: 1, + source: "memory", + snippet: `${params.key} recall`, + recallCount: 2, + dailyCount: 0, + groundedCount: 0, + totalScore: 1.8, + maxScore: 0.95, + firstRecalledAt: "2026-04-01T00:00:00.000Z", + lastRecalledAt: "2026-04-04T00:00:00.000Z", + queryHashes: ["a", "b"], + recallDays: ["2026-04-04"], + conceptTags: [], + ...params, + }; +} + +function groundedCandidateFixture( + params: Pick & + Partial, +): GroundedCandidateFixture { + return { + startLine: 1, + endLine: 1, + score: 0.9, + signalCount: 1, + dayBucket: "2026-04-03", + ...params, + }; +} + describe("short-term promotion", () => { let fixtureRoot = ""; let caseId = 0; @@ -220,6 +260,50 @@ describe("short-term promotion", () => { return notePath; } + async function seedGatewayPromotionCandidate(workspaceDir: string) { + await writeDailyMemoryNote(workspaceDir, "2026-04-01", [ + "alpha", + "beta", + "gamma", + "delta", + "epsilon", + "zeta", + "eta", + "theta", + "iota", + "Gateway binds loopback and port 18789", + "Keep gateway on localhost only", + "Document healthcheck endpoint", + ]); + await recordMemoryRecalls(workspaceDir, "gateway host", [ + memoryRecallResult( + "memory/2026-04-01.md", + 10, + 12, + 0.92, + "Gateway binds loopback and port 18789", + ), + ]); + return await rankAllCandidates(workspaceDir); + } + + function recordRotateCredentialsRecall(workspaceDir: string): Promise { + return recordMemoryRecalls( + workspaceDir, + "rotate creds", + [ + memoryRecallResult( + "memory/2026-04-29.md", + 3, + 3, + 0.96, + "Rotate the staging Postgres credentials before next deploy.", + ), + ], + { nowMs: Date.parse("2026-04-29T10:00:00.000Z") }, + ); + } + function requireCandidateKey( candidate: { key?: string } | null | undefined, label: string, @@ -240,25 +324,20 @@ describe("short-term promotion", () => { return candidate.promotedAt; } - async function readRecallStoreEntries(workspaceDir: string): Promise< - Record< - string, - { - claimHash?: unknown; - firstRecalledAt?: unknown; - lastRecalledAt?: unknown; - dailyCount?: unknown; - recallCount?: unknown; - snippet?: unknown; - totalScore?: unknown; - } - > - > { + async function readRecallStoreEntries(workspaceDir: string) { return await testing .readRecallStore(workspaceDir, new Date().toISOString()) .then((store) => store.entries); } + async function clearPromotedAt(workspaceDir: string): Promise { + const store = await testing.readRecallStore(workspaceDir, new Date().toISOString()); + for (const entry of Object.values(store.entries)) { + delete entry.promotedAt; + } + await testing.writeRawRecallStore(workspaceDir, store); + } + function readEntrySnippet(entry: { snippet?: unknown }): string { return typeof entry.snippet === "string" ? entry.snippet : ""; } @@ -412,16 +491,11 @@ describe("short-term promotion", () => { workspaceDir, query: "__dreaming_grounded_backfill__", items: [ - { + groundedCandidateFixture({ path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, snippet: longSnippet, - score: 0.9, query: "__dreaming_grounded_backfill__:candidate", - signalCount: 1, - dayBucket: "2026-04-03", - }, + }), ], nowMs: Date.parse("2026-04-03T10:00:00.000Z"), }); @@ -448,14 +522,13 @@ describe("short-term promotion", () => { it("ignores dream report paths when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "dream recall", [ - { - path: "memory/dreaming/deep/2026-04-03.md", - source: "memory", - startLine: 1, - endLine: 1, - score: 0.9, - snippet: "Auto-generated dream report should not seed promotions.", - }, + memoryRecallResult( + "memory/dreaming/deep/2026-04-03.md", + 1, + 1, + 0.9, + "Auto-generated dream report should not seed promotions.", + ), ]); expect(await readRecallStoreEntries(workspaceDir)).toEqual({}); @@ -463,14 +536,13 @@ describe("short-term promotion", () => { it("ignores prefixed dream report paths when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "prefixed dream recall", [ - { - path: "../../vault/memory/dreaming/deep/2026-04-03.md", - source: "memory", - startLine: 1, - endLine: 1, - score: 0.9, - snippet: "External dream report should not seed promotions.", - }, + memoryRecallResult( + "../../vault/memory/dreaming/deep/2026-04-03.md", + 1, + 1, + 0.9, + "External dream report should not seed promotions.", + ), ]); expect(await readRecallStoreEntries(workspaceDir)).toEqual({}); @@ -478,15 +550,13 @@ describe("short-term promotion", () => { it("ignores contaminated dreaming snippets when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "action preference", [ - { - path: "memory/2026-04-03.md", - source: "memory", - startLine: 1, - endLine: 1, - score: 0.92, - snippet: - "Candidate: Default to action. confidence: 0.76 evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1 recalls: 3 status: staged", - }, + memoryRecallResult( + "memory/2026-04-03.md", + 1, + 1, + 0.92, + "Candidate: Default to action. confidence: 0.76 evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1 recalls: 3 status: staged", + ), ]); const store = await testing.readRecallStore(workspaceDir, new Date().toISOString()); @@ -496,20 +566,19 @@ describe("short-term promotion", () => { it("ignores bullet-prefixed dreaming snippets when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "action preference", [ - { - path: "memory/2026-04-03.md", - source: "memory", - startLine: 1, - endLine: 5, - score: 0.92, - snippet: [ + memoryRecallResult( + "memory/2026-04-03.md", + 1, + 5, + 0.92, + [ "- Candidate: Default to action.", " - confidence: 0.76", " - evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1", " - recalls: 3", " - status: staged", ].join("\n"), - }, + ), ]); const store = await testing.readRecallStore(workspaceDir, new Date().toISOString()); @@ -519,32 +588,27 @@ describe("short-term promotion", () => { it("ignores raw session and transcript snippets when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "session recap", [ - { - path: "memory/2026-06-18.md", - source: "memory", - startLine: 1, - endLine: 1, - score: 0.92, - snippet: - "Session: 2026-06-18 10:37:05 EDT; Session Key: agent:cody:discord:channel:1502199757592989836; Session ID: 6d52b6a2-a2e1-4839-a69a-a532b9090a6d; Source: discord", - }, - { - path: "memory/2026-06-18.md", - source: "memory", - startLine: 2, - endLine: 2, - score: 0.91, - snippet: "Conversation Summary: assistant: Traced all three. No changes made.", - }, - { - path: "memory/2026-06-18.md", - source: "memory", - startLine: 3, - endLine: 3, - score: 0.9, - snippet: - "user: Save important context from this session to the daily memory file. STRICT RULES: 1. The file MUST be named exactly memory/2026-06-18.md", - }, + memoryRecallResult( + "memory/2026-06-18.md", + 1, + 1, + 0.92, + "Session: 2026-06-18 10:37:05 EDT; Session Key: agent:cody:discord:channel:1502199757592989836; Session ID: 6d52b6a2-a2e1-4839-a69a-a532b9090a6d; Source: discord", + ), + memoryRecallResult( + "memory/2026-06-18.md", + 2, + 2, + 0.91, + "Conversation Summary: assistant: Traced all three. No changes made.", + ), + memoryRecallResult( + "memory/2026-06-18.md", + 3, + 3, + 0.9, + "user: Save important context from this session to the daily memory file. STRICT RULES: 1. The file MUST be named exactly memory/2026-06-18.md", + ), ]); const store = await testing.readRecallStore(workspaceDir, new Date().toISOString()); @@ -554,15 +618,13 @@ describe("short-term promotion", () => { it("ignores already-promoted score metadata snippets when recording short-term recalls", async (workspaceDir) => { await recordMemoryRecalls(workspaceDir, "promotion metadata", [ - { - path: "memory/2026-06-18.md", - source: "memory", - startLine: 1, - endLine: 1, - score: 0.94, - snippet: - "2026-06-13 09:20 America/New_York - Polycore PR #112 re-review... [score=0.837 recalls=0 avg=0.620 source=memory/2026-06-13.md:10-12]", - }, + memoryRecallResult( + "memory/2026-06-18.md", + 1, + 1, + 0.94, + "2026-06-13 09:20 America/New_York - Polycore PR #112 re-review... [score=0.837 recalls=0 avg=0.620 source=memory/2026-06-13.md:10-12]", + ), ]); const store = await testing.readRecallStore(workspaceDir, new Date().toISOString()); @@ -855,36 +917,24 @@ describe("short-term promotion", () => { workspaceDir, query: "__dreaming_grounded_backfill__", items: [ - { + groundedCandidateFixture({ path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, snippet: 'Always use "Happy Together" calendar for flights and reservations.', score: 0.92, query: "__dreaming_grounded_backfill__:lasting-update", - signalCount: 1, - dayBucket: "2026-04-03", - }, - { + }), + groundedCandidateFixture({ path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, snippet: 'Always use "Happy Together" calendar for flights and reservations.', score: 0.82, query: "__dreaming_grounded_backfill__:candidate", - signalCount: 1, - dayBucket: "2026-04-03", - }, - { + }), + groundedCandidateFixture({ path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, snippet: 'Always use "Happy Together" calendar for flights and reservations.', score: 0.86, query: "__dreaming_grounded_backfill__:durable-fact", - signalCount: 1, - dayBucket: "2026-04-03", - }, + }), ], dedupeByQueryPerDay: true, nowMs: Date.parse("2026-04-03T10:00:00.000Z"), @@ -921,17 +971,14 @@ describe("short-term promotion", () => { workspaceDir, query: "__dreaming_grounded_backfill__", items: [ - { + groundedCandidateFixture({ path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, snippet: "Grounded only rule.", score: 0.92, query: "__dreaming_grounded_backfill__:lasting-update", signalCount: 2, - dayBucket: "2026-04-03", - }, - { + }), + groundedCandidateFixture({ path: "memory/2026-04-03.md", startLine: 2, endLine: 2, @@ -939,8 +986,7 @@ describe("short-term promotion", () => { score: 0.92, query: "__dreaming_grounded_backfill__:lasting-update", signalCount: 2, - dayBucket: "2026-04-03", - }, + }), ], dedupeByQueryPerDay: true, }); @@ -1250,11 +1296,7 @@ describe("short-term promotion", () => { expect(firstApply.appended).toBe(1); expect(firstApply.reconciledExisting).toBe(0); - const rawStore = await testing.readRecallStore(workspaceDir, new Date().toISOString()); - for (const entry of Object.values(rawStore.entries)) { - delete entry.promotedAt; - } - await testing.writeRawRecallStore(workspaceDir, rawStore); + await clearPromotedAt(workspaceDir); const secondApply = await applyAllCandidates(workspaceDir, ranked); expect(secondApply.applied).toBe(1); @@ -1292,11 +1334,7 @@ describe("short-term promotion", () => { expect(firstApply.applied).toBe(1); expect(firstApply.appended).toBe(1); - const rawStore = await testing.readRecallStore(workspaceDir, new Date().toISOString()); - for (const entry of Object.values(rawStore.entries)) { - delete entry.promotedAt; - } - await testing.writeRawRecallStore(workspaceDir, rawStore); + await clearPromotedAt(workspaceDir); const secondApply = await applyAllCandidates(workspaceDir, ranked); expect(secondApply.applied).toBe(1); @@ -1588,31 +1626,7 @@ describe("short-term promotion", () => { }); it("applies promotion candidates to MEMORY.md and marks them promoted", async (workspaceDir) => { - await writeDailyMemoryNote(workspaceDir, "2026-04-01", [ - "alpha", - "beta", - "gamma", - "delta", - "epsilon", - "zeta", - "eta", - "theta", - "iota", - "Gateway binds loopback and port 18789", - "Keep gateway on localhost only", - "Document healthcheck endpoint", - ]); - await recordMemoryRecalls(workspaceDir, "gateway host", [ - memoryRecallResult( - "memory/2026-04-01.md", - 10, - 12, - 0.92, - "Gateway binds loopback and port 18789", - ), - ]); - - const ranked = await rankAllCandidates(workspaceDir); + const ranked = await seedGatewayPromotionCandidate(workspaceDir); const applied = await applyAllCandidates(workspaceDir, ranked); expect(applied.applied).toBe(1); @@ -1728,31 +1742,7 @@ describe("short-term promotion", () => { }); it("does not re-append candidates that were promoted in a prior run", async (workspaceDir) => { - await writeDailyMemoryNote(workspaceDir, "2026-04-01", [ - "alpha", - "beta", - "gamma", - "delta", - "epsilon", - "zeta", - "eta", - "theta", - "iota", - "Gateway binds loopback and port 18789", - "Keep gateway on localhost only", - "Document healthcheck endpoint", - ]); - await recordMemoryRecalls(workspaceDir, "gateway host", [ - memoryRecallResult( - "memory/2026-04-01.md", - 10, - 12, - 0.92, - "Gateway binds loopback and port 18789", - ), - ]); - - const ranked = await rankAllCandidates(workspaceDir); + const ranked = await seedGatewayPromotionCandidate(workspaceDir); const first = await applyAllCandidates(workspaceDir, ranked); expect(first.applied).toBe(1); @@ -2213,31 +2203,16 @@ describe("short-term promotion", () => { it("audits and repairs dangling recall entries and their phase signals", async (workspaceDir) => { await writeDailyMemoryNote(workspaceDir, "2026-04-01", ["Live source note."]); await fs.mkdir(path.join(workspaceDir, "memory", "2026-04-02.md")); - const buildEntry = (key: string, entryPath: string) => ({ - key, - path: entryPath, - startLine: 1, - endLine: 1, - source: "memory" as const, - snippet: `${key} recall`, - recallCount: 2, - dailyCount: 0, - groundedCount: 0, - totalScore: 1.8, - maxScore: 0.95, - firstRecalledAt: "2026-04-01T00:00:00.000Z", - lastRecalledAt: "2026-04-04T00:00:00.000Z", - queryHashes: ["a", "b"], - recallDays: ["2026-04-04"], - conceptTags: [], - }); await testing.writeRawRecallStore(workspaceDir, { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", entries: { - live: buildEntry("live", "memory/2026-04-01.md"), - directory: buildEntry("directory", "memory/2026-04-02.md"), - missing: buildEntry("missing", "memory/2026-04-03.md"), + live: recallStoreEntryFixture({ key: "live", path: "memory/2026-04-01.md" }), + directory: recallStoreEntryFixture({ + key: "directory", + path: "memory/2026-04-02.md", + }), + missing: recallStoreEntryFixture({ key: "missing", path: "memory/2026-04-03.md" }), }, }); await testing.writeRawPhaseSignalStore(workspaceDir, { @@ -2279,24 +2254,11 @@ describe("short-term promotion", () => { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", entries: { - missing: { + missing: recallStoreEntryFixture({ key: "missing", path: "memory/2026-04-03.md", - startLine: 1, - endLine: 1, - source: "memory", snippet: "Missing source recall", - recallCount: 2, - dailyCount: 0, - groundedCount: 0, - totalScore: 1.8, - maxScore: 0.95, - firstRecalledAt: "2026-04-01T00:00:00.000Z", - lastRecalledAt: "2026-04-04T00:00:00.000Z", - queryHashes: ["a", "b"], - recallDays: ["2026-04-04"], - conceptTags: [], - }, + }), }, }); const nowIso = "2026-04-05T00:00:00.000Z"; @@ -2320,30 +2282,12 @@ describe("short-term promotion", () => { it("converges on retry when the recall write fails after phase cleanup", async (workspaceDir) => { await writeDailyMemoryNote(workspaceDir, "2026-04-01", ["Live source note."]); - const buildEntry = (key: string, entryPath: string) => ({ - key, - path: entryPath, - startLine: 1, - endLine: 1, - source: "memory" as const, - snippet: `${key} recall`, - recallCount: 2, - dailyCount: 0, - groundedCount: 0, - totalScore: 1.8, - maxScore: 0.95, - firstRecalledAt: "2026-04-01T00:00:00.000Z", - lastRecalledAt: "2026-04-04T00:00:00.000Z", - queryHashes: ["a", "b"], - recallDays: ["2026-04-04"], - conceptTags: [], - }); await testing.writeRawRecallStore(workspaceDir, { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", entries: { - live: buildEntry("live", "memory/2026-04-01.md"), - missing: buildEntry("missing", "memory/2026-04-03.md"), + live: recallStoreEntryFixture({ key: "live", path: "memory/2026-04-01.md" }), + missing: recallStoreEntryFixture({ key: "missing", path: "memory/2026-04-03.md" }), }, }); await testing.writeRawPhaseSignalStore(workspaceDir, { @@ -2396,24 +2340,11 @@ describe("short-term promotion", () => { }); it("fails closed without changing recall state when source inspection is denied", async (workspaceDir) => { - const entry = { + const entry = recallStoreEntryFixture({ key: "protected", path: "memory/2026-04-01.md", - startLine: 1, - endLine: 1, - source: "memory" as const, snippet: "Protected source recall", - recallCount: 2, - dailyCount: 0, - groundedCount: 0, - totalScore: 1.8, - maxScore: 0.95, - firstRecalledAt: "2026-04-01T00:00:00.000Z", - lastRecalledAt: "2026-04-04T00:00:00.000Z", - queryHashes: ["a", "b"], - recallDays: ["2026-04-04"], - conceptTags: [], - }; + }); await testing.writeRawRecallStore(workspaceDir, { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", @@ -2447,24 +2378,19 @@ describe("short-term promotion", () => { entries: Object.fromEntries( Array.from({ length: maxEntries + 3 }, (_, index) => [ `entry-${index}`, - { + recallStoreEntryFixture({ key: `entry-${index}`, path: "memory/2026-04-01.md", startLine: index + 1, endLine: index + 1, - source: "memory", snippet: `Oversized recall ${index} ${"x".repeat(maxSnippetChars + 100)}`, recallCount: 1, - dailyCount: 0, - groundedCount: 0, totalScore: index, maxScore: 0.75, - firstRecalledAt: "2026-04-01T00:00:00.000Z", lastRecalledAt: new Date(Date.parse("2026-04-01T00:00:00.000Z") + index).toISOString(), queryHashes: [`q-${index}`], recallDays: ["2026-04-01"], - conceptTags: [], - }, + }), ]), ), }); @@ -2493,24 +2419,18 @@ describe("short-term promotion", () => { version: 1, updatedAt: "2026-04-01T10:00:00.000Z", entries: { - [key]: { + [key]: recallStoreEntryFixture({ key, path: "memory/2026-04-01.md", - startLine: 1, - endLine: 1, - source: "memory", snippet: "The owner prefers green tea.", recallCount: 1, - dailyCount: 0, - groundedCount: 0, totalScore: 0.8, maxScore: 0.8, firstRecalledAt: "2026-04-01T10:00:00.000Z", lastRecalledAt: "2026-04-01T10:00:00.000Z", queryHashes: ["legacy"], recallDays: ["2026-04-01"], - conceptTags: [], - }, + }), }, }); const legacy = await testing.readRecallStore(workspaceDir, "2026-04-01T10:00:00.000Z"); @@ -2553,24 +2473,17 @@ describe("short-term promotion", () => { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", entries: { - contaminated: { + contaminated: recallStoreEntryFixture({ key: "contaminated", path: "memory/2026-04-01.md", - startLine: 1, - endLine: 1, - source: "memory", snippet: `Candidate: ${"x".repeat(maxSnippetChars + 100)} confidence: 9 evidence: memory/.dreams/session-corpus/2026-04-01.txt status: staged recalls: 1`, recallCount: 1, - dailyCount: 0, - groundedCount: 0, totalScore: 1, maxScore: 0.75, - firstRecalledAt: "2026-04-01T00:00:00.000Z", lastRecalledAt: "2026-04-01T00:00:00.000Z", queryHashes: ["q"], recallDays: ["2026-04-01"], - conceptTags: [], - }, + }), }, }); @@ -2598,22 +2511,11 @@ describe("short-term promotion", () => { version: 1, updatedAt: "2026-04-04T00:00:00.000Z", entries: { - good: { + good: recallStoreEntryFixture({ key: "good", path: "memory/2026-04-01.md", - startLine: 1, endLine: 2, - source: "memory", snippet, - recallCount: 2, - dailyCount: 0, - groundedCount: 0, - totalScore: 1.8, - maxScore: 0.95, - firstRecalledAt: "2026-04-01T00:00:00.000Z", - lastRecalledAt: "2026-04-04T00:00:00.000Z", - queryHashes: ["a", "b"], - recallDays: ["2026-04-04"], conceptTags: deriveConceptTags({ path: "memory/2026-04-01.md", snippet, @@ -2623,7 +2525,7 @@ describe("short-term promotion", () => { sessionKind: "unknown", observedAt: Date.parse("2026-04-04T00:00:00.000Z"), }, - }, + }), }, }; await testing.writeRawRecallStore(workspaceDir, raw); @@ -2769,50 +2671,51 @@ describe("short-term promotion", () => { describe("MEMORY.md budget compaction (#73691)", () => { async function applyBudgetCompactionPromotion(workspaceDir: string) { const nowMs = Date.parse("2026-04-29T10:00:00.000Z"); - await recordMemoryRecalls( - workspaceDir, - "rotate creds", - [ - memoryRecallResult( - "memory/2026-04-29.md", - 3, - 3, - 0.96, - "Rotate the staging Postgres credentials before next deploy.", - ), - ], - { nowMs }, - ); + await recordRotateCredentialsRecall(workspaceDir); return await applyAllCandidates(workspaceDir, await rankAllCandidates(workspaceDir), { nowMs, memoryFileMaxChars: 1_400, }); } - it("preserves mixed marker-backed user text during a real promotion write", async (workspaceDir) => { + async function seedBudgetMemory( + workspaceDir: string, + firstMarker: string, + secondMarker: string, + between: string[] = [], + ): Promise { await writeDailyMemoryNote(workspaceDir, "2026-04-29", [ "Notes", "", "Rotate the staging Postgres credentials before next deploy.", ]); - const memoryPath = path.join(workspaceDir, "MEMORY.md"); const filler = "x".repeat(600); - const seeded = [ - "# Long-Term Memory", - "", - "## Promoted From Short-Term Memory (2026-04-10)", - "", - `- ${filler}`, - "", + await fs.writeFile( + memoryPath, + [ + "# Long-Term Memory", + "", + "## Promoted From Short-Term Memory (2026-04-10)", + ``, + `- ${filler}`, + "", + ...between, + "## Promoted From Short-Term Memory (2026-04-20)", + ``, + `- ${filler}`, + "", + ].join("\n"), + "utf-8", + ); + return memoryPath; + } + + it("preserves mixed marker-backed user text during a real promotion write", async (workspaceDir) => { + const memoryPath = await seedBudgetMemory(workspaceDir, "legacy-mixed", "legacy-generated", [ "USER-AUTHORED: recovery key is paper-copy-17", "", - "## Promoted From Short-Term Memory (2026-04-20)", - "", - `- ${filler}`, - "", - ].join("\n"); - await fs.writeFile(memoryPath, seeded, "utf-8"); + ]); const applied = await applyBudgetCompactionPromotion(workspaceDir); @@ -2826,30 +2729,11 @@ describe("short-term promotion", () => { }); it("preserves an indented user ATX heading when compaction writes MEMORY.md", async (workspaceDir) => { - await writeDailyMemoryNote(workspaceDir, "2026-04-29", [ - "Notes", - "", - "Rotate the staging Postgres credentials before next deploy.", - ]); - - const memoryPath = path.join(workspaceDir, "MEMORY.md"); - const filler = "x".repeat(600); - const seeded = [ - "# Long-Term Memory", - "", - "## Promoted From Short-Term Memory (2026-04-10)", - "", - `- ${filler}`, - "", + const memoryPath = await seedBudgetMemory(workspaceDir, "legacy-old", "legacy-newer", [ " ### Correction (added by me)", "The prod DB is db-2.corp.example, NOT db-1.", "", - "## Promoted From Short-Term Memory (2026-04-20)", - "", - `- ${filler}`, - "", - ].join("\n"); - await fs.writeFile(memoryPath, seeded, "utf-8"); + ]); const applied = await applyBudgetCompactionPromotion(workspaceDir); @@ -2863,29 +2747,8 @@ describe("short-term promotion", () => { }); it("drops the oldest promoted section before write when memoryFileMaxChars would be exceeded", async (workspaceDir) => { - // Source daily note that the candidate references (rehydrate reads it). - await writeDailyMemoryNote(workspaceDir, "2026-04-29", [ - "Notes", - "", - "Rotate the staging Postgres credentials before next deploy.", - ]); - // Seed an oversized MEMORY.md with two pre-existing promotion sections. - const memoryPath = path.join(workspaceDir, "MEMORY.md"); - const filler = "x".repeat(600); - const seeded = [ - "# Long-Term Memory", - "", - "## Promoted From Short-Term Memory (2026-04-10)", - "", - `- ${filler}`, - "", - "## Promoted From Short-Term Memory (2026-04-20)", - "", - `- ${filler}`, - "", - ].join("\n"); - await fs.writeFile(memoryPath, seeded, "utf-8"); + const memoryPath = await seedBudgetMemory(workspaceDir, "legacy-old", "legacy-newer"); const applied = await applyBudgetCompactionPromotion(workspaceDir); @@ -3103,20 +2966,7 @@ describe("short-term promotion", () => { const seeded = `# Long-Term Memory\n\n${filler}\n- ${sentinel}\n`; await fs.writeFile(memoryPath, seeded, "utf-8"); - await recordMemoryRecalls( - workspaceDir, - "rotate creds", - [ - memoryRecallResult( - "memory/2026-04-29.md", - 3, - 3, - 0.96, - "Rotate the staging Postgres credentials before next deploy.", - ), - ], - { nowMs: Date.parse("2026-04-29T10:00:00.000Z") }, - ); + await recordRotateCredentialsRecall(workspaceDir); const ranked = await rankAllCandidates(workspaceDir); diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index 14daad2a4bc4..bd78a8913088 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -251,6 +251,19 @@ function materializeRegisteredTool( : toolOrFactory; } +function registeredTool( + registerTool: ReturnType, + name: string, + context: Record = {}, +) { + const factory = registerTool.mock.calls.find(([, options]) => options?.name === name)?.[0]; + const tool = materializeRegisteredTool(factory, context); + if (!tool) { + throw new Error(`expected ${name} tool registration`); + } + return tool; +} + function createAgentScopedSchemaMock() { return vi.fn(async () => ({ fields: [{ name: "agentId" }] })); } @@ -263,6 +276,43 @@ function createAgentScopedVectorQuery(limit: ReturnType) { }; } +function createStandardMemoryTableHarness( + options: { + toArray?: ReturnType; + limit?: ReturnType; + vectorSearch?: ReturnType; + countRows?: ReturnType; + add?: ReturnType; + deleteRows?: ReturnType; + } = {}, +) { + const toArray = options.toArray ?? vi.fn(async () => []); + const limit = options.limit ?? vi.fn(() => ({ toArray })); + const vectorSearch = options.vectorSearch ?? vi.fn(() => createAgentScopedVectorQuery(limit)); + const countRows = options.countRows ?? vi.fn(async () => 0); + const add = options.add ?? vi.fn(async () => undefined); + const deleteRows = options.deleteRows ?? vi.fn(async () => undefined); + const openTable = vi.fn(async () => ({ + schema: createAgentScopedSchemaMock(), + vectorSearch, + countRows, + add, + delete: deleteRows, + })); + const connect = vi.fn(async () => ({ + tableNames: vi.fn(async () => ["memories"]), + openTable, + })); + const module = { connect }; + return { + add, + limit, + loadLanceDbModule: vi.fn(async () => module), + module, + vectorSearch, + }; +} + function firstAddedMemory(add: ReturnType) { const batch = firstMockArg(add as MockCallSource, "memory add") as | Array> @@ -369,6 +419,19 @@ describe("memory plugin e2e", () => { }) as MemoryPluginTestConfig | undefined; } + function createPluginConfig(overrides: Partial = {}) { + return { + embedding: { + apiKey: OPENAI_API_KEY, + model: "text-embedding-3-small", + }, + dbPath: getDbPath(), + autoCapture: false, + autoRecall: false, + ...overrides, + } satisfies MemoryPluginTestConfig; + } + function setupMemoryHookHarness(options: { autoCapture: boolean; autoRecall: boolean; @@ -379,32 +442,14 @@ describe("memory plugin e2e", () => { data: [{ embedding: [0.1, 0.2, 0.3] }], })); const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); - const add = vi.fn(async () => undefined); const toArray = vi.fn(async () => options.searchResults ?? []); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const openTable = vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add, - delete: vi.fn(async () => undefined), - })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable, - })), - })); - const pluginConfig: MemoryPluginTestConfig = { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + const { add, loadLanceDbModule, vectorSearch } = createStandardMemoryTableHarness({ + toArray, + }); + const pluginConfig = createPluginConfig({ autoCapture: options.autoCapture, autoRecall: options.autoRecall, - }; + }); let configFile: Record = { plugins: { entries: { "memory-lancedb": { config: pluginConfig } } }, }; @@ -537,12 +582,7 @@ describe("memory plugin e2e", () => { test("registers as disabled instead of throwing when inspected without config", () => { const registerService = vi.fn(); - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; + const logger = createTestLogger(); const mockApi = createMemoryPluginApi(getDbPath(), { pluginConfig: {}, logger, @@ -565,15 +605,10 @@ describe("memory plugin e2e", () => { test("registers auto-recall on before_prompt_build instead of the legacy hook", () => { const on = vi.fn(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: true, - }, + }), on, }); @@ -944,18 +979,7 @@ describe("memory plugin e2e", () => { data: [{ embedding: [0.1, 0.2, 0.3] }], })); const toArray = vi.fn(async () => []); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch: vi.fn(() => createAgentScopedVectorQuery(vi.fn(() => ({ toArray })))), - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const { loadLanceDbModule } = createStandardMemoryTableHarness({ toArray }); await withMockedOpenAiMemoryPlugin({ ensureGlobalUndiciEnvProxyDispatcher: vi.fn(), @@ -1054,40 +1078,17 @@ describe("memory plugin e2e", () => { })); const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); const toArray = vi.fn(async () => []); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const { limit, loadLanceDbModule } = createStandardMemoryTableHarness({ toArray }); await withMockedOpenAiMemoryPlugin({ ensureGlobalUndiciEnvProxyDispatcher, embeddingsCreate, loadLanceDbModule, run: async () => { - const registeredTools: any[] = []; - const mockApi = createMemoryPluginApi(getDbPath(), { - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, - }); + const mockApi = createMemoryPluginApi(getDbPath()); registerTestPlugin(memoryPlugin, mockApi); - const recallTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); await recallTool.execute("test-call-string-limit", { query: "project memory", @@ -1143,37 +1144,18 @@ describe("memory plugin e2e", () => { _distance: 0.2, }, ]); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const { limit, loadLanceDbModule } = createStandardMemoryTableHarness({ toArray }); await withMockedOpenAiMemoryPlugin({ ensureGlobalUndiciEnvProxyDispatcher, embeddingsCreate, loadLanceDbModule, run: async () => { - const registeredTools: any[] = []; - const pluginConfig = { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + const pluginConfig = createPluginConfig({ autoCapture: false, autoRecall: false, recallMaxChars: 1000, - }; + }); const mockApi = createMemoryPluginApi(getDbPath(), { pluginConfig, runtime: { @@ -1189,18 +1171,10 @@ describe("memory plugin e2e", () => { }), }, }, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, }); registerTestPlugin(memoryPlugin, mockApi); - const recallTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); const result = await recallTool.execute("test-call-untrusted-recall", { query: "stored instructions", @@ -1274,27 +1248,13 @@ describe("memory plugin e2e", () => { openAiPost: post, loadLanceDbModule, run: async () => { - const registeredTools: any[] = []; - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; + const logger = createTestLogger(); const mockApi = createMemoryPluginApi(getDbPath(), { logger, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, }); registerTestPlugin(memoryPlugin, mockApi); - const recallTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); const resultPromise = recallTool.execute("timeout-call", { query: "project memory" }); await vi.advanceTimersByTimeAsync(15_000); @@ -1379,15 +1339,10 @@ describe("memory plugin e2e", () => { test("keeps before_prompt_build registered but inert when auto-recall is disabled", async () => { const on = vi.fn(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: true, autoRecall: false, - }, + }), on, }); @@ -1409,15 +1364,10 @@ describe("memory plugin e2e", () => { test("keeps agent_end registered but inert when auto-capture is disabled", async () => { const on = vi.fn(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: true, - }, + }), on, }); @@ -1452,15 +1402,10 @@ describe("memory plugin e2e", () => { run: async () => { const on = vi.fn(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: true, - }, + }), on, }); @@ -1507,21 +1452,9 @@ describe("memory plugin e2e", () => { _distance: 0.1, }, ]); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const openTable = vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable, - })), - })); + const { limit, loadLanceDbModule, vectorSearch } = createStandardMemoryTableHarness({ + toArray, + }); await withMockedOpenAiMemoryPlugin({ ensureGlobalUndiciEnvProxyDispatcher, @@ -1529,23 +1462,13 @@ describe("memory plugin e2e", () => { loadLanceDbModule, run: async () => { const on = vi.fn(); - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; + const logger = createTestLogger(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: true, recallMaxChars: 120, - }, + }), logger, on, }); @@ -1616,18 +1539,7 @@ describe("memory plugin e2e", () => { const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); const toArray = vi.fn(() => new Promise(() => {})); const limit = vi.fn(() => ({ toArray })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch: vi.fn(() => createAgentScopedVectorQuery(limit)), - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const { loadLanceDbModule } = createStandardMemoryTableHarness({ limit }); try { await withMockedOpenAiMemoryPlugin({ @@ -1636,27 +1548,13 @@ describe("memory plugin e2e", () => { loadLanceDbModule, run: async () => { const on = vi.fn(); - const registeredTools: any[] = []; - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; + const logger = createTestLogger(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: true, - }, + }), logger, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, on, }); @@ -1696,12 +1594,7 @@ describe("memory plugin e2e", () => { "memory-lancedb: auto-recall skipped during recall cooldown: auto-recall timed out after 15s", ); - const recallTool = materializeRegisteredTool( - registeredTools.find((tool) => tool.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); const toolResult = await recallTool.execute("cooldown-call", { query: "editor" }); expect(toolResult.details).toMatchObject({ count: 0, @@ -2322,23 +2215,8 @@ describe("memory plugin e2e", () => { data: [{ embedding: [0.1, 0.2, 0.3] }], })); const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); - const add = vi.fn(async () => undefined); const toArray = vi.fn(async () => overrides?.searchResults ?? []); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const openTable = vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add, - delete: vi.fn(async () => undefined), - })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable, - })), - })); + const { add, loadLanceDbModule } = createStandardMemoryTableHarness({ toArray }); installOpenAiMemoryModuleMocks({ ensureGlobalUndiciEnvProxyDispatcher, @@ -2347,22 +2225,12 @@ describe("memory plugin e2e", () => { }); const on = vi.fn(); - const logger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }; + const logger = createTestLogger(); const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: true, autoRecall: false, - }, + }), logger, on, }); @@ -2665,18 +2533,7 @@ describe("memory plugin e2e", () => { const toArray = vi.fn(async () => []); const limit = vi.fn(() => ({ toArray })); const vectorSearch = vi.fn((_vector?: number[]) => createAgentScopedVectorQuery(limit)); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const { loadLanceDbModule } = createStandardMemoryTableHarness({ limit, vectorSearch }); const post = vi.fn((_path: string, opts: { body?: unknown }) => invokeEmbeddingCreate(embeddingsCreate, opts.body), @@ -2688,30 +2545,20 @@ describe("memory plugin e2e", () => { }); try { - const registeredTools: any[] = []; const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { + pluginConfig: createPluginConfig({ embedding: { apiKey: OPENAI_API_KEY, model: "text-embedding-3-small", dimensions: 1024, }, - dbPath: getDbPath(), autoCapture: false, autoRecall: false, - }, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, + }), }); registerTestPlugin(memoryPlugin, mockApi); - const recallTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); await recallTool.execute("test-call-dims", { query: "hello dimensions" }); expect(loadLanceDbModule).toHaveBeenCalledTimes(1); @@ -2753,23 +2600,11 @@ describe("memory plugin e2e", () => { })); const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); const toArray = vi.fn(async () => []); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); + const { module } = createStandardMemoryTableHarness({ toArray }); const loadLanceDbModule = vi .fn() .mockRejectedValueOnce(new Error("temporary LanceDB install failure")) - .mockResolvedValueOnce({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 0), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - }); + .mockResolvedValueOnce(module); installOpenAiMemoryModuleMocks({ ensureGlobalUndiciEnvProxyDispatcher, @@ -2778,20 +2613,10 @@ describe("memory plugin e2e", () => { }); try { - const registeredTools: any[] = []; - const mockApi = createMemoryPluginApi(getDbPath(), { - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, - }); + const mockApi = createMemoryPluginApi(getDbPath()); registerTestPlugin(memoryPlugin, mockApi); - const recallTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool, - ); - if (!recallTool) { - throw new Error("memory_recall tool was not registered"); - } + const recallTool = registeredTool(mockApi.registerTool, "memory_recall"); await expect(recallTool.execute("test-call-retry-1", { query: "hello" })).rejects.toThrow( "temporary LanceDB install failure", @@ -3124,38 +2949,18 @@ describe("memory plugin e2e", () => { const ensureGlobalUndiciEnvProxyDispatcher = vi.fn(); const add = vi.fn(async () => undefined); const toArray = vi.fn(async (): Promise[]> => []); - const limit = vi.fn(() => ({ toArray })); - const vectorSearch = vi.fn(() => createAgentScopedVectorQuery(limit)); - const openTable = vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - add, - countRows: vi.fn(async () => 0), - delete: vi.fn(async () => undefined), - })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable, - })), - })); + const { loadLanceDbModule } = createStandardMemoryTableHarness({ add, toArray }); await withMockedOpenAiMemoryPlugin({ ensureGlobalUndiciEnvProxyDispatcher, embeddingsCreate, loadLanceDbModule, run: async () => { - const registeredTools: any[] = []; - const pluginConfig = { - embedding: { - apiKey: OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: getDbPath(), + const pluginConfig = createPluginConfig({ autoCapture: false, autoRecall: false, captureMaxChars: 1000, - }; + }); const mockApi = createMemoryPluginApi(getDbPath(), { pluginConfig, runtime: { @@ -3171,24 +2976,15 @@ describe("memory plugin e2e", () => { }), }, }, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, }); registerTestPlugin(memoryPlugin, mockApi); - const storeTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_store")?.tool, - ); - if (!storeTool) { - throw new Error("memory_store tool was not registered"); - } + const storeTool = registeredTool(mockApi.registerTool, "memory_store"); expect(storeTool.description).toContain("does not guarantee semantic recall"); - const incognitoStoreTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_store")?.tool, - { sessionKey: "agent:main:internal-session-effects:incognito-memory-test" }, - ); + const incognitoStoreTool = registeredTool(mockApi.registerTool, "memory_store", { + sessionKey: "agent:main:internal-session-effects:incognito-memory-test", + }); const incognitoRejected = await incognitoStoreTool.execute("test-call-incognito", { text: "The user prefers concise replies", }); @@ -3348,23 +3144,15 @@ describe("memory plugin e2e", () => { })), }), run: async () => { - const registeredTools: any[] = []; const mockApi = createMemoryPluginApi(getDbPath(), { - pluginConfig: { - embedding: { apiKey: OPENAI_API_KEY, model: "text-embedding-3-small" }, - dbPath: getDbPath(), + pluginConfig: createPluginConfig({ autoCapture: false, autoRecall: false, recallMaxChars: 100, - }, - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, + }), }); registerTestPlugin(memoryPlugin, mockApi); - const forgetTool = materializeRegisteredTool( - registeredTools.find((entry) => entry.opts?.name === "memory_forget")?.tool, - ); + const forgetTool = registeredTool(mockApi.registerTool, "memory_forget"); expectToolExecute(forgetTool, "memory_forget"); const directAbsent = await forgetTool.execute("forget-direct-absent", { memoryId }); @@ -3483,18 +3271,13 @@ describe("memory plugin e2e", () => { const embeddingsCreate = vi.fn(async () => ({ data: [{ embedding: [0.1, 0.2, 0.3] }], })); - const loadLanceDbModule = vi.fn(async () => ({ - connect: vi.fn(async () => ({ - tableNames: vi.fn(async () => ["memories"]), - openTable: vi.fn(async () => ({ - schema: createAgentScopedSchemaMock(), - vectorSearch, - countRows: vi.fn(async () => 2), - add: vi.fn(async () => undefined), - delete: vi.fn(async () => undefined), - })), - })), - })); + const countRows = vi.fn(async () => 2); + const { loadLanceDbModule } = createStandardMemoryTableHarness({ + toArray, + limit: limitFn, + vectorSearch, + countRows, + }); installOpenAiMemoryModuleMocks({ ensureGlobalUndiciEnvProxyDispatcher: vi.fn(), embeddingsCreate, @@ -3502,20 +3285,10 @@ describe("memory plugin e2e", () => { }); try { - const registeredTools: any[] = []; - const mockApi = createMemoryPluginApi(getDbPath(), { - registerTool: (tool: any, opts: any) => { - registeredTools.push({ tool, opts }); - }, - }); + const mockApi = createMemoryPluginApi(getDbPath()); registerTestPlugin(memoryPlugin, mockApi); - const forgetTool = materializeRegisteredTool( - registeredTools.find((t) => t.opts?.name === "memory_forget")?.tool, - ); - if (!forgetTool) { - throw new Error("expected memory_forget tool registration"); - } + const forgetTool = registeredTool(mockApi.registerTool, "memory_forget"); expectToolExecute(forgetTool); const result = await forgetTool.execute("test-call-full-ids", { query: "user preference" }); diff --git a/extensions/openshell/src/backend.exec-workdir.test.ts b/extensions/openshell/src/backend.exec-workdir.test.ts index d0a03f99b4a0..879b83f19acd 100644 --- a/extensions/openshell/src/backend.exec-workdir.test.ts +++ b/extensions/openshell/src/backend.exec-workdir.test.ts @@ -2,21 +2,16 @@ import fs from "node:fs/promises"; import path from "node:path"; import { expectDefined } from "@openclaw/normalization-core"; -import type { CreateSandboxBackendParams } from "openclaw/plugin-sdk/sandbox"; import { resolvePreferredOpenClawTmpDir, tempWorkspace, type TempWorkspace, } from "openclaw/plugin-sdk/temp-path"; -import { - createSandboxBrowserConfig, - createSandboxPruneConfig, - createSandboxSshConfig, - createSandboxTestContext, -} from "openclaw/plugin-sdk/test-fixtures"; +import { createSandboxTestContext } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createOpenShellSandboxBackendFactory } from "./backend.js"; import { resolveOpenShellPluginConfig } from "./config.js"; +import { createOpenShellBackendSandboxConfig } from "./openshell.test-support.js"; const sdkMocks = vi.hoisted(() => ({ runSshSandboxCommand: vi.fn(), @@ -50,32 +45,6 @@ vi.mock("./cli.js", async (importOriginal) => { const tempWorkspaces: TempWorkspace[] = []; -function createOpenShellBackendSandboxConfig(): CreateSandboxBackendParams["cfg"] { - return { - mode: "all", - backend: "openshell", - scope: "session", - workspaceAccess: "rw", - workspaceRoot: "/tmp/openclaw-sandboxes", - dockerTmpfsSource: "configured", - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: false, - tmpfs: [], - network: "none", - capDrop: [], - binds: [], - env: {}, - }, - ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"), - browser: createSandboxBrowserConfig(), - tools: { allow: ["*"], deny: [] }, - prune: createSandboxPruneConfig(), - }; -} - async function createOpenShellBackendFixture(params: { workspaceDir: string; scopeKey: string; @@ -152,18 +121,9 @@ describe("openshell backend exec workdir validation", () => { await fs.mkdir(protectedPath, { recursive: true }); await fs.writeFile(path.join(protectedPath, "private.txt"), "host-only", "utf8"); } - const backendFactory = createOpenShellSandboxBackendFactory({ - pluginConfig: resolveOpenShellPluginConfig({ - command: "openshell", - mode: "mirror", - }), - }); - const backend = await backendFactory({ - sessionKey: "agent:main:turn", + const backend = await createOpenShellBackendFixture({ scopeKey: "agent:somalley_alice:dashboard-8", workspaceDir, - agentWorkspaceDir: workspaceDir, - cfg: createOpenShellBackendSandboxConfig(), }); await expect(backend.validateWorkdir?.("/workspace")).resolves.toBe("/workspace"); @@ -241,18 +201,9 @@ describe("openshell backend exec workdir validation", () => { tempWorkspaces.push(workspace); const workspaceDir = workspace.dir; await fs.writeFile(path.join(workspaceDir, "seed.txt"), "seed", "utf8"); - const backendFactory = createOpenShellSandboxBackendFactory({ - pluginConfig: resolveOpenShellPluginConfig({ - command: "openshell", - mode: "mirror", - }), - }); - const backend = await backendFactory({ - sessionKey: "agent:main:turn", + const backend = await createOpenShellBackendFixture({ scopeKey: "agent:main", workspaceDir, - agentWorkspaceDir: workspaceDir, - cfg: createOpenShellBackendSandboxConfig(), }); await expect(backend.validateWorkdir?.("/workspace")).resolves.toBe("/workspace"); diff --git a/extensions/openshell/src/backend.remote-seed.test.ts b/extensions/openshell/src/backend.remote-seed.test.ts index 3f381b8c34bd..88d5ac157db8 100644 --- a/extensions/openshell/src/backend.remote-seed.test.ts +++ b/extensions/openshell/src/backend.remote-seed.test.ts @@ -3,20 +3,15 @@ // memory, and must never re-seed roots that already hold content. import fs from "node:fs/promises"; import path from "node:path"; -import type { CreateSandboxBackendParams } from "openclaw/plugin-sdk/sandbox"; import { resolvePreferredOpenClawTmpDir, tempWorkspace, type TempWorkspace, } from "openclaw/plugin-sdk/temp-path"; -import { - createSandboxBrowserConfig, - createSandboxPruneConfig, - createSandboxSshConfig, -} from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createOpenShellSandboxBackendFactory } from "./backend.js"; import { resolveOpenShellPluginConfig } from "./config.js"; +import { createOpenShellBackendSandboxConfig } from "./openshell.test-support.js"; const sdkMocks = vi.hoisted(() => ({ runSshSandboxCommand: vi.fn(), @@ -50,32 +45,6 @@ vi.mock("./cli.js", async (importOriginal) => { const tempWorkspaces: TempWorkspace[] = []; -function createOpenShellBackendSandboxConfig(): CreateSandboxBackendParams["cfg"] { - return { - mode: "all", - backend: "openshell", - scope: "session", - workspaceAccess: "rw", - workspaceRoot: "/tmp/openclaw-sandboxes", - dockerTmpfsSource: "configured", - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: false, - tmpfs: [], - network: "none", - capDrop: [], - binds: [], - env: {}, - }, - ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"), - browser: createSandboxBrowserConfig(), - tools: { allow: ["*"], deny: [] }, - prune: createSandboxPruneConfig(), - }; -} - async function createAdoptedRemoteBackend(params: { probeStdout: string }) { const workspace = await tempWorkspace({ rootDir: resolvePreferredOpenClawTmpDir(), diff --git a/extensions/openshell/src/openshell-core.test.ts b/extensions/openshell/src/openshell-core.test.ts index 33780ba3d26a..099bacbb0c41 100644 --- a/extensions/openshell/src/openshell-core.test.ts +++ b/extensions/openshell/src/openshell-core.test.ts @@ -7,19 +7,13 @@ import { buildExecRemoteCommand, disposeSshSandboxSession, shellEscape, - type CreateSandboxBackendParams, } from "openclaw/plugin-sdk/sandbox"; import { resolvePreferredOpenClawTmpDir, tempWorkspace, type TempWorkspace, } from "openclaw/plugin-sdk/temp-path"; -import { - createSandboxBrowserConfig, - createSandboxPruneConfig, - createSandboxSshConfig, - createSandboxTestContext, -} from "openclaw/plugin-sdk/test-fixtures"; +import { createSandboxTestContext } from "openclaw/plugin-sdk/test-fixtures"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenShellSandboxBackend } from "./backend.types.js"; import { @@ -28,6 +22,10 @@ import { runOpenShellCli, } from "./cli.js"; import { resolveOpenShellPluginConfig } from "./config.js"; +import { + createOpenShellBackendSandboxConfig, + createOpenShellRuntimeEntryFixture, +} from "./openshell.test-support.js"; const openShellTestWorkspaceRoot = resolvePreferredOpenClawTmpDir(); @@ -522,16 +520,7 @@ describe("openshell backend manager", () => { }); const result = await manager.describeRuntime({ - entry: { - containerName: "openclaw-session-1234", - backendId: "openshell", - runtimeLabel: "openclaw-session-1234", - sessionKey: "agent:main", - createdAtMs: 1, - lastUsedAtMs: 1, - image: "custom-source", - configLabelKind: "Source", - }, + entry: createOpenShellRuntimeEntryFixture("openclaw-session-1234", "custom-source"), config: { plugins: { entries: { @@ -579,16 +568,7 @@ describe("openshell backend manager", () => { await expect( manager.describeRuntime({ - entry: { - containerName: "openclaw-session-1234", - backendId: "openshell", - runtimeLabel: "openclaw-session-1234", - sessionKey: "agent:main", - createdAtMs: 1, - lastUsedAtMs: 1, - image: "openclaw", - configLabelKind: "Source", - }, + entry: createOpenShellRuntimeEntryFixture("openclaw-session-1234"), config: {}, }), ).resolves.toMatchObject({ running: false }); @@ -610,16 +590,7 @@ describe("openshell backend manager", () => { }); await manager.removeRuntime({ - entry: { - containerName: "openclaw-session-5678", - backendId: "openshell", - runtimeLabel: "openclaw-session-5678", - sessionKey: "agent:main", - createdAtMs: 1, - lastUsedAtMs: 1, - image: "openclaw", - configLabelKind: "Source", - }, + entry: createOpenShellRuntimeEntryFixture("openclaw-session-5678"), config: {}, }); @@ -636,16 +607,7 @@ describe("openshell backend manager", () => { }); await manager.removeRuntime({ - entry: { - containerName: "openclaw-session-5678", - backendId: "openshell", - runtimeLabel: "openclaw-session-5678", - sessionKey: "agent:main", - createdAtMs: 1, - lastUsedAtMs: 1, - image: "openclaw", - configLabelKind: "Source", - }, + entry: createOpenShellRuntimeEntryFixture("openclaw-session-5678"), config: { plugins: { entries: { @@ -691,16 +653,7 @@ describe("openshell backend manager", () => { await expect( manager.removeRuntime({ - entry: { - containerName: "openclaw-session-5678", - backendId: "openshell", - runtimeLabel: "openclaw-session-5678", - sessionKey: "agent:main", - createdAtMs: 1, - lastUsedAtMs: 1, - image: "openclaw", - configLabelKind: "Source", - }, + entry: createOpenShellRuntimeEntryFixture("openclaw-session-5678"), config: {}, }), ).rejects.toThrow(expected); @@ -929,32 +882,6 @@ describe("openshell backend manager", () => { const executableWorkspaces: TempWorkspace[] = []; -function createOpenShellBackendSandboxConfig(): CreateSandboxBackendParams["cfg"] { - return { - mode: "all", - backend: "openshell", - scope: "session", - workspaceAccess: "rw", - workspaceRoot: "/tmp/openclaw-sandboxes", - dockerTmpfsSource: "configured", - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: false, - tmpfs: [], - network: "none", - capDrop: [], - binds: [], - env: {}, - }, - ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"), - browser: createSandboxBrowserConfig(), - tools: { allow: ["*"], deny: [] }, - prune: createSandboxPruneConfig(), - }; -} - async function makeExecutable(params: { name: string; script: string }): Promise { const workspace = await createOpenShellTestWorkspace("bin"); executableWorkspaces.push(workspace); diff --git a/extensions/openshell/src/openshell.test-support.ts b/extensions/openshell/src/openshell.test-support.ts new file mode 100644 index 000000000000..5bbcc9729f2a --- /dev/null +++ b/extensions/openshell/src/openshell.test-support.ts @@ -0,0 +1,45 @@ +import type { CreateSandboxBackendParams } from "openclaw/plugin-sdk/sandbox"; +import { + createSandboxBrowserConfig, + createSandboxPruneConfig, + createSandboxSshConfig, +} from "openclaw/plugin-sdk/test-fixtures"; + +export function createOpenShellBackendSandboxConfig(): CreateSandboxBackendParams["cfg"] { + return { + mode: "all", + backend: "openshell", + scope: "session", + workspaceAccess: "rw", + workspaceRoot: "/tmp/openclaw-sandboxes", + dockerTmpfsSource: "configured", + docker: { + image: "openclaw-sandbox:bookworm-slim", + containerPrefix: "openclaw-sbx-", + workdir: "/workspace", + readOnlyRoot: false, + tmpfs: [], + network: "none", + capDrop: [], + binds: [], + env: {}, + }, + ssh: createSandboxSshConfig("/tmp/openclaw-sandboxes"), + browser: createSandboxBrowserConfig(), + tools: { allow: ["*"], deny: [] }, + prune: createSandboxPruneConfig(), + }; +} + +export function createOpenShellRuntimeEntryFixture(runtimeId: string, configLabel = "openclaw") { + return { + containerName: runtimeId, + backendId: "openshell", + runtimeLabel: runtimeId, + sessionKey: "agent:main", + createdAtMs: 1, + lastUsedAtMs: 1, + image: configLabel, + configLabelKind: "Source", + } as const; +} diff --git a/src/wizard/setup.finalize.test.ts b/src/wizard/setup.finalize.test.ts index cd5f863f5786..43bbce034fdf 100644 --- a/src/wizard/setup.finalize.test.ts +++ b/src/wizard/setup.finalize.test.ts @@ -325,40 +325,12 @@ function expectFirstOnboardingInstallPlanCallOmitsToken() { expect("token" in firstArg).toBe(false); } -type AdvancedFinalizeArgs = { - nextConfig?: OpenClawConfig; - prompter?: ReturnType; - runtime?: RuntimeEnv; - installDaemon?: boolean; -}; +type FinalizeArgs = Parameters[0]; -function createModelAuthFinalizeArgs(params: { - prompter: ReturnType; - nextConfig?: OpenClawConfig; -}) { - return { - flow: "quickstart" as const, - opts: { - acceptRisk: true, - authChoice: "skip" as const, - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: params.nextConfig ?? {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback" as const, - authMode: "token" as const, - gatewayToken: undefined, - tailscaleMode: "off" as const, - }, - prompter: params.prompter, - runtime: createRuntime(), - }; -} +type FinalizeArgsOverrides = Omit, "flow" | "opts" | "settings"> & { + opts?: Partial; + settings?: Partial; +}; function createLaterPrompter() { return buildWizardPrompter({ @@ -380,28 +352,35 @@ function createEnabledFirecrawlSearchConfig(): OpenClawConfig { }; } -function createAdvancedFinalizeArgs(params: AdvancedFinalizeArgs = {}) { +function createFinalizeArgs( + flow: FinalizeArgs["flow"], + overrides: FinalizeArgsOverrides = {}, +): FinalizeArgs { + const { opts, settings, ...rest } = overrides; return { - flow: "advanced" as const, + flow, opts: { acceptRisk: true, - authChoice: "skip" as const, - installDaemon: params.installDaemon ?? false, + authChoice: "skip", + installDaemon: false, skipHealth: true, - skipUi: true, + skipUi: flow === "advanced", + ...opts, }, baseConfig: {}, - nextConfig: params.nextConfig ?? {}, + nextConfig: {}, workspaceDir: "/tmp", settings: { port: 18789, - bind: "loopback" as const, - authMode: "token" as const, + bind: "loopback", + authMode: "token", gatewayToken: undefined, - tailscaleMode: "off" as const, + tailscaleMode: "off", + ...settings, }, - prompter: params.prompter ?? createLaterPrompter(), - runtime: params.runtime ?? createRuntime(), + prompter: createLaterPrompter(), + runtime: createRuntime(), + ...rest, }; } @@ -553,39 +532,25 @@ describe("finalizeSetupWizard", () => { const runtime = createRuntime(); try { - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "password", - password: { - source: "env", - provider: "default", - id: "OPENCLAW_GATEWAY_PASSWORD", + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + settings: { authMode: "password" }, + nextConfig: { + gateway: { + auth: { + mode: "password", + password: { + source: "env", + provider: "default", + id: "OPENCLAW_GATEWAY_PASSWORD", + }, }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "password", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime, - }); + prompter, + runtime, + }), + ); } finally { if (previous === undefined) { delete process.env.OPENCLAW_GATEWAY_PASSWORD; @@ -622,7 +587,7 @@ describe("finalizeSetupWizard", () => { }); }); - const finalizing = finalizeSetupWizard(createModelAuthFinalizeArgs({ prompter })); + const finalizing = finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); await vi.waitFor(() => expect(waitForControlUiDocument).toHaveBeenCalledOnce()); expectNoteTitleNotCalled(prompter, "Control UI"); expect(prompter.outro).not.toHaveBeenCalled(); @@ -642,7 +607,7 @@ describe("finalizeSetupWizard", () => { reason: "Control UI build failed: missing startup.js", }); const prompter = createLaterPrompter(); - const args = createModelAuthFinalizeArgs({ prompter }); + const args = createFinalizeArgs("quickstart", { prompter }); const gatewayToken = ["classic", "token"].join("-"); await finalizeSetupWizard({ @@ -681,17 +646,16 @@ describe("finalizeSetupWizard", () => { ])("does not wait for dashboard assets when $name", async ({ skipUi, enabled, reachable }) => { probeGatewayReachable.mockResolvedValue({ ok: reachable, detail: "offline" }); const prompter = createLaterPrompter(); - const args = createModelAuthFinalizeArgs({ - prompter, - nextConfig: { gateway: { controlUi: { enabled } } }, - }); const gatewayToken = ["offline", "token"].join("-"); - await finalizeSetupWizard({ - ...args, - opts: { ...args.opts, skipUi }, - settings: { ...args.settings, gatewayToken }, - }); + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { skipUi }, + nextConfig: { gateway: { controlUi: { enabled } } }, + settings: { gatewayToken }, + prompter, + }), + ); expect(resolveControlUiHandoffTarget).not.toHaveBeenCalled(); expect(waitForControlUiDocument).not.toHaveBeenCalled(); @@ -720,18 +684,17 @@ describe("finalizeSetupWizard", () => { tls: tlsConfig, }, }; - const args = createModelAuthFinalizeArgs({ prompter: createLaterPrompter(), nextConfig }); - - await finalizeSetupWizard({ - ...args, - baseConfig: { gateway: { controlUi: { basePath: "/dashboard" } } }, - settings: { - ...args.settings, - port: 19876, - bind: "custom", - customBindHost: "10.0.0.5", - }, - }); + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + baseConfig: { gateway: { controlUi: { basePath: "/dashboard" } } }, + nextConfig, + settings: { + port: 19876, + bind: "custom", + customBindHost: "10.0.0.5", + }, + }), + ); expect(resolveControlUiHandoffTarget).toHaveBeenCalledWith( expect.objectContaining({ @@ -765,27 +728,14 @@ describe("finalizeSetupWizard", () => { wsUrl: "ws://127.0.0.1:18789", }); const prompter = createLaterPrompter(); - const args = createAdvancedFinalizeArgs({ - nextConfig: { - gateway: { - bind: "lan", - }, - }, - prompter, - }); - - await finalizeSetupWizard({ - ...args, - opts: { - ...args.opts, - skipHealth: false, - skipUi: false, - }, - settings: { - ...args.settings, - bind: "lan", - }, - }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { skipHealth: false, skipUi: false }, + nextConfig: { gateway: { bind: "lan" } }, + settings: { bind: "lan" }, + prompter, + }), + ); expect(resolveAdvertisedControlUiLinks).toHaveBeenCalledWith( expect.objectContaining({ bind: "lan", port: 18789 }), @@ -799,27 +749,14 @@ describe("finalizeSetupWizard", () => { it("shows static Windows Firewall guidance for LAN Control UI links without inspection", async () => { const prompter = createLaterPrompter(); - const args = createAdvancedFinalizeArgs({ - nextConfig: { - gateway: { - bind: "lan", - }, - }, - prompter, - }); - - await finalizeSetupWizard({ - ...args, - opts: { - ...args.opts, - skipHealth: false, - skipUi: false, - }, - settings: { - ...args.settings, - bind: "lan", - }, - }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { skipHealth: false, skipUi: false }, + nextConfig: { gateway: { bind: "lan" } }, + settings: { bind: "lan" }, + prompter, + }), + ); expect(inspectWindowsGatewayFirewall).not.toHaveBeenCalled(); expectNoteContains( @@ -842,28 +779,7 @@ describe("finalizeSetupWizard", () => { confirm: vi.fn(async () => false), }); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); expect(runTui).toHaveBeenCalledWith({ local: true, @@ -901,7 +817,7 @@ describe("finalizeSetupWizard", () => { }, } satisfies OpenClawConfig; - await finalizeSetupWizard(createModelAuthFinalizeArgs({ prompter, nextConfig })); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter, nextConfig })); expect(loadModelCatalog).toHaveBeenCalledWith({ config: nextConfig, readOnly: true }); expect(resolveDefaultModelCatalogFacts).toHaveBeenCalledWith(nextConfig, catalog, { @@ -926,7 +842,7 @@ describe("finalizeSetupWizard", () => { }); await finalizeSetupWizard( - createModelAuthFinalizeArgs({ + createFinalizeArgs("quickstart", { prompter, nextConfig: { agents: { @@ -964,7 +880,7 @@ describe("finalizeSetupWizard", () => { confirm: vi.fn(async () => false), }); - await finalizeSetupWizard(createModelAuthFinalizeArgs({ prompter })); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); expect(runTui).toHaveBeenCalledWith(expect.objectContaining({ message: undefined })); expectNoteTitleNotCalled(prompter, "Model auth missing"); @@ -986,7 +902,7 @@ describe("finalizeSetupWizard", () => { confirm: vi.fn(async () => false), }); - await finalizeSetupWizard(createModelAuthFinalizeArgs({ prompter })); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); expect(runTui).toHaveBeenCalledWith(expect.objectContaining({ message: undefined })); expectNoteTitleNotCalled(prompter, "Model auth missing"); @@ -1000,29 +916,9 @@ describe("finalizeSetupWizard", () => { confirm: vi.fn(async () => false), }); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - hadExistingConfig: true, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { hadExistingConfig: true, prompter }), + ); expect(runTui).toHaveBeenCalledWith({ local: true, @@ -1048,28 +944,7 @@ describe("finalizeSetupWizard", () => { }); try { - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); expect(runTui).toHaveBeenCalledWith({ local: true, @@ -1090,28 +965,7 @@ describe("finalizeSetupWizard", () => { probeGatewayReachable.mockResolvedValueOnce({ ok: true }); const prompter = createLaterPrompter(); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); expect(prompter.outro).toHaveBeenCalledWith( "Onboarding complete. Use the dashboard link above to control OpenClaw.", @@ -1136,28 +990,13 @@ describe("finalizeSetupWizard", () => { const prompter = buildWizardPrompter({ select: select as never }); await expect( - finalizeSetupWizard({ - flow: "advanced", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "test-token", - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }), + finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { skipUi: false }, + settings: { gatewayToken: "test-token" }, + prompter, + }), + ), ).rejects.toThrow("TUI exited with code 1"); expect(restoreTerminalState).toHaveBeenCalledWith("pre-setup tui", { @@ -1185,39 +1024,26 @@ describe("finalizeSetupWizard", () => { }, }); - await finalizeSetupWizard({ - flow: "advanced", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: true, - skipHealth: true, - skipUi: true, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "token", - token: { - source: "env", - provider: "default", - id: "OPENCLAW_GATEWAY_TOKEN", + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { installDaemon: true }, + settings: { gatewayToken: "session-token" }, + nextConfig: { + gateway: { + auth: { + mode: "token", + token: { + source: "env", + provider: "default", + id: "OPENCLAW_GATEWAY_TOKEN", + }, }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "session-token", - tailscaleMode: "off", - }, - prompter, - runtime, - }); + prompter, + runtime, + }), + ); expect(resolveGatewayInstallToken).toHaveBeenCalledTimes(1); expect(buildGatewayInstallPlan).toHaveBeenCalledTimes(1); @@ -1256,7 +1082,7 @@ describe("finalizeSetupWizard", () => { }); const finalizePromise = finalizeSetupWizard( - createAdvancedFinalizeArgs({ installDaemon: true, prompter }), + createFinalizeArgs("advanced", { opts: { installDaemon: true }, prompter }), ); await vi.waitFor(() => { expect(prompter.note).toHaveBeenCalledWith("Gateway install warning", "Gateway service"); @@ -1276,7 +1102,9 @@ describe("finalizeSetupWizard", () => { throw new Error("plan failed"); }); - await finalizeSetupWizard(createAdvancedFinalizeArgs({ installDaemon: true, prompter })); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { opts: { installDaemon: true }, prompter }), + ); expect(prompter.note).toHaveBeenCalledWith("Gateway install warning", "Gateway service"); expectNoteContains(prompter, "plan failed", "Gateway"); @@ -1287,9 +1115,13 @@ describe("finalizeSetupWizard", () => { gatewayServiceInstall.mockRejectedValueOnce(new Error("service install exploded")); const prompter = createLaterPrompter(); const runtime = createRuntime(); - const args = createAdvancedFinalizeArgs({ installDaemon: true, prompter, runtime }); - - await finalizeSetupWizard({ ...args, opts: { ...args.opts, skipHealth: false } }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { installDaemon: true, skipHealth: false }, + prompter, + runtime, + }), + ); expect(waitForGatewayReachable).not.toHaveBeenCalled(); expect(probeGatewayReachable).not.toHaveBeenCalled(); @@ -1313,9 +1145,12 @@ describe("finalizeSetupWizard", () => { waitForGatewayReachable.mockResolvedValue({ ok: false, detail }); probeGatewayReachable.mockResolvedValue({ ok: false, detail }); const prompter = createLaterPrompter(); - const args = createAdvancedFinalizeArgs({ installDaemon: true, prompter }); - - await finalizeSetupWizard({ ...args, opts: { ...args.opts, skipHealth: false } }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { installDaemon: true, skipHealth: false }, + prompter, + }), + ); expectNoteContains(prompter, "managed Mock Platform Service", "Gateway"); expectNoteContains(prompter, "openclaw gateway status --deep", "Gateway"); @@ -1330,9 +1165,12 @@ describe("finalizeSetupWizard", () => { waitForGatewayReachable.mockResolvedValue({ ok: false, detail: "readiness timed out" }); probeGatewayReachable.mockResolvedValue({ ok: false, detail: "readiness timed out" }); const prompter = createLaterPrompter(); - const args = createAdvancedFinalizeArgs({ installDaemon: true, prompter }); - - await finalizeSetupWizard({ ...args, opts: { ...args.opts, skipHealth: false } }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { installDaemon: true, skipHealth: false }, + prompter, + }), + ); expectNoteContains(prompter, "托管的 Mock Platform Service 在设置后仍无法访问", "Gateway"); expectNoteContains(prompter, "检查服务状态和日志", "Gateway"); @@ -1405,12 +1243,12 @@ describe("finalizeSetupWizard", () => { detail: "external gateway is offline", }); const prompter = createLaterPrompter(); - const args = createAdvancedFinalizeArgs({ prompter }); - - await finalizeSetupWizard({ - ...args, - opts: { ...args.opts, skipHealth: false, skipUi: false }, - }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { skipHealth: false, skipUi: false }, + prompter, + }), + ); expect(isSystemdUserServiceAvailable).not.toHaveBeenCalled(); expect(isContainerEnvironment).not.toHaveBeenCalled(); @@ -1554,28 +1392,14 @@ describe("finalizeSetupWizard", () => { const runtime = { log: runtimeLog, error: runtimeError, exit: vi.fn() }; probeGatewayReachable.mockResolvedValue({ ok: true }); - await finalizeSetupWizard({ - flow: "advanced", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "session-token", - tailscaleMode: "off", - }, - prompter, - runtime, - }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + opts: { skipUi: false }, + settings: { gatewayToken: "session-token" }, + prompter, + runtime, + }), + ); const terminalOutput = [prompter.note, prompter.outro] .flatMap((writer) => vi.mocked(writer).mock.calls.flat()) @@ -1607,28 +1431,9 @@ describe("finalizeSetupWizard", () => { progress: vi.fn(() => ({ update: progressUpdate, stop: progressStop })), }); - await finalizeSetupWizard({ - flow: "advanced", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: true, - skipHealth: true, - skipUi: true, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard( + createFinalizeArgs("advanced", { opts: { installDaemon: true }, prompter }), + ); expect(gatewayServiceRestart).toHaveBeenCalledTimes(1); expect(gatewayServiceInstall).not.toHaveBeenCalled(); @@ -1643,7 +1448,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); try { - await finalizeSetupWizard(createAdvancedFinalizeArgs({ prompter })); + await finalizeSetupWizard(createFinalizeArgs("advanced", { prompter })); } finally { if (previousLocale === undefined) { delete process.env.OPENCLAW_LOCALE; @@ -1666,7 +1471,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); await finalizeSetupWizard( - createAdvancedFinalizeArgs({ + createFinalizeArgs("advanced", { nextConfig: createEnabledFirecrawlSearchConfig(), prompter, }), @@ -1697,7 +1502,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); - await finalizeSetupWizard(createAdvancedFinalizeArgs({ prompter })); + await finalizeSetupWizard(createFinalizeArgs("advanced", { prompter })); expectNoteContains( prompter, @@ -1723,7 +1528,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); await finalizeSetupWizard( - createAdvancedFinalizeArgs({ + createFinalizeArgs("advanced", { nextConfig: createEnabledFirecrawlSearchConfig(), prompter, }), @@ -1756,7 +1561,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); await finalizeSetupWizard( - createAdvancedFinalizeArgs({ + createFinalizeArgs("advanced", { nextConfig: { tools: { web: { @@ -1814,7 +1619,7 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); await finalizeSetupWizard( - createAdvancedFinalizeArgs({ + createFinalizeArgs("advanced", { nextConfig: { tools: { web: { search: { provider: "parallel-free", enabled: true } } }, }, @@ -1844,35 +1649,21 @@ describe("finalizeSetupWizard", () => { vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "env-token"); const prompter = createLaterPrompter(); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: false, - skipUi: true, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "token", - token: "config-token", + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { skipHealth: false, skipUi: true }, + settings: { gatewayToken: "session-token" }, + nextConfig: { + gateway: { + auth: { + mode: "token", + token: "config-token", + }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "session-token", - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + prompter, + }), + ); const healthArgs = requireMockArg(healthCommand) as { json?: boolean; @@ -1895,28 +1686,13 @@ describe("finalizeSetupWizard", () => { healthCommand.mockRejectedValueOnce(new ExitError(1)); const prompter = createLaterPrompter(); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: false, - skipUi: true, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "session-token", - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { skipHealth: false, skipUi: true }, + settings: { gatewayToken: "session-token" }, + prompter, + }), + ); expect(prompter.outro).toHaveBeenCalledWith(expect.stringContaining("health check failed")); }); @@ -1927,7 +1703,7 @@ describe("finalizeSetupWizard", () => { isContainerEnvironment.mockReturnValue(true); const prompter = createLaterPrompter(); - await finalizeSetupWizard(createAdvancedFinalizeArgs({ prompter })); + await finalizeSetupWizard(createFinalizeArgs("advanced", { prompter })); expectNoteContains( prompter, @@ -1957,35 +1733,21 @@ describe("finalizeSetupWizard", () => { startGatewayServer.mockResolvedValueOnce(sessionGateway); const prompter = createLaterPrompter(); - const finalizing = finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: undefined, - skipHealth: false, - skipUi: false, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "token", - token: "test-token", + const finalizing = finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { installDaemon: undefined, skipHealth: false }, + settings: { gatewayToken: "test-token" }, + nextConfig: { + gateway: { + auth: { + mode: "token", + token: "test-token", + }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "test-token", - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + prompter, + }), + ); await vi.waitFor(() => expect(sessionGateway.close).toHaveBeenCalledOnce()); expect(resolveTuiShutdownHardExitMs).toHaveBeenCalledWith({ localMode: true }); @@ -2039,7 +1801,7 @@ describe("finalizeSetupWizard", () => { startGatewayServer.mockResolvedValueOnce(sessionGateway); const prompter = createLaterPrompter(); - void finalizeSetupWizard(createModelAuthFinalizeArgs({ prompter })); + void finalizeSetupWizard(createFinalizeArgs("quickstart", { prompter })); await vi.waitFor(() => expect(sessionGateway.close).toHaveBeenCalledOnce()); expect(scheduleProcessExitAfterTuiReturn).toHaveBeenCalledOnce(); @@ -2058,35 +1820,21 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); await expect( - finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: undefined, - skipHealth: false, - skipUi: false, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "token", - token: "test-token", + finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { installDaemon: undefined, skipHealth: false }, + settings: { gatewayToken: "test-token" }, + nextConfig: { + gateway: { + auth: { + mode: "token", + token: "test-token", + }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "test-token", - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }), + prompter, + }), + ), ).rejects.toThrow("probe failed"); expect(runTui).not.toHaveBeenCalled(); @@ -2099,39 +1847,25 @@ describe("finalizeSetupWizard", () => { resolveSetupSecretInputString.mockResolvedValueOnce("session-password"); const prompter = createLaterPrompter(); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: false, - skipUi: true, - }, - baseConfig: {}, - nextConfig: { - gateway: { - auth: { - mode: "password", - password: { - source: "env", - provider: "default", - id: "OPENCLAW_GATEWAY_PASSWORD", + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { skipHealth: false, skipUi: true }, + settings: { authMode: "password" }, + nextConfig: { + gateway: { + auth: { + mode: "password", + password: { + source: "env", + provider: "default", + id: "OPENCLAW_GATEWAY_PASSWORD", + }, }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "password", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + prompter, + }), + ); const waitArgs = requireMockArg(waitForGatewayReachable) as { url?: string; @@ -2168,28 +1902,14 @@ describe("finalizeSetupWizard", () => { const prompter = createLaterPrompter(); const runtime = createRuntime(); - await finalizeSetupWizard({ - flow: "quickstart", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: false, - skipUi: false, - }, - baseConfig: {}, - nextConfig: {}, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: "test-token", - tailscaleMode: "off", - }, - prompter, - runtime, - }); + await finalizeSetupWizard( + createFinalizeArgs("quickstart", { + opts: { skipHealth: false }, + settings: { gatewayToken: "test-token" }, + prompter, + runtime, + }), + ); expect(runtime.error).not.toHaveBeenCalledWith("health failed"); expectNoteContains(prompter, "Setup was run without Gateway service install", "Gateway"); @@ -2207,40 +1927,24 @@ describe("finalizeSetupWizard", () => { confirm: vi.fn(async () => false), }); - await finalizeSetupWizard({ - flow: "advanced", - opts: { - acceptRisk: true, - authChoice: "skip", - installDaemon: false, - skipHealth: true, - skipUi: true, - }, - baseConfig: {}, - nextConfig: { - tools: { - web: { - search: { - enabled: false, - openaiCodex: { - enabled: true, - mode: "cached", + await finalizeSetupWizard( + createFinalizeArgs("advanced", { + nextConfig: { + tools: { + web: { + search: { + enabled: false, + openaiCodex: { + enabled: true, + mode: "cached", + }, }, }, }, }, - }, - workspaceDir: "/tmp", - settings: { - port: 18789, - bind: "loopback", - authMode: "token", - gatewayToken: undefined, - tailscaleMode: "off", - }, - prompter, - runtime: createRuntime(), - }); + prompter, + }), + ); expect(note.mock.calls.filter((call) => call[1] === "Codex native search")).toEqual([]); });