Replace plugin diff bridge with workspace metadata

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-16 08:41:18 -05:00
1 parent 56eb9a5638
commit 09ea87fb12
16 files changed
+1058 -128

No files matched your search

@@ -1,11 +1,40 @@
import { describe, expect, it, vi } from "vitest";
import { execFile } from "node:child_process";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import { createTestHarness } from "@paperclipai/plugin-sdk/testing";
import manifest from "../src/manifest.js";
import plugin from "../src/worker.js";
import { diffResponse } from "./fixtures.js";
const execFileAsync = promisify(execFile);
const tempRoots: string[] = [];
async function git(cwd: string, args: string[]) {
return execFileAsync("git", ["-C", cwd, ...args], { cwd });
}
async function createGitWorkspace() {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-workspace-diff-plugin-"));
tempRoots.push(root);
await fs.mkdir(path.join(root, "src"), { recursive: true });
await git(root, ["init"]);
await git(root, ["config", "user.email", "paperclip@example.com"]);
await git(root, ["config", "user.name", "Paperclip Test"]);
await fs.writeFile(path.join(root, "src/app.ts"), "export const value = 1;\n");
await git(root, ["add", "src/app.ts"]);
await git(root, ["commit", "-m", "initial"]);
return root;
}
describe("workspace diff plugin", () => {
it("declares workspace Changes tabs and host diff capability", () => {
afterEach(async () => {
await Promise.all(tempRoots.map((root) => fs.rm(root, { recursive: true, force: true })));
tempRoots.length = 0;
});
it("declares workspace Changes tabs and workspace read capabilities", () => {
expect(manifest.capabilities).toContain("ui.detailTab.register");
expect(manifest.capabilities).toContain("execution.workspaces.read");
expect(manifest.capabilities).toContain("project.workspaces.read");
@@ -16,10 +45,25 @@ describe("workspace diff plugin", () => {
}));
});
it("fetches changed workspace diffs through the host bridge client", async () => {
it("fetches changed execution workspace diffs from host metadata", async () => {
const root = await createGitWorkspace();
await fs.writeFile(path.join(root, "src/app.ts"), "export const value = 2;\n");
const harness = createTestHarness({ manifest });
const getDiff = vi.fn().mockResolvedValue(diffResponse());
harness.ctx.executionWorkspaces.getDiff = getDiff;
harness.seed({
executionWorkspaces: [{
id: "workspace-1",
companyId: "company-1",
projectId: "project-1",
projectWorkspaceId: null,
path: root,
cwd: root,
repoUrl: null,
baseRef: "HEAD",
branchName: "main",
providerType: "git_worktree",
providerMetadata: null,
}],
});
await plugin.definition.setup(harness.ctx);
const result = await harness.getData("workspace-diff", {
@@ -30,40 +74,36 @@ describe("workspace diff plugin", () => {
paths: ["src/app.ts"],
});
expect(getDiff).toHaveBeenCalledWith("workspace-1", "company-1", {
view: "working-tree",
baseRef: null,
includeUntracked: false,
paths: ["src/app.ts"],
});
expect(result).toMatchObject({
stats: { fileCount: 1 },
files: [expect.objectContaining({ path: "src/app.ts" })],
});
});
it("returns empty and truncated host responses without fabricating filesystem data", async () => {
it("returns an empty diff when the workspace has no changes", async () => {
const root = await createGitWorkspace();
const harness = createTestHarness({ manifest });
const empty = diffResponse({ files: [] });
const truncated = diffResponse({
files: [],
truncated: true,
warnings: [{ code: "file_count_truncated", message: "Too many files.", path: null }],
harness.seed({
executionWorkspaces: [{
id: "workspace-1",
companyId: "company-1",
projectId: "project-1",
projectWorkspaceId: null,
path: root,
cwd: root,
repoUrl: null,
baseRef: "HEAD",
branchName: "main",
providerType: "git_worktree",
providerMetadata: null,
}],
});
const getDiff = vi.fn()
.mockResolvedValueOnce(empty)
.mockResolvedValueOnce(truncated);
harness.ctx.executionWorkspaces.getDiff = getDiff;
await plugin.definition.setup(harness.ctx);
await expect(harness.getData("workspace-diff", {
workspaceId: "workspace-1",
companyId: "company-1",
})).resolves.toMatchObject({ files: [], truncated: false });
await expect(harness.getData("workspace-diff", {
workspaceId: "workspace-1",
companyId: "company-1",
})).resolves.toMatchObject({ files: [], truncated: true });
});
it("returns a clear bridge error when required context is missing", async () => {