2026-05-19 15:51:13 -05:00
|
|
|
import { definePlugin, runWorker, type PluginContext } from "@paperclipai/plugin-sdk";
|
[codex] Add workspace diff viewer plugin (#6071)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators need to inspect what agents changed inside execution and
project workspaces.
> - The existing workspace detail views did not provide a first-party
rich diff surface for staged, unstaged, head, renamed, binary,
oversized, and untracked changes.
> - The plugin system is the intended extension point for optional rich
UI surfaces.
> - This pull request adds a workspace diff plugin plus host services
and shared contracts so Changes tabs can render workspace diffs through
plugin slots.
> - The diff-renderer dependency should stay owned by the plugin package
rather than the core UI app.
> - The dependency surface must stay aligned with repository PR policy,
including intentionally omitting `pnpm-lock.yaml` from the PR.
> - The benefit is a more reviewable workspace surface without
hard-coding the renderer into every page.
## What Changed
- Added `@paperclipai/plugin-workspace-diff`, including diff
normalization, plugin manifest/worker/UI entrypoints, and focused plugin
tests.
- Kept `@pierre/diffs` scoped to `@paperclipai/plugin-workspace-diff`;
removed the core UI lab diff-renderer surface and direct UI package
dependency.
- Added shared workspace diff types and validators, plus plugin SDK
surface for workspace diff host services.
- Added server workspace diff service support and route coverage for
execution/project workspace diff flows.
- Wired Execution Workspace and Project Workspace Changes tabs to load
the diff plugin, including loading/error fallback behavior.
- Added UI tests and fixtures for the Changes tabs and plugin bridge
behavior.
- Added the new plugin package manifest to the Docker deps stage so PR
policy can validate dependency coverage.
- Addressed review hardening around empty untracked patches, workspace
path exposure, project workspace read capability checks, and default
base refs.
## Verification
- `pnpm --filter @paperclipai/plugin-workspace-diff test`
- `pnpm exec vitest run
packages/shared/src/validators/workspace-diff.test.ts
server/src/__tests__/workspace-diff-service.test.ts
ui/src/pages/ProjectWorkspaceDetail.test.tsx
ui/src/pages/ExecutionWorkspaceDetail.test.tsx`
- `pnpm exec vitest run ui/src/plugins/bridge.test.ts
server/src/__tests__/workspace-runtime-routes-authz.test.ts`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/plugin-workspace-diff typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `node ./scripts/check-docker-deps-stage.mjs`
- Browser screenshot captured from the local worktree dev server:
https://files.catbox.moe/ofdpsp.png
- Confirmed branch is rebased onto `public-gh/master`,
`.github/workflows/pr.yml` is not included in the PR diff,
`ui/package.json` is not included in the PR diff, and `pnpm-lock.yaml`
is not included in the PR diff.
## Risks
- Medium UI integration risk: the Changes tab depends on the plugin slot
and host diff service path.
- Medium dependency risk: this adds `@pierre/diffs` in the plugin
package, but `pnpm-lock.yaml` is intentionally omitted per packaging
instructions because repository automation manages lockfile updates.
- Current CI blocker: downstream frozen installs fail until the
repository policy path for new plugin package dependencies is chosen.
- Diff rendering edge cases are covered for common working-tree and head
diff states, but very large repositories may still expose performance
limits.
- No migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 class coding model, tool-enabled local execution
environment. Exact context window was not exposed by the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-18 08:50:06 -05:00
|
|
|
import { workspaceDiffQuerySchema } from "./contracts.js";
|
|
|
|
|
import { workspaceDiffService } from "./workspace-diff.js";
|
|
|
|
|
|
|
|
|
|
const PLUGIN_NAME = "workspace-diff";
|
|
|
|
|
|
|
|
|
|
function readString(value: unknown): string {
|
|
|
|
|
return typeof value === "string" ? value.trim() : "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readOptionalString(value: unknown): string | null {
|
|
|
|
|
const trimmed = readString(value);
|
|
|
|
|
return trimmed || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function resolveDefaultBaseRef(input: {
|
|
|
|
|
workspaceBaseRef?: unknown;
|
|
|
|
|
projectWorkspaceDefaultRef?: unknown;
|
|
|
|
|
projectWorkspaceRepoRef?: unknown;
|
|
|
|
|
}): string | null {
|
|
|
|
|
return readOptionalString(input.workspaceBaseRef)
|
|
|
|
|
?? readOptionalString(input.projectWorkspaceDefaultRef)
|
|
|
|
|
?? readOptionalString(input.projectWorkspaceRepoRef);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 15:51:13 -05:00
|
|
|
async function resolveProjectWorkspaceDefaultBaseRef(input: {
|
|
|
|
|
ctx: PluginContext;
|
|
|
|
|
projectId: string;
|
|
|
|
|
companyId: string;
|
|
|
|
|
projectWorkspaceId?: string | null;
|
|
|
|
|
}): Promise<string | null> {
|
|
|
|
|
if (!input.projectId) return null;
|
|
|
|
|
const workspaces = await input.ctx.projects.listWorkspaces(input.projectId, input.companyId);
|
|
|
|
|
const projectWorkspace = input.projectWorkspaceId
|
|
|
|
|
? workspaces.find((candidate) => candidate.id === input.projectWorkspaceId)
|
|
|
|
|
: workspaces.find((candidate) => candidate.isPrimary) ?? workspaces[0] ?? null;
|
|
|
|
|
return projectWorkspace
|
|
|
|
|
? resolveDefaultBaseRef({
|
|
|
|
|
projectWorkspaceDefaultRef: projectWorkspace.defaultRef,
|
|
|
|
|
projectWorkspaceRepoRef: projectWorkspace.repoRef,
|
|
|
|
|
})
|
|
|
|
|
: null;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add workspace diff viewer plugin (#6071)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators need to inspect what agents changed inside execution and
project workspaces.
> - The existing workspace detail views did not provide a first-party
rich diff surface for staged, unstaged, head, renamed, binary,
oversized, and untracked changes.
> - The plugin system is the intended extension point for optional rich
UI surfaces.
> - This pull request adds a workspace diff plugin plus host services
and shared contracts so Changes tabs can render workspace diffs through
plugin slots.
> - The diff-renderer dependency should stay owned by the plugin package
rather than the core UI app.
> - The dependency surface must stay aligned with repository PR policy,
including intentionally omitting `pnpm-lock.yaml` from the PR.
> - The benefit is a more reviewable workspace surface without
hard-coding the renderer into every page.
## What Changed
- Added `@paperclipai/plugin-workspace-diff`, including diff
normalization, plugin manifest/worker/UI entrypoints, and focused plugin
tests.
- Kept `@pierre/diffs` scoped to `@paperclipai/plugin-workspace-diff`;
removed the core UI lab diff-renderer surface and direct UI package
dependency.
- Added shared workspace diff types and validators, plus plugin SDK
surface for workspace diff host services.
- Added server workspace diff service support and route coverage for
execution/project workspace diff flows.
- Wired Execution Workspace and Project Workspace Changes tabs to load
the diff plugin, including loading/error fallback behavior.
- Added UI tests and fixtures for the Changes tabs and plugin bridge
behavior.
- Added the new plugin package manifest to the Docker deps stage so PR
policy can validate dependency coverage.
- Addressed review hardening around empty untracked patches, workspace
path exposure, project workspace read capability checks, and default
base refs.
## Verification
- `pnpm --filter @paperclipai/plugin-workspace-diff test`
- `pnpm exec vitest run
packages/shared/src/validators/workspace-diff.test.ts
server/src/__tests__/workspace-diff-service.test.ts
ui/src/pages/ProjectWorkspaceDetail.test.tsx
ui/src/pages/ExecutionWorkspaceDetail.test.tsx`
- `pnpm exec vitest run ui/src/plugins/bridge.test.ts
server/src/__tests__/workspace-runtime-routes-authz.test.ts`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/plugin-workspace-diff typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `node ./scripts/check-docker-deps-stage.mjs`
- Browser screenshot captured from the local worktree dev server:
https://files.catbox.moe/ofdpsp.png
- Confirmed branch is rebased onto `public-gh/master`,
`.github/workflows/pr.yml` is not included in the PR diff,
`ui/package.json` is not included in the PR diff, and `pnpm-lock.yaml`
is not included in the PR diff.
## Risks
- Medium UI integration risk: the Changes tab depends on the plugin slot
and host diff service path.
- Medium dependency risk: this adds `@pierre/diffs` in the plugin
package, but `pnpm-lock.yaml` is intentionally omitted per packaging
instructions because repository automation manages lockfile updates.
- Current CI blocker: downstream frozen installs fail until the
repository policy path for new plugin package dependencies is chosen.
- Diff rendering edge cases are covered for common working-tree and head
diff states, but very large repositories may still expose performance
limits.
- No migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 class coding model, tool-enabled local execution
environment. Exact context window was not exposed by the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-18 08:50:06 -05:00
|
|
|
const plugin = definePlugin({
|
|
|
|
|
async setup(ctx) {
|
|
|
|
|
ctx.logger.info(`${PLUGIN_NAME} plugin setup`);
|
|
|
|
|
const workspaceDiff = workspaceDiffService();
|
|
|
|
|
|
|
|
|
|
ctx.data.register("workspace-diff", async (params: Record<string, unknown>) => {
|
|
|
|
|
const workspaceId = readString(params.workspaceId);
|
|
|
|
|
const companyId = readString(params.companyId);
|
|
|
|
|
if (!workspaceId || !companyId) {
|
|
|
|
|
throw new Error("workspaceId and companyId are required");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (params.entityType === "project_workspace") {
|
|
|
|
|
const projectId = readString(params.projectId);
|
|
|
|
|
if (!projectId) {
|
|
|
|
|
throw new Error("projectId is required for project workspace diffs");
|
|
|
|
|
}
|
|
|
|
|
const workspaces = await ctx.projects.listWorkspaces(projectId, companyId);
|
|
|
|
|
const workspace = workspaces.find((candidate) => candidate.id === workspaceId);
|
|
|
|
|
if (!workspace) {
|
|
|
|
|
throw new Error("Workspace not found");
|
|
|
|
|
}
|
|
|
|
|
return workspaceDiff.getDiff({
|
|
|
|
|
id: workspace.id,
|
|
|
|
|
companyId,
|
|
|
|
|
cwd: workspace.path,
|
|
|
|
|
baseRef: resolveDefaultBaseRef({
|
|
|
|
|
projectWorkspaceDefaultRef: workspace.defaultRef,
|
|
|
|
|
projectWorkspaceRepoRef: workspace.repoRef,
|
|
|
|
|
}),
|
|
|
|
|
}, workspaceDiffQuerySchema.parse(params));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const workspace = await ctx.executionWorkspaces.get(workspaceId, companyId);
|
|
|
|
|
if (!workspace) {
|
|
|
|
|
throw new Error("Workspace not found");
|
|
|
|
|
}
|
|
|
|
|
let projectWorkspaceDefaultBaseRef: string | null = null;
|
2026-05-19 15:51:13 -05:00
|
|
|
if (!readOptionalString(workspace.baseRef)) {
|
|
|
|
|
projectWorkspaceDefaultBaseRef = await resolveProjectWorkspaceDefaultBaseRef({
|
|
|
|
|
ctx,
|
|
|
|
|
projectId: workspace.projectId || readString(params.projectId),
|
|
|
|
|
companyId,
|
|
|
|
|
projectWorkspaceId: workspace.projectWorkspaceId,
|
|
|
|
|
});
|
[codex] Add workspace diff viewer plugin (#6071)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators need to inspect what agents changed inside execution and
project workspaces.
> - The existing workspace detail views did not provide a first-party
rich diff surface for staged, unstaged, head, renamed, binary,
oversized, and untracked changes.
> - The plugin system is the intended extension point for optional rich
UI surfaces.
> - This pull request adds a workspace diff plugin plus host services
and shared contracts so Changes tabs can render workspace diffs through
plugin slots.
> - The diff-renderer dependency should stay owned by the plugin package
rather than the core UI app.
> - The dependency surface must stay aligned with repository PR policy,
including intentionally omitting `pnpm-lock.yaml` from the PR.
> - The benefit is a more reviewable workspace surface without
hard-coding the renderer into every page.
## What Changed
- Added `@paperclipai/plugin-workspace-diff`, including diff
normalization, plugin manifest/worker/UI entrypoints, and focused plugin
tests.
- Kept `@pierre/diffs` scoped to `@paperclipai/plugin-workspace-diff`;
removed the core UI lab diff-renderer surface and direct UI package
dependency.
- Added shared workspace diff types and validators, plus plugin SDK
surface for workspace diff host services.
- Added server workspace diff service support and route coverage for
execution/project workspace diff flows.
- Wired Execution Workspace and Project Workspace Changes tabs to load
the diff plugin, including loading/error fallback behavior.
- Added UI tests and fixtures for the Changes tabs and plugin bridge
behavior.
- Added the new plugin package manifest to the Docker deps stage so PR
policy can validate dependency coverage.
- Addressed review hardening around empty untracked patches, workspace
path exposure, project workspace read capability checks, and default
base refs.
## Verification
- `pnpm --filter @paperclipai/plugin-workspace-diff test`
- `pnpm exec vitest run
packages/shared/src/validators/workspace-diff.test.ts
server/src/__tests__/workspace-diff-service.test.ts
ui/src/pages/ProjectWorkspaceDetail.test.tsx
ui/src/pages/ExecutionWorkspaceDetail.test.tsx`
- `pnpm exec vitest run ui/src/plugins/bridge.test.ts
server/src/__tests__/workspace-runtime-routes-authz.test.ts`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/plugin-workspace-diff typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `node ./scripts/check-docker-deps-stage.mjs`
- Browser screenshot captured from the local worktree dev server:
https://files.catbox.moe/ofdpsp.png
- Confirmed branch is rebased onto `public-gh/master`,
`.github/workflows/pr.yml` is not included in the PR diff,
`ui/package.json` is not included in the PR diff, and `pnpm-lock.yaml`
is not included in the PR diff.
## Risks
- Medium UI integration risk: the Changes tab depends on the plugin slot
and host diff service path.
- Medium dependency risk: this adds `@pierre/diffs` in the plugin
package, but `pnpm-lock.yaml` is intentionally omitted per packaging
instructions because repository automation manages lockfile updates.
- Current CI blocker: downstream frozen installs fail until the
repository policy path for new plugin package dependencies is chosen.
- Diff rendering edge cases are covered for common working-tree and head
diff states, but very large repositories may still expose performance
limits.
- No migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 class coding model, tool-enabled local execution
environment. Exact context window was not exposed by the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-18 08:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return workspaceDiff.getDiff({
|
|
|
|
|
...workspace,
|
|
|
|
|
baseRef: resolveDefaultBaseRef({
|
|
|
|
|
workspaceBaseRef: workspace.baseRef,
|
|
|
|
|
projectWorkspaceDefaultRef: projectWorkspaceDefaultBaseRef,
|
|
|
|
|
}),
|
|
|
|
|
}, workspaceDiffQuerySchema.parse(params));
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async onHealth() {
|
|
|
|
|
return { status: "ok", message: `${PLUGIN_NAME} ready` };
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default plugin;
|
|
|
|
|
runWorker(plugin, import.meta.url);
|