diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e3c0931..1c6169fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,14 @@ The tools run in the browser as the signed-in person, over the same `POST /api/p person uses, so who may take a slug is answered the same way and the `configuration.changed` audit row is written the same way. +### Resetting a computer clears the activity pane + +Resetting a Bot's computer deletes the browser profile it worked on, but the activity pane beside the +chat went on listing the commands and file operations that ran there. They sat alongside anything the +Bot did afterwards with nothing to tell the two apart, and only reloading the page cleared them. The +pane now forgets a Bot's history when that Bot's computer is reset. Stopping a browser is unchanged: +the profile survives a stop and is meant to resume, so what it did is still true of the machine. + ### A person can set standing instructions that every coworker follows Settings now has a box for standing instructions: one piece of text per person, saved once and diff --git a/app/src/lib/computers/mutations.ts b/app/src/lib/computers/mutations.ts index 8fa014bec..a4c1e8f43 100644 --- a/app/src/lib/computers/mutations.ts +++ b/app/src/lib/computers/mutations.ts @@ -1,5 +1,6 @@ import { mutationOptions, type QueryClient } from "@tanstack/react-query"; import { client } from "@/lib/client"; +import { clearActivity } from "./activity"; import { type ActionPolicy, computerKeys } from "./queries"; /** Stopping frees the container; resetting also deletes the browser profile. */ @@ -23,7 +24,11 @@ export function setComputerStateMutationOptions(queryClient: QueryClient) { }, ); }, - onSuccess: () => invalidateComputers(queryClient), + /** A reset deletes the profile those commands ran on; a stop keeps it, so only reset forgets. */ + onSuccess: (_result, variables) => { + if (variables.action === "reset") clearActivity(variables.botId); + return invalidateComputers(queryClient); + }, }); } diff --git a/app/tests/computer-reset-clears-activity.test.ts b/app/tests/computer-reset-clears-activity.test.ts new file mode 100644 index 000000000..53e2be294 --- /dev/null +++ b/app/tests/computer-reset-clears-activity.test.ts @@ -0,0 +1,74 @@ +import { afterEach, beforeEach, expect, test } from "bun:test"; +import type { QueryClient } from "@tanstack/react-query"; +import { + activityFor, + clearActivity, + recordActivity, +} from "../src/lib/computers/activity"; +import { setComputerStateMutationOptions } from "../src/lib/computers/mutations"; + +const realFetch = globalThis.fetch; + +const queryClient = { + invalidateQueries: async () => undefined, +} as unknown as QueryClient; + +beforeEach(() => { + globalThis.fetch = (async () => + new Response(null, { status: 200 })) as unknown as typeof fetch; + clearActivity("general-assistant"); + recordActivity("general-assistant", { + kind: "command", + subject: "echo repro-marker-bravo", + output: "repro-marker-bravo\n", + exitCode: 0, + }); +}); + +afterEach(() => { + globalThis.fetch = realFetch; + clearActivity("general-assistant"); +}); + +async function run(action: "stop" | "reset") { + const options = setComputerStateMutationOptions(queryClient); + const variables = { action, botId: "general-assistant" } as const; + await options.mutationFn?.(variables); + await options.onSuccess?.( + undefined as never, + variables, + undefined as never, + undefined as never, + ); +} + +test("resetting a computer forgets what the Bot did on the profile it deleted", async () => { + expect(activityFor("general-assistant")).toHaveLength(1); + + await run("reset"); + + expect(activityFor("general-assistant")).toEqual([]); +}); + +test("stopping a browser keeps the history, because the profile survives it", async () => { + await run("stop"); + + expect(activityFor("general-assistant")).toHaveLength(1); + expect(activityFor("general-assistant")[0]?.subject).toBe( + "echo repro-marker-bravo", + ); +}); + +test("one Bot's reset leaves another Bot's history alone", async () => { + recordActivity("knowledge", { + kind: "list_files", + subject: "the workspace", + output: "notes.md\n", + }); + + await run("reset"); + + expect(activityFor("general-assistant")).toEqual([]); + expect(activityFor("knowledge")).toHaveLength(1); + clearActivity("knowledge"); +});