mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 13:24:55 +01:00
Replace plugin diff bridge with workspace metadata
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
56eb9a5638
commit
09ea87fb12
16 files changed
+1058
-128
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createHostClientHandlers } from "../../../packages/plugins/sdk/src/host-client-factory.js";
|
||||
import { PLUGIN_RPC_ERROR_CODES } from "../../../packages/plugins/sdk/src/protocol.js";
|
||||
|
||||
describe("plugin execution workspace bridge", () => {
|
||||
it("routes metadata reads through the host client when the capability is declared", async () => {
|
||||
const get = vi.fn().mockResolvedValue({
|
||||
id: "workspace-1",
|
||||
companyId: "company-1",
|
||||
projectId: "project-1",
|
||||
projectWorkspaceId: null,
|
||||
path: "/tmp/workspace-1",
|
||||
cwd: "/tmp/workspace-1",
|
||||
repoUrl: null,
|
||||
baseRef: "main",
|
||||
branchName: "feature/workspace-1",
|
||||
providerType: "git_worktree",
|
||||
providerMetadata: null,
|
||||
});
|
||||
const handlers = createHostClientHandlers({
|
||||
pluginId: "workspace-plugin",
|
||||
capabilities: ["execution.workspaces.read"],
|
||||
services: {
|
||||
executionWorkspaces: { get },
|
||||
} as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
handlers["executionWorkspaces.get"]({ workspaceId: "workspace-1", companyId: "company-1" }),
|
||||
).resolves.toMatchObject({
|
||||
id: "workspace-1",
|
||||
cwd: "/tmp/workspace-1",
|
||||
});
|
||||
expect(get).toHaveBeenCalledWith({ workspaceId: "workspace-1", companyId: "company-1" });
|
||||
});
|
||||
|
||||
it("rejects metadata reads when the plugin lacks execution.workspace read access", async () => {
|
||||
const get = vi.fn();
|
||||
const handlers = createHostClientHandlers({
|
||||
pluginId: "workspace-plugin",
|
||||
capabilities: [],
|
||||
services: {
|
||||
executionWorkspaces: { get },
|
||||
} as any,
|
||||
});
|
||||
|
||||
await expect(
|
||||
handlers["executionWorkspaces.get"]({ workspaceId: "workspace-1", companyId: "company-1" }),
|
||||
).rejects.toMatchObject({
|
||||
code: PLUGIN_RPC_ERROR_CODES.CAPABILITY_DENIED,
|
||||
});
|
||||
expect(get).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
companies,
|
||||
costEvents,
|
||||
createDb,
|
||||
executionWorkspaces,
|
||||
heartbeatRuns,
|
||||
issueRelations,
|
||||
issues,
|
||||
@@ -67,6 +68,7 @@ describeEmbeddedPostgres("plugin orchestration APIs", () => {
|
||||
await db.delete(agentWakeupRequests);
|
||||
await db.delete(issueRelations);
|
||||
await db.delete(issues);
|
||||
await db.delete(executionWorkspaces);
|
||||
await db.delete(pluginManagedResources);
|
||||
await db.delete(projects);
|
||||
await db.delete(plugins);
|
||||
@@ -107,6 +109,61 @@ describeEmbeddedPostgres("plugin orchestration APIs", () => {
|
||||
return root;
|
||||
}
|
||||
|
||||
it("returns plugin-safe execution workspace metadata scoped to the company", async () => {
|
||||
const { companyId } = await seedCompanyAndAgent();
|
||||
const otherCompanyId = randomUUID();
|
||||
const projectId = randomUUID();
|
||||
const workspaceId = randomUUID();
|
||||
await db.insert(companies).values({
|
||||
id: otherCompanyId,
|
||||
name: "Other",
|
||||
issuePrefix: issuePrefix(otherCompanyId),
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
});
|
||||
await db.insert(projects).values({
|
||||
id: projectId,
|
||||
companyId,
|
||||
name: "Workspaces",
|
||||
status: "in_progress",
|
||||
});
|
||||
await db.insert(executionWorkspaces).values({
|
||||
id: workspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
mode: "isolated_workspace",
|
||||
strategyType: "git_worktree",
|
||||
name: "Feature workspace",
|
||||
status: "active",
|
||||
cwd: "/tmp/paperclip-feature",
|
||||
repoUrl: "https://example.com/paperclip.git",
|
||||
baseRef: "main",
|
||||
branchName: "feature/workspace",
|
||||
providerType: "git_worktree",
|
||||
providerRef: "/tmp/paperclip-feature",
|
||||
metadata: {
|
||||
providerMetadata: { sandboxId: "sandbox-1" },
|
||||
workspaceRealizationRequest: { hiddenInternal: true },
|
||||
},
|
||||
});
|
||||
|
||||
const services = buildHostServices(db, "plugin-record-id", "paperclip.workspace", createEventBusStub());
|
||||
|
||||
await expect(services.executionWorkspaces.get({ workspaceId, companyId })).resolves.toMatchObject({
|
||||
id: workspaceId,
|
||||
companyId,
|
||||
projectId,
|
||||
projectWorkspaceId: null,
|
||||
path: "/tmp/paperclip-feature",
|
||||
cwd: "/tmp/paperclip-feature",
|
||||
repoUrl: "https://example.com/paperclip.git",
|
||||
baseRef: "main",
|
||||
branchName: "feature/workspace",
|
||||
providerType: "git_worktree",
|
||||
providerMetadata: { sandboxId: "sandbox-1" },
|
||||
});
|
||||
await expect(services.executionWorkspaces.get({ workspaceId, companyId: otherCompanyId })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("creates plugin-origin issues with full orchestration fields and audit activity", async () => {
|
||||
const { companyId, agentId } = await seedCompanyAndAgent();
|
||||
const blockerIssueId = randomUUID();
|
||||
|
||||
@@ -3,6 +3,63 @@ import type { PaperclipPluginManifestV1 } from "@paperclipai/shared";
|
||||
import { createTestHarness } from "@paperclipai/plugin-sdk/testing";
|
||||
|
||||
describe("plugin SDK test harness", () => {
|
||||
it("returns scoped execution workspace metadata with the read capability", async () => {
|
||||
const manifest: PaperclipPluginManifestV1 = {
|
||||
id: "paperclip.test-execution-workspace-metadata",
|
||||
apiVersion: 1,
|
||||
version: "0.1.0",
|
||||
displayName: "Execution Workspace Metadata",
|
||||
description: "Test plugin",
|
||||
author: "Paperclip",
|
||||
categories: ["automation"],
|
||||
capabilities: ["execution.workspaces.read"],
|
||||
entrypoints: { worker: "./dist/worker.js" },
|
||||
};
|
||||
const harness = createTestHarness({ manifest });
|
||||
harness.seed({
|
||||
executionWorkspaces: [{
|
||||
id: "workspace-1",
|
||||
companyId: "company-1",
|
||||
projectId: "project-1",
|
||||
projectWorkspaceId: "project-workspace-1",
|
||||
path: "/tmp/paperclip-test",
|
||||
cwd: "/tmp/paperclip-test",
|
||||
repoUrl: "https://example.com/repo.git",
|
||||
baseRef: "main",
|
||||
branchName: "feature/test",
|
||||
providerType: "git_worktree",
|
||||
providerMetadata: { sandboxId: "sandbox-1" },
|
||||
}],
|
||||
});
|
||||
|
||||
await expect(harness.ctx.executionWorkspaces.get("workspace-1", "company-1")).resolves.toMatchObject({
|
||||
id: "workspace-1",
|
||||
cwd: "/tmp/paperclip-test",
|
||||
branchName: "feature/test",
|
||||
providerMetadata: { sandboxId: "sandbox-1" },
|
||||
});
|
||||
await expect(harness.ctx.executionWorkspaces.get("workspace-1", "company-2")).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("requires execution.workspaces.read before returning workspace metadata", async () => {
|
||||
const manifest: PaperclipPluginManifestV1 = {
|
||||
id: "paperclip.test-missing-execution-workspace-read",
|
||||
apiVersion: 1,
|
||||
version: "0.1.0",
|
||||
displayName: "Missing Workspace Read Capability",
|
||||
description: "Test plugin",
|
||||
author: "Paperclip",
|
||||
categories: ["automation"],
|
||||
capabilities: [],
|
||||
entrypoints: { worker: "./dist/worker.js" },
|
||||
};
|
||||
const harness = createTestHarness({ manifest });
|
||||
|
||||
await expect(harness.ctx.executionWorkspaces.get("workspace-1", "company-1")).rejects.toThrow(
|
||||
"missing required capability 'execution.workspaces.read'",
|
||||
);
|
||||
});
|
||||
|
||||
it("requires skills.managed capability before resetting a missing declaration", async () => {
|
||||
const manifest: PaperclipPluginManifestV1 = {
|
||||
id: "paperclip.test-missing-managed-skill-capability",
|
||||
|
||||
Reference in new issue
Block a user