[codex] Show bundled plugins in plugin manager (#6734)

## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is how Paperclip exposes optional capabilities and
integrations without bloating the control plane.
> - Operators need the Instance Settings plugin manager to show both
installed external plugins and bundled built-in plugins.
> - Bundled plugins were available in the server/UI surface but were not
represented consistently in the plugin manager list.
> - Workspace runtime reuse also needed to stay pinned to the current
branch/base so the plugin manager can be validated from the intended
checkout.
> - This pull request shows bundled plugins in the manager, marks
experimental bundled plugins clearly, and tightens runtime/worktree
reuse guards.
> - The benefit is that operators can discover bundled plugins from the
same management screen as installed plugins without stale workspace
sessions hiding the latest branch state.

## What Changed

- Lists bundled monorepo plugin packages through the plugin routes API,
including plugin status and install metadata needed by the UI.
- Updates the plugin manager UI/API client to render bundled plugins and
display experimental badges based on installed plugin records.
- Adds server authorization coverage around plugin routes so board and
agent access stay company-scoped.
- Guards execution workspace/runtime reuse against stale base refs and
defaults new worktrees to the fetched target base.
- Expands workspace runtime tests for service reuse, stale workspace
prevention, and controlled runtime stops.
- Addressed Greptile feedback by respecting `origin/HEAD`, using async
cached bundled-plugin discovery, and avoiding duplicated UI experimental
plugin lists.

## Verification

- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/plugin-sdk build && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `gh pr checks 6734 --repo paperclipai/paperclip` reports all checks
passing on `10e1ba9e0f505637cd913713fb28c2c99ae92011`.
- Greptile Review reports 5/5 on
`10e1ba9e0f505637cd913713fb28c2c99ae92011`.
- Confirmed the branch is rebased onto `public-gh/master` and the PR
diff does not include `pnpm-lock.yaml` or `.github/workflows` changes.
- UI screenshots were not captured in this PR-creation pass because the
available local board runtime is authenticated; the visible UI path is
covered by the plugin manager code changes and server/API tests above.

## Risks

- Medium risk: this touches shared plugin listing behavior and workspace
runtime reuse, so regressions could affect plugin manager visibility or
service reuse across execution workspaces.
- No database migrations.
- No lockfile or GitHub workflow changes.

> 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 GPT-5 Codex, coding-agent workflow with shell/tool use in a
local Paperclip worktree. Context window not surfaced by the runtime;
reasoning mode not externally reported.

## 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
- [ ] 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>
This commit is contained in:
Dotta 2026-05-26 08:32:45 -05:00 committed by GitHub
parent 9aea3e3d35
commit f0ddd24d61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 609 additions and 93 deletions

View file

@ -87,6 +87,7 @@ import { logActivity, publishPluginDomainEvent, type LogActivityInput } from "./
import {
buildWorkspaceReadyComment,
cleanupExecutionWorkspaceArtifacts,
ensurePersistedExecutionWorkspaceAvailable,
ensureRuntimeServicesForRun,
persistAdapterManagedRuntimeServices,
realizeExecutionWorkspace,
@ -594,6 +595,8 @@ export function mergeExecutionWorkspaceMetadataForPersistence(input: {
createdByRuntime: boolean;
configSnapshot: Record<string, unknown> | null;
shouldReuseExisting: boolean;
baseRef: string | null | undefined;
baseRefSha: string | null | undefined;
}) {
const base = {
...(input.existingMetadata ?? {}),
@ -601,6 +604,17 @@ export function mergeExecutionWorkspaceMetadataForPersistence(input: {
createdByRuntime: input.createdByRuntime,
} as Record<string, unknown>;
const existingSnapshot = parseObject(base.baseRefSnapshot);
if (
typeof existingSnapshot.resolvedSha !== "string"
&& input.baseRefSha
) {
base.baseRefSnapshot = {
baseRef: input.baseRef ?? null,
resolvedSha: input.baseRefSha,
};
}
if (input.shouldReuseExisting || !input.configSnapshot) {
return base;
}
@ -624,6 +638,8 @@ export function buildRealizedExecutionWorkspaceFromPersisted(input: {
}
const strategy = input.workspace.strategyType === "git_worktree" ? "git_worktree" : "project_primary";
const baseRefSnapshot = parseObject(input.workspace.metadata?.baseRefSnapshot);
const baseRefSha = typeof baseRefSnapshot.resolvedSha === "string" ? baseRefSnapshot.resolvedSha : null;
return {
baseCwd: input.base.baseCwd,
source: input.workspace.mode === "shared_workspace" ? "project_primary" : "task_session",
@ -637,6 +653,7 @@ export function buildRealizedExecutionWorkspaceFromPersisted(input: {
worktreePath: strategy === "git_worktree" ? (readNonEmptyString(input.workspace.providerRef) ?? cwd) : null,
warnings: [],
created: false,
baseRefSha,
};
}
@ -7229,7 +7246,34 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
repoRef: resolvedWorkspace.repoRef,
} satisfies ExecutionWorkspaceInput;
const reusedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace
? buildRealizedExecutionWorkspaceFromPersisted({
? await ensurePersistedExecutionWorkspaceAvailable({
base: executionWorkspaceBase,
workspace: {
mode: existingExecutionWorkspace.mode,
strategyType: existingExecutionWorkspace.strategyType,
cwd: existingExecutionWorkspace.cwd,
providerRef: existingExecutionWorkspace.providerRef,
projectId: existingExecutionWorkspace.projectId,
projectWorkspaceId: existingExecutionWorkspace.projectWorkspaceId,
repoUrl: existingExecutionWorkspace.repoUrl,
baseRef: existingExecutionWorkspace.baseRef,
branchName: existingExecutionWorkspace.branchName,
metadata: existingExecutionWorkspace.metadata as Record<string, unknown> | null,
config: {
provisionCommand:
existingExecutionWorkspace.config?.provisionCommand
?? projectExecutionWorkspacePolicy?.workspaceStrategy?.provisionCommand
?? null,
},
},
issue: issueRef,
agent: {
id: agent.id,
name: agent.name,
companyId: agent.companyId,
},
recorder: workspaceOperationRecorder,
}) ?? buildRealizedExecutionWorkspaceFromPersisted({
base: executionWorkspaceBase,
workspace: existingExecutionWorkspace,
})
@ -7254,6 +7298,8 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
createdByRuntime: executionWorkspace.created,
configSnapshot,
shouldReuseExisting,
baseRef: executionWorkspace.repoRef,
baseRefSha: executionWorkspace.baseRefSha ?? null,
});
try {
persistedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace