diff --git a/src/agents/workspace.test.ts b/src/agents/workspace.test.ts index 3586c6c8e..ac236e3c0 100644 --- a/src/agents/workspace.test.ts +++ b/src/agents/workspace.test.ts @@ -121,6 +121,21 @@ describe("ensureAgentWorkspace", () => { const memoryContent = await fs.readFile(path.join(tempDir, "MEMORY.md"), "utf-8"); expect(memoryContent).toBe("# Long-term memory\nImportant stuff"); }); + + it("treats git-backed workspaces as existing even when template files are missing", async () => { + const tempDir = await makeTempWorkspace("openclaw-workspace-"); + await fs.mkdir(path.join(tempDir, ".git"), { recursive: true }); + await fs.writeFile(path.join(tempDir, ".git", "HEAD"), "ref: refs/heads/main\n"); + + await ensureAgentWorkspace({ dir: tempDir, ensureBootstrapFiles: true }); + + await expect(fs.access(path.join(tempDir, DEFAULT_IDENTITY_FILENAME))).resolves.toBeUndefined(); + await expect(fs.access(path.join(tempDir, DEFAULT_BOOTSTRAP_FILENAME))).rejects.toMatchObject({ + code: "ENOENT", + }); + const state = await readOnboardingState(tempDir); + expect(state.onboardingCompletedAt).toMatch(/\d{4}-\d{2}-\d{2}T/); + }); }); describe("loadWorkspaceBootstrapFiles", () => { diff --git a/src/agents/workspace.ts b/src/agents/workspace.ts index d4db74358..830b44504 100644 --- a/src/agents/workspace.ts +++ b/src/agents/workspace.ts @@ -408,7 +408,11 @@ export async function ensureAgentWorkspace(params?: { fs.readFile(userPath, "utf-8"), ]); const hasUserContent = await (async () => { - const indicators = [path.join(dir, "memory"), path.join(dir, DEFAULT_MEMORY_FILENAME)]; + const indicators = [ + path.join(dir, "memory"), + path.join(dir, DEFAULT_MEMORY_FILENAME), + path.join(dir, ".git"), + ]; for (const indicator of indicators) { try { await fs.access(indicator); diff --git a/src/commands/onboard.test.ts b/src/commands/onboard.test.ts index 9e7dde1ed..a0f8d205c 100644 --- a/src/commands/onboard.test.ts +++ b/src/commands/onboard.test.ts @@ -76,6 +76,34 @@ describe("onboardCommand", () => { ); }); + it("uses configured default workspace for --reset when --workspace is not provided", async () => { + const runtime = makeRuntime(); + mocks.readConfigFileSnapshot.mockResolvedValue({ + exists: true, + valid: true, + config: { + agents: { + defaults: { + workspace: "/tmp/openclaw-custom-workspace", + }, + }, + }, + }); + + await onboardCommand( + { + reset: true, + }, + runtime, + ); + + expect(mocks.handleReset).toHaveBeenCalledWith( + "config+creds+sessions", + "/tmp/openclaw-custom-workspace", + runtime, + ); + }); + it("accepts explicit --reset-scope full", async () => { const runtime = makeRuntime(); diff --git a/src/plugins/wired-hooks-compaction.test.ts b/src/plugins/wired-hooks-compaction.test.ts index f58d0d680..31eec6bb4 100644 --- a/src/plugins/wired-hooks-compaction.test.ts +++ b/src/plugins/wired-hooks-compaction.test.ts @@ -2,6 +2,7 @@ * Test: before_compaction & after_compaction hook wiring */ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { emitAgentEvent } from "../infra/agent-events.js"; const hookMocks = vi.hoisted(() => ({ runner: { @@ -35,6 +36,7 @@ describe("compaction hook wiring", () => { hookMocks.runner.runBeforeCompaction.mockResolvedValue(undefined); hookMocks.runner.runAfterCompaction.mockClear(); hookMocks.runner.runAfterCompaction.mockResolvedValue(undefined); + vi.mocked(emitAgentEvent).mockClear(); }); it("calls runBeforeCompaction in handleAutoCompactionStart", () => { @@ -45,6 +47,7 @@ describe("compaction hook wiring", () => { runId: "r1", sessionKey: "agent:main:web-abc123", session: { messages: [1, 2, 3], sessionFile: "/tmp/test.jsonl" }, + onAgentEvent: vi.fn(), }, state: { compactionInFlight: false }, log: { debug: vi.fn(), warn: vi.fn() }, @@ -67,6 +70,16 @@ describe("compaction hook wiring", () => { expect(event?.sessionFile).toBe("/tmp/test.jsonl"); const hookCtx = beforeCalls[0]?.[1] as { sessionKey?: string } | undefined; expect(hookCtx?.sessionKey).toBe("agent:main:web-abc123"); + expect(ctx.ensureCompactionPromise).toHaveBeenCalledTimes(1); + expect(emitAgentEvent).toHaveBeenCalledWith({ + runId: "r1", + stream: "compaction", + data: { phase: "start" }, + }); + expect(ctx.params.onAgentEvent).toHaveBeenCalledWith({ + stream: "compaction", + data: { phase: "start" }, + }); }); it("calls runAfterCompaction when willRetry is false", () => { @@ -77,6 +90,7 @@ describe("compaction hook wiring", () => { state: { compactionInFlight: true }, log: { debug: vi.fn(), warn: vi.fn() }, maybeResolveCompactionWait: vi.fn(), + incrementCompactionCount: vi.fn(), getCompactionCount: () => 1, }; @@ -98,6 +112,13 @@ describe("compaction hook wiring", () => { | undefined; expect(event?.messageCount).toBe(2); expect(event?.compactedCount).toBe(1); + expect(ctx.incrementCompactionCount).toHaveBeenCalledTimes(1); + expect(ctx.maybeResolveCompactionWait).toHaveBeenCalledTimes(1); + expect(emitAgentEvent).toHaveBeenCalledWith({ + runId: "r2", + stream: "compaction", + data: { phase: "end", willRetry: false }, + }); }); it("does not call runAfterCompaction when willRetry is true", () => { @@ -109,6 +130,7 @@ describe("compaction hook wiring", () => { log: { debug: vi.fn(), warn: vi.fn() }, noteCompactionRetry: vi.fn(), resetForCompactionRetry: vi.fn(), + maybeResolveCompactionWait: vi.fn(), getCompactionCount: () => 0, }; @@ -121,6 +143,14 @@ describe("compaction hook wiring", () => { ); expect(hookMocks.runner.runAfterCompaction).not.toHaveBeenCalled(); + expect(ctx.noteCompactionRetry).toHaveBeenCalledTimes(1); + expect(ctx.resetForCompactionRetry).toHaveBeenCalledTimes(1); + expect(ctx.maybeResolveCompactionWait).not.toHaveBeenCalled(); + expect(emitAgentEvent).toHaveBeenCalledWith({ + runId: "r3", + stream: "compaction", + data: { phase: "end", willRetry: true }, + }); }); it("clears stale assistant usage after final compaction", () => {