diff --git a/src/gateway/server.sessions.create.test.ts b/src/gateway/server.sessions.create.test.ts index 4c9f9109f22f..3c769184f60b 100644 --- a/src/gateway/server.sessions.create.test.ts +++ b/src/gateway/server.sessions.create.test.ts @@ -5,7 +5,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; -import { afterEach, expect, test, vi } from "vitest"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; import { findGitCheckoutRoot } from "../agents/worktrees/git.js"; import { @@ -57,11 +57,35 @@ import { seedSessionTranscript, } from "./test/server-sessions.test-helpers.js"; +type EnsureSessionDiffBaseline = + (typeof import("../sessions/session-diff-baseline.js"))["ensureSessionDiffBaseline"]; + +const sessionDiffBaselineMocks = vi.hoisted(() => ({ + ensure: vi.fn(), + useReal: false, +})); + +vi.mock("../sessions/session-diff-baseline.js", async (importOriginal) => { + const actual = await importOriginal(); + sessionDiffBaselineMocks.ensure.mockImplementation(async (params) => + sessionDiffBaselineMocks.useReal + ? await actual.ensureSessionDiffBaseline(params) + : params.entry, + ); + return { ...actual, ensureSessionDiffBaseline: sessionDiffBaselineMocks.ensure }; +}); + const { createSessionStoreDir, createSelectedGlobalSessionStore, openClient } = setupGatewaySessionsTestHarness(); const execFileAsync = promisify(execFile); const tempDirs = useAutoCleanupTempDirTracker(afterEach); +beforeEach(() => { + sessionDiffBaselineMocks.ensure.mockClear(); + // Baseline capture has dedicated owner coverage and one authenticated integration below. + sessionDiffBaselineMocks.useReal = false; +}); + async function makeNonGitTempDir(prefix: string): Promise { let root = await fs.realpath(os.tmpdir()); for (;;) { @@ -695,6 +719,51 @@ async function initializeGitWorkspace(root: string): Promise { return await fs.realpath(workspace); } +test("sessions.create captures and persists the initial workspace diff baseline", async () => { + const root = tempDirs.make("openclaw-session-diff-baseline-"); + const workspace = await initializeGitWorkspace(root); + await fs.appendFile(path.join(workspace, "README.md"), "dirty at session start\n"); + const { storePath } = await createSessionStoreDir(); + sessionDiffBaselineMocks.useReal = true; + const { ws } = await openClient({ + browserOrigin: "http://127.0.0.1", + client: { + id: GATEWAY_CLIENT_NAMES.CONTROL_UI, + version: "dev", + platform: "web", + mode: GATEWAY_CLIENT_MODES.WEBCHAT, + }, + }); + try { + const created = await rpcReq<{ key?: string; sessionId?: string }>(ws, "sessions.create", { + agentId: "main", + cwd: workspace, + }); + expect(created.ok, JSON.stringify(created.error)).toBe(true); + expect(sessionDiffBaselineMocks.ensure).toHaveBeenCalledTimes(1); + const sessionKey = requireNonEmptyString(created.payload?.key, "baseline session key"); + const sessionId = requireNonEmptyString(created.payload?.sessionId, "baseline session id"); + expect(loadSessionEntry({ sessionKey, storePath })).toMatchObject({ + sessionId, + spawnedCwd: workspace, + sessionDiffBaseline: { + version: 1, + sessionId, + root: workspace, + files: [ + { + path: "README.md", + fingerprint: expect.any(String), + }, + ], + }, + }); + } finally { + sessionDiffBaselineMocks.useReal = false; + ws.close(); + } +}); + function requireNonEmptyString(value: string | undefined, label: string): string { if (!value) { throw new Error(`expected ${label}`);