fix(workspace): persist bootstrap onboarding state

This commit is contained in:
Gustavo Madeira Santana
2026-02-14 18:56:26 -05:00
parent ea0ef18704
commit 28b78b25b7
3 changed files with 178 additions and 14 deletions

View File

@@ -5,8 +5,11 @@ import { makeTempWorkspace, writeWorkspaceFile } from "../test-helpers/workspace
import {
DEFAULT_AGENTS_FILENAME,
DEFAULT_BOOTSTRAP_FILENAME,
DEFAULT_IDENTITY_FILENAME,
DEFAULT_MEMORY_ALT_FILENAME,
DEFAULT_MEMORY_FILENAME,
DEFAULT_TOOLS_FILENAME,
DEFAULT_USER_FILENAME,
ensureAgentWorkspace,
loadWorkspaceBootstrapFiles,
resolveDefaultAgentWorkspaceDir,
@@ -23,8 +26,23 @@ describe("resolveDefaultAgentWorkspaceDir", () => {
});
});
const WORKSPACE_STATE_PATH_SEGMENTS = [".openclaw", "workspace-state.json"] as const;
async function readOnboardingState(dir: string): Promise<{
version: number;
bootstrapSeededAt?: string;
onboardingCompletedAt?: string;
}> {
const raw = await fs.readFile(path.join(dir, ...WORKSPACE_STATE_PATH_SEGMENTS), "utf-8");
return JSON.parse(raw) as {
version: number;
bootstrapSeededAt?: string;
onboardingCompletedAt?: string;
};
}
describe("ensureAgentWorkspace", () => {
it("creates BOOTSTRAP.md for a brand new workspace", async () => {
it("creates BOOTSTRAP.md and records a seeded marker for brand new workspaces", async () => {
const tempDir = await makeTempWorkspace("openclaw-workspace-");
await ensureAgentWorkspace({ dir: tempDir, ensureBootstrapFiles: true });
@@ -32,9 +50,12 @@ describe("ensureAgentWorkspace", () => {
await expect(
fs.access(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME)),
).resolves.toBeUndefined();
const state = await readOnboardingState(tempDir);
expect(state.bootstrapSeededAt).toMatch(/\d{4}-\d{2}-\d{2}T/);
expect(state.onboardingCompletedAt).toBeUndefined();
});
it("creates BOOTSTRAP.md even when workspace already has other bootstrap files", async () => {
it("recovers partial initialization by creating BOOTSTRAP.md when marker is missing", async () => {
const tempDir = await makeTempWorkspace("openclaw-workspace-");
await writeWorkspaceFile({ dir: tempDir, name: DEFAULT_AGENTS_FILENAME, content: "existing" });
@@ -43,18 +64,41 @@ describe("ensureAgentWorkspace", () => {
await expect(
fs.access(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME)),
).resolves.toBeUndefined();
const state = await readOnboardingState(tempDir);
expect(state.bootstrapSeededAt).toMatch(/\d{4}-\d{2}-\d{2}T/);
});
it("does not recreate BOOTSTRAP.md after onboarding deletion", async () => {
it("does not recreate BOOTSTRAP.md after completion, even when a core file is recreated", async () => {
const tempDir = await makeTempWorkspace("openclaw-workspace-");
await ensureAgentWorkspace({ dir: tempDir, ensureBootstrapFiles: true });
await writeWorkspaceFile({ dir: tempDir, name: DEFAULT_IDENTITY_FILENAME, content: "custom" });
await writeWorkspaceFile({ dir: tempDir, name: DEFAULT_USER_FILENAME, content: "custom" });
await fs.unlink(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME));
await fs.unlink(path.join(tempDir, DEFAULT_TOOLS_FILENAME));
await ensureAgentWorkspace({ dir: tempDir, ensureBootstrapFiles: true });
await expect(fs.access(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME))).rejects.toMatchObject({
code: "ENOENT",
});
await expect(fs.access(path.join(tempDir, DEFAULT_TOOLS_FILENAME))).resolves.toBeUndefined();
const state = await readOnboardingState(tempDir);
expect(state.onboardingCompletedAt).toMatch(/\d{4}-\d{2}-\d{2}T/);
});
it("does not re-seed BOOTSTRAP.md for legacy completed workspaces without state marker", async () => {
const tempDir = await makeTempWorkspace("openclaw-workspace-");
await writeWorkspaceFile({ dir: tempDir, name: DEFAULT_IDENTITY_FILENAME, content: "custom" });
await writeWorkspaceFile({ dir: tempDir, name: DEFAULT_USER_FILENAME, content: "custom" });
await ensureAgentWorkspace({ dir: tempDir, ensureBootstrapFiles: true });
await expect(fs.access(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME))).rejects.toMatchObject({
code: "ENOENT",
});
const state = await readOnboardingState(tempDir);
expect(state.bootstrapSeededAt).toBeUndefined();
expect(state.onboardingCompletedAt).toMatch(/\d{4}-\d{2}-\d{2}T/);
});
});