evOo-local/packages/adapters/src/thread-artifacts.test.ts
cynarAI e28c3acf02
feat: let the pane beside a conversation follow the bot's work (#123)
The live screen opens wide beside the chat while the bot works on it or asks
for a takeover, a file the bot presents as its result opens next to the
conversation, and a question that scrolled away gets a "Needs you" pill back
to it. Shared core rules derive the focus from signals clients already have
(pointer events, takeover requests, a new `present` flag on attach_file,
pending asks), move the pane only on focus edges, never replace a pane the
user opened, and learn per bot whether the screen opens on its own. Pane
widths persist per device; mobile shows a pill that opens the computer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7VSRHHzyt2KwoCkUkZ2Gc
2026-10-03 22:23:46 +02:00

195 lines
5.6 KiB
TypeScript

import type {
AdapterContext,
ArtifactStore,
ComputerRef,
SandboxProvider,
} from "@rakazo/adapter-kit";
import type { MessageBlock } from "@rakazo/contracts";
import type { PrismaClient } from "@rakazo/db";
import { describe, expect, it, vi } from "vitest";
import {
attachWorkspaceFileToThread,
currentTurnFilesInstruction,
materializeCurrentTurnFiles,
} from "./thread-artifacts.js";
describe("current-turn thread files", () => {
it("removes stored bytes when artifact metadata cannot be created", async () => {
const remove = vi.fn().mockResolvedValue(undefined);
const failure = new Error("database unavailable");
await expect(
attachWorkspaceFileToThread(
{
prisma: {
artifact: { create: vi.fn().mockRejectedValue(failure) },
} as unknown as PrismaClient,
artifacts: {
put: vi.fn().mockResolvedValue({ id: "stored-1", hash: "hash" }),
remove,
} as unknown as ArtifactStore,
},
{
spaceId: "workspace-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
filePath: "report.pdf",
bytes: new Uint8Array([1, 2, 3]),
operationId: "attach-1",
},
),
).rejects.toBe(failure);
expect(remove).toHaveBeenCalledWith(
"stored-1",
expect.objectContaining({ spaceId: "workspace-1", botId: "bot-1" }),
);
});
it("marks a file the bot presents as its result", async () => {
const deps = {
prisma: {
artifact: {
create: vi.fn(
async ({ data }: { data: { name: string; mimeType: string; size: number } }) => ({
id: "artifact-1",
...data,
}),
),
},
} as unknown as PrismaClient,
artifacts: {
put: vi.fn().mockResolvedValue({ id: "stored-1", hash: "hash" }),
remove: vi.fn(),
} as unknown as ArtifactStore,
};
const input = {
spaceId: "workspace-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
filePath: "out/report.md",
bytes: new TextEncoder().encode("# Report\n"),
operationId: "attach-1",
};
const presented = await attachWorkspaceFileToThread(deps, { ...input, present: true });
expect(presented.block).toMatchObject({ kind: "file", name: "report.md", present: true });
const attached = await attachWorkspaceFileToThread(deps, input);
expect(attached.block).not.toHaveProperty("present");
});
it("copies non-image attachments into the bot workspace and describes their paths", async () => {
const findMany = vi.fn().mockResolvedValue([
{
id: "artifact-1",
spaceId: "workspace-1",
botId: "bot-1",
name: "../quarterly report.pdf",
mimeType: "application/pdf",
size: 4,
storageKey: "stored-1",
},
]);
const get = vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4]));
const writeFile = vi.fn().mockResolvedValue(undefined);
const markWorkspaceDirty = vi.fn();
const context: AdapterContext & { botId: string } = {
operationId: "run-1",
traceId: "run-1",
spaceId: "workspace-1",
userId: "user-1",
botId: "bot-1",
runId: "run-1",
signal: new AbortController().signal,
};
const computer: ComputerRef = {
id: "computer-1",
botId: "bot-1",
kind: "fake",
providerRef: "fake-1",
};
const blocks: MessageBlock[] = [
{
kind: "file",
artifactId: "artifact-1",
name: "../quarterly report.pdf",
mimeType: "application/pdf",
size: 4,
},
];
const files = await materializeCurrentTurnFiles(
{
prisma: { artifact: { findMany } } as unknown as PrismaClient,
artifacts: { get } as unknown as ArtifactStore,
sandbox: { writeFile } as unknown as SandboxProvider,
},
blocks,
{ context, computer, computerMode: "team", markWorkspaceDirty },
);
expect(get).toHaveBeenCalledWith("stored-1", context);
expect(writeFile).toHaveBeenCalledWith(
computer,
{
path: "bots/bot-1/attachments/artifact-1.pdf",
content: new Uint8Array([1, 2, 3, 4]),
},
context,
);
expect(markWorkspaceDirty).toHaveBeenCalledOnce();
expect(markWorkspaceDirty.mock.invocationCallOrder[0]).toBeLessThan(
writeFile.mock.invocationCallOrder[0]!,
);
expect(files).toEqual([
{
name: "../quarterly report.pdf",
mimeType: "application/pdf",
size: 4,
path: "attachments/artifact-1.pdf",
},
]);
expect(currentTurnFilesInstruction(files)).toContain('"attachments/artifact-1.pdf"');
});
it("does not load images as computer files", async () => {
const findMany = vi.fn();
const files = await materializeCurrentTurnFiles(
{
prisma: { artifact: { findMany } } as unknown as PrismaClient,
artifacts: {} as ArtifactStore,
sandbox: {} as SandboxProvider,
},
[
{
kind: "image",
artifactId: "image-1",
name: "photo.png",
mimeType: "image/png",
},
],
{
context: {
operationId: "run-1",
traceId: "run-1",
spaceId: "workspace-1",
userId: "user-1",
botId: "bot-1",
signal: new AbortController().signal,
},
computer: {
id: "computer-1",
botId: "bot-1",
kind: "fake",
providerRef: "fake-1",
},
computerMode: "team",
},
);
expect(files).toEqual([]);
expect(findMany).not.toHaveBeenCalled();
});
});