Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion app/src/lib/computers/mutations.ts
Original file line number Diff line number Diff line change
@@ -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. */
Expand All @@ -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);
},
});
}

Expand Down
74 changes: 74 additions & 0 deletions app/tests/computer-reset-clears-activity.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});