2026-03-13 16:22:34 -05:00
|
|
|
/**
|
|
|
|
|
* @fileoverview Plugin management REST API routes
|
|
|
|
|
*
|
|
|
|
|
* This module provides Express routes for managing the complete plugin lifecycle:
|
|
|
|
|
* - Listing and filtering plugins by status
|
|
|
|
|
* - Installing plugins from npm or local paths
|
|
|
|
|
* - Uninstalling plugins (soft delete or hard purge)
|
|
|
|
|
* - Enabling/disabling plugins
|
|
|
|
|
* - Running health diagnostics
|
|
|
|
|
* - Upgrading plugins
|
|
|
|
|
* - Retrieving UI slot contributions for frontend rendering
|
|
|
|
|
* - Discovering and executing plugin-contributed agent tools
|
|
|
|
|
*
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [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-04-15 08:41:15 -05:00
|
|
|
* All routes require board-level authentication, and sensitive instance-wide
|
|
|
|
|
* mutations such as install/upgrade require instance-admin privileges.
|
2026-03-13 16:22:34 -05:00
|
|
|
*
|
|
|
|
|
* @module server/routes/plugins
|
|
|
|
|
* @see doc/plugins/PLUGIN_SPEC.md for the full plugin specification
|
|
|
|
|
*/
|
|
|
|
|
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
import { access, readdir, readFile } from "node:fs/promises";
|
2026-03-13 16:22:34 -05:00
|
|
|
import path from "node:path";
|
|
|
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
|
import { Router } from "express";
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
import type { Request, Response } from "express";
|
2026-03-13 16:22:34 -05:00
|
|
|
import { and, desc, eq, gte } from "drizzle-orm";
|
|
|
|
|
import type { Db } from "@paperclipai/db";
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
import {
|
|
|
|
|
agents,
|
|
|
|
|
companies,
|
|
|
|
|
heartbeatRuns,
|
|
|
|
|
pluginLogs,
|
|
|
|
|
pluginWebhookDeliveries,
|
|
|
|
|
projects,
|
|
|
|
|
} from "@paperclipai/db";
|
2026-03-13 16:22:34 -05:00
|
|
|
import type {
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
PluginApiRouteDeclaration,
|
2026-03-13 16:22:34 -05:00
|
|
|
PluginStatus,
|
|
|
|
|
PaperclipPluginManifestV1,
|
|
|
|
|
PluginBridgeErrorCode,
|
|
|
|
|
PluginLauncherRenderContextSnapshot,
|
|
|
|
|
} from "@paperclipai/shared";
|
|
|
|
|
import {
|
|
|
|
|
PLUGIN_STATUSES,
|
|
|
|
|
} from "@paperclipai/shared";
|
|
|
|
|
import { pluginRegistryService } from "../services/plugin-registry.js";
|
|
|
|
|
import { pluginLifecycleManager } from "../services/plugin-lifecycle.js";
|
|
|
|
|
import { getPluginUiContributionMetadata, pluginLoader } from "../services/plugin-loader.js";
|
|
|
|
|
import { logActivity } from "../services/activity-log.js";
|
|
|
|
|
import { publishGlobalLiveEvent } from "../services/live-events.js";
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
import { issueService } from "../services/issues.js";
|
2026-03-13 16:22:34 -05:00
|
|
|
import type { PluginJobScheduler } from "../services/plugin-job-scheduler.js";
|
|
|
|
|
import type { PluginJobStore } from "../services/plugin-job-store.js";
|
|
|
|
|
import type { PluginWorkerManager } from "../services/plugin-worker-manager.js";
|
|
|
|
|
import type { PluginStreamBus } from "../services/plugin-stream-bus.js";
|
|
|
|
|
import type { PluginToolDispatcher } from "../services/plugin-tool-dispatcher.js";
|
2026-05-22 09:16:24 -05:00
|
|
|
import type { PluginPerformActionActorContext, ToolRunContext } from "@paperclipai/plugin-sdk";
|
2026-03-13 16:22:34 -05:00
|
|
|
import { JsonRpcCallError, PLUGIN_RPC_ERROR_CODES } from "@paperclipai/plugin-sdk";
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
import {
|
|
|
|
|
assertAuthenticated,
|
|
|
|
|
assertBoard,
|
|
|
|
|
assertBoardOrgAccess,
|
|
|
|
|
assertCompanyAccess,
|
|
|
|
|
assertInstanceAdmin,
|
|
|
|
|
getActorInfo,
|
|
|
|
|
} from "./authz.js";
|
2026-03-13 16:22:34 -05:00
|
|
|
import { validateInstanceConfig } from "../services/plugin-config-validator.js";
|
Expand plugin host surface (#5205)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The plugin system is the extension boundary for optional product
capabilities
> - Rich plugins need more than a worker entrypoint: they need scoped
database storage, local project folders, managed agents/routines, host
navigation, and reusable UI components
> - The LLM Wiki work exposed those missing host surfaces while keeping
plugin code outside the core control plane
> - This pull request expands the core plugin host, SDK, server APIs,
and UI bridge so plugins can declare and use those surfaces
> - The benefit is that future plugins can integrate with Paperclip
through documented, validated contracts instead of bespoke server or UI
imports
## What Changed
- Added plugin-managed database namespaces and migration tracking,
including Drizzle schema/migration files and SQL validation for
namespace isolation.
- Added server support for plugin local folders, managed agents, managed
routines, scoped plugin APIs, and plugin operation visibility.
- Expanded shared plugin manifest/types/validators and SDK
host/testing/UI exports for richer plugin surfaces.
- Added reusable UI pieces for file trees, managed routines, resizable
sidebars, route sidebars, and plugin bridge initialization.
- Updated plugin docs and example plugins to use the expanded host and
SDK surface.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
packages/shared/src/validators/plugin.test.ts
server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-local-folders.test.ts
server/src/__tests__/plugin-managed-agents.test.ts
server/src/__tests__/plugin-managed-routines.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
ui/src/api/plugins.test.ts ui/src/components/FileTree.test.tsx
ui/src/components/ResizableSidebarPane.test.tsx
ui/src/pages/PluginPage.test.tsx ui/src/plugins/bridge.test.ts` passed:
11 files, 67 tests.
- Confirmed this PR changes 89 files and does not include
`pnpm-lock.yaml` or `.github/workflows/*`.
## Risks
- Medium: this expands plugin host contracts across db/shared/server/ui
and includes a new core migration (`0076_useful_elektra.sql`).
- The plugin database namespace validator is intentionally restrictive;
plugin authors may need follow-up affordances for SQL patterns that
remain blocked.
- Merge this before the LLM Wiki plugin PR so the plugin can resolve the
new SDK and host APIs.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window size 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-05 07:42:57 -05:00
|
|
|
import {
|
|
|
|
|
findLocalFolderDeclaration,
|
|
|
|
|
getStoredLocalFolders,
|
|
|
|
|
inspectPluginLocalFolder,
|
|
|
|
|
requireLocalFolderDeclaration,
|
|
|
|
|
setStoredLocalFolder,
|
|
|
|
|
} from "../services/plugin-local-folders.js";
|
Add secrets provider vaults and remote import (#5429)
## Thinking Path
> - Paperclip orchestrates AI-agent companies and needs secrets handling
to work across local development, hosted operators, and governed agent
execution.
> - The affected subsystem is the company-scoped secrets control plane:
database schema, server services/routes, CLI workflows, and the Secrets
settings UI.
> - The gap was that secrets were local-only and operators could not
manage provider vaults or import existing remote references without
exposing plaintext.
> - This branch adds provider vault configuration plus an AWS Secrets
Manager remote-import path while preserving company boundaries, binding
context, and audit trails.
> - I kept the PR to a single branch PR, removed unrelated
lockfile/package drift, rebased the full branch onto the current
`public-gh/master`, and addressed fresh Greptile findings.
> - The benefit is a reviewable implementation of provider-backed
secrets with focused tests covering provider selection, import
conflicts, deleted secret reuse, rotation guards, and AWS signing
behavior.
## What Changed
- Added provider vault support for company secrets, including provider
config storage, default vault handling, health checks, binding usage,
access events, and remote import preview/commit.
- Added an AWS Secrets Manager provider using SigV4 request signing,
bounded request timeouts, namespace guardrails, cached runtime
credential resolution, and external-reference linking without plaintext
reads.
- Added Secrets UI surfaces for vault management and remote import, plus
CLI/API documentation for setup and operations.
- Stabilized routine webhook secret binding paths and SSH
environment-driver fixture bindings discovered during verification.
- Addressed Greptile and CI findings: no lockfile/package drift,
monotonic migration metadata, disabled-vault default races, soft-deleted
secret hiding/recreate behavior, remove behavior with disabled vaults,
soft-deleted external-reference re-import, non-active rotation guards,
managed-secret soft deletion through PATCH, and per-call AWS SDK
credential client churn.
- Rebased this branch onto `public-gh/master` at `0e1a5828` and
force-pushed with lease to keep this as the single PR for the branch.
## Verification
- `git fetch public-gh master`
- `git rebase public-gh/master`
- `git diff --name-only public-gh/master...HEAD | grep
'^pnpm-lock\.yaml$' || true` confirmed `pnpm-lock.yaml` is not in the PR
diff.
- Confirmed migration ordering: master ends at `0081_optimal_dormammu`;
this PR adds `0082_dry_vision` and
`0083_company_secret_provider_configs`.
- Inspected migrations for repeat safety: new tables/indexes use `IF NOT
EXISTS`; foreign keys are guarded by `DO $$ ... IF NOT EXISTS`; column
additions use `ADD COLUMN IF NOT EXISTS`.
- `pnpm -r typecheck` passed before the Greptile follow-up commits.
- `pnpm test:run` ran the full stable Vitest path before the Greptile
follow-up commits; it completed with 3 timing-related failures under
parallel load: `codex-local-execute.test.ts`,
`cursor-local-execute.test.ts`, and `environment-service.test.ts`.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/codex-local-execute.test.ts
src/__tests__/cursor-local-execute.test.ts
src/__tests__/environment-service.test.ts` passed on targeted rerun
(`24/24`).
- `pnpm build` passed before the Greptile follow-up commits. Vite
reported existing chunk-size/dynamic-import warnings.
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/secrets-service.test.ts` passed (`26/26`).
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/aws-secrets-manager-provider.test.ts
src/__tests__/secrets-service.test.ts` passed (`39/39`).
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
typecheck` passed.
- Captured Storybook screenshots from `ui/storybook-static` for visual
review.
- Latest PR checks on `5ca3a5cf`: `policy`, serialized server suites
1/4-4/4, `Canary Dry Run`, `e2e`, `security/snyk`, and `Greptile Review`
pass; aggregate `verify` is still registering the completed child
checks.
- Greptile review loop continued through the latest requested pass; all
Greptile review threads are resolved and the latest `Greptile Review`
check on `5ca3a5cf` passed with 0 comments added.
## Screenshots
Before: the provider-vault and remote-import surfaces did not exist on
`master`; these are after-state screenshots from the Storybook fixtures.



## Risks
- Migration risk: this adds new secret provider tables and extends
existing secret rows. The migrations were checked for monotonic ordering
and idempotent guards, but reviewers should still inspect upgrade
behavior carefully.
- Provider risk: AWS support uses direct SigV4 requests. Automated tests
cover signing, request timeouts, vault-config selection, namespace
guardrails, pending-version archival, sanitized provider errors, and
service-level cleanup paths. A real-vault AWS smoke test remains
deployment validation for an operator with AWS credentials rather than
an unverified merge blocker in this local branch.
- UI risk: the Secrets page and import dialog are large new surfaces;
screenshots are included above for reviewer inspection.
- Verification risk: the full local stable test command hit
parallel-load timing failures, although the exact failed files passed
when rerun directly.
- Operational risk: remote import intentionally avoids plaintext reads;
operators must understand that imported external references resolve at
runtime and may fail if AWS permissions change.
> 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 coding agent with local shell/tool use in the
Paperclip worktree. Exact context-window size 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
- [ ] 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>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:22:17 -05:00
|
|
|
import {
|
|
|
|
|
extractSecretRefPathsFromConfig,
|
|
|
|
|
PLUGIN_SECRET_REFS_DISABLED_MESSAGE,
|
|
|
|
|
} from "../services/plugin-secrets-handler.js";
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
import { badRequest, forbidden, notFound, unauthorized, unprocessable } from "../errors.js";
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
/** UI slot declaration extracted from plugin manifest */
|
|
|
|
|
type PluginUiSlotDeclaration = NonNullable<NonNullable<PaperclipPluginManifestV1["ui"]>["slots"]>[number];
|
|
|
|
|
/** Launcher declaration extracted from plugin manifest */
|
|
|
|
|
type PluginLauncherDeclaration = NonNullable<PaperclipPluginManifestV1["launchers"]>[number];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalized UI contribution for frontend slot host consumption.
|
|
|
|
|
* Only includes plugins in 'ready' state with non-empty slot declarations.
|
|
|
|
|
*/
|
|
|
|
|
type PluginUiContribution = {
|
|
|
|
|
pluginId: string;
|
|
|
|
|
pluginKey: string;
|
|
|
|
|
displayName: string;
|
|
|
|
|
version: string;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
/**
|
|
|
|
|
* Relative path within the plugin's UI directory to the entry module
|
|
|
|
|
* (e.g. `"index.js"`). The frontend constructs the full import URL as
|
|
|
|
|
* `/_plugins/${pluginId}/ui/${uiEntryFile}`.
|
|
|
|
|
*/
|
|
|
|
|
uiEntryFile: string;
|
|
|
|
|
slots: PluginUiSlotDeclaration[];
|
|
|
|
|
launchers: PluginLauncherDeclaration[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Request body for POST /api/plugins/install */
|
|
|
|
|
interface PluginInstallRequest {
|
|
|
|
|
/** npm package name (e.g., @paperclip/plugin-linear) or local path */
|
|
|
|
|
packageName: string;
|
|
|
|
|
/** Target version for npm packages (optional, defaults to latest) */
|
|
|
|
|
version?: string;
|
|
|
|
|
/** True if packageName is a local filesystem path */
|
|
|
|
|
isLocalPath?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
interface AvailableBundledPlugin {
|
2026-03-13 16:22:34 -05:00
|
|
|
packageName: string;
|
|
|
|
|
pluginKey: string;
|
|
|
|
|
displayName: string;
|
|
|
|
|
description: string;
|
|
|
|
|
localPath: string;
|
[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
|
|
|
tag: "example" | "first-party";
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
experimental: boolean;
|
2026-03-13 16:22:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Response body for GET /api/plugins/:pluginId/health */
|
|
|
|
|
interface PluginHealthCheckResult {
|
|
|
|
|
pluginId: string;
|
|
|
|
|
status: string;
|
|
|
|
|
healthy: boolean;
|
|
|
|
|
checks: Array<{
|
|
|
|
|
name: string;
|
|
|
|
|
passed: boolean;
|
|
|
|
|
message?: string;
|
|
|
|
|
}>;
|
|
|
|
|
lastError?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** UUID v4 regex used for plugin ID route resolution. */
|
|
|
|
|
const UUID_REGEX =
|
|
|
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
|
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
const PLUGIN_API_BODY_LIMIT_BYTES = 1_000_000;
|
|
|
|
|
const PLUGIN_SCOPED_API_RESPONSE_HEADER_ALLOWLIST = new Set([
|
|
|
|
|
"cache-control",
|
|
|
|
|
"etag",
|
|
|
|
|
"last-modified",
|
|
|
|
|
"x-request-id",
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
const REPO_ROOT = path.resolve(__dirname, "../../..");
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
const EXPERIMENTAL_BUNDLED_PLUGIN_PACKAGE_NAMES = new Set([
|
|
|
|
|
"@paperclipai/plugin-llm-wiki",
|
|
|
|
|
"@paperclipai/plugin-modal",
|
|
|
|
|
"@paperclipai/plugin-workspace-diff",
|
|
|
|
|
]);
|
|
|
|
|
let bundledPluginsCache: Promise<AvailableBundledPlugin[]> | null = null;
|
|
|
|
|
|
|
|
|
|
function titleCasePluginName(packageName: string): string {
|
|
|
|
|
const localName = packageName.split("/").pop() ?? packageName;
|
|
|
|
|
return localName
|
|
|
|
|
.replace(/^paperclip-plugin-/, "")
|
|
|
|
|
.replace(/^plugin-/, "")
|
|
|
|
|
.split("-")
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
|
|
|
.join(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fileExists(filePath: string): Promise<boolean> {
|
|
|
|
|
return access(filePath).then(() => true, () => false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(await readFile(filePath, "utf8")) as Record<string, unknown>;
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function findPackageJsonFiles(root: string, maxDepth = 4): Promise<string[]> {
|
|
|
|
|
if (!(await fileExists(root))) return [];
|
|
|
|
|
|
|
|
|
|
const packageJsonFiles: string[] = [];
|
|
|
|
|
const walk = async (dir: string, depth: number): Promise<void> => {
|
|
|
|
|
if (depth > maxDepth) return;
|
|
|
|
|
|
|
|
|
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
|
|
|
const entryPath = path.join(dir, entry.name);
|
|
|
|
|
|
|
|
|
|
if (entry.isFile() && entry.name === "package.json") {
|
|
|
|
|
packageJsonFiles.push(entryPath);
|
|
|
|
|
} else if (entry.isDirectory()) {
|
|
|
|
|
await walk(entryPath, depth + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await walk(root, 0);
|
|
|
|
|
return packageJsonFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function manifestSourcePath(packageRoot: string, pkgJson: Record<string, unknown>): string | null {
|
|
|
|
|
const paperclipPlugin = pkgJson.paperclipPlugin;
|
|
|
|
|
if (
|
|
|
|
|
!paperclipPlugin
|
|
|
|
|
|| typeof paperclipPlugin !== "object"
|
|
|
|
|
|| Array.isArray(paperclipPlugin)
|
|
|
|
|
) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const manifestPath = (paperclipPlugin as Record<string, unknown>).manifest;
|
|
|
|
|
if (typeof manifestPath !== "string") return null;
|
|
|
|
|
|
|
|
|
|
const sourcePath = manifestPath
|
|
|
|
|
.replace(/^\.\/dist\//, "./src/")
|
|
|
|
|
.replace(/\.js$/, ".ts");
|
|
|
|
|
return path.resolve(packageRoot, sourcePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function firstStringLiteral(source: string, key: string): string | null {
|
|
|
|
|
const match = source.match(
|
|
|
|
|
new RegExp(`${key}:\\s*(?:"([^"]*)"|'([^']*)'|\`([^\`]*)\`)`, "s"),
|
|
|
|
|
);
|
|
|
|
|
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function bundledPluginMetadata(
|
|
|
|
|
packageRoot: string,
|
|
|
|
|
pkgJson: Record<string, unknown>,
|
|
|
|
|
): Promise<{ pluginKey?: string; displayName?: string; description?: string }> {
|
|
|
|
|
const sourcePath = manifestSourcePath(packageRoot, pkgJson);
|
|
|
|
|
if (!sourcePath || !(await fileExists(sourcePath))) return {};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const source = await readFile(sourcePath, "utf8");
|
|
|
|
|
const pluginId = source
|
|
|
|
|
.match(/(?:export\s+)?const\s+PLUGIN_ID\s*=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/)
|
|
|
|
|
?.slice(1)
|
|
|
|
|
.find(Boolean)
|
|
|
|
|
?? firstStringLiteral(source, "id")
|
|
|
|
|
?? null;
|
|
|
|
|
return {
|
|
|
|
|
pluginKey: pluginId ?? undefined,
|
|
|
|
|
displayName: firstStringLiteral(source, "displayName") ?? undefined,
|
|
|
|
|
description: firstStringLiteral(source, "description") ?? undefined,
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isExperimentalBundledPlugin(packageRoot: string, packageName: string): boolean {
|
|
|
|
|
return (
|
|
|
|
|
EXPERIMENTAL_BUNDLED_PLUGIN_PACKAGE_NAMES.has(packageName)
|
|
|
|
|
|| packageRoot.includes(`${path.sep}sandbox-providers${path.sep}`)
|
|
|
|
|
|| packageName.includes("sandbox")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function discoverBundledPlugins(): Promise<AvailableBundledPlugin[]> {
|
|
|
|
|
const pluginRoot = path.resolve(REPO_ROOT, "packages/plugins");
|
|
|
|
|
const bundledPlugins: AvailableBundledPlugin[] = [];
|
|
|
|
|
for (const packageJsonPath of await findPackageJsonFiles(pluginRoot)) {
|
|
|
|
|
const packageRoot = path.dirname(packageJsonPath);
|
|
|
|
|
const pkgJson = await readJsonFile(packageJsonPath);
|
|
|
|
|
const paperclipPlugin = pkgJson?.paperclipPlugin;
|
|
|
|
|
if (
|
|
|
|
|
!pkgJson
|
|
|
|
|
|| !paperclipPlugin
|
|
|
|
|
|| typeof paperclipPlugin !== "object"
|
|
|
|
|
|| Array.isArray(paperclipPlugin)
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const packageName = pkgJson.name;
|
|
|
|
|
if (typeof packageName !== "string" || packageName.length === 0) continue;
|
|
|
|
|
|
|
|
|
|
const metadata = await bundledPluginMetadata(packageRoot, pkgJson);
|
|
|
|
|
const tag = packageRoot.includes(`${path.sep}examples${path.sep}`) ? "example" : "first-party";
|
|
|
|
|
bundledPlugins.push({
|
|
|
|
|
packageName,
|
|
|
|
|
pluginKey: metadata.pluginKey ?? packageName,
|
|
|
|
|
displayName: metadata.displayName ?? titleCasePluginName(packageName),
|
|
|
|
|
description: metadata.description
|
|
|
|
|
?? `Bundled Paperclip plugin from ${path.relative(REPO_ROOT, packageRoot)}.`,
|
|
|
|
|
localPath: packageRoot,
|
|
|
|
|
tag,
|
|
|
|
|
experimental: isExperimentalBundledPlugin(packageRoot, packageName),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bundledPlugins.sort((left, right) => {
|
|
|
|
|
if (left.tag !== right.tag) return left.tag === "first-party" ? -1 : 1;
|
|
|
|
|
return left.displayName.localeCompare(right.displayName);
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-03-13 16:22:34 -05:00
|
|
|
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
async function listBundledPlugins(): Promise<AvailableBundledPlugin[]> {
|
|
|
|
|
bundledPluginsCache ??= discoverBundledPlugins().catch((error: unknown) => {
|
|
|
|
|
bundledPluginsCache = null;
|
|
|
|
|
throw error;
|
2026-03-13 16:22:34 -05:00
|
|
|
});
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
return bundledPluginsCache;
|
2026-03-13 16:22:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve a plugin by either database ID or plugin key.
|
|
|
|
|
*
|
|
|
|
|
* Lookup order:
|
|
|
|
|
* - UUID-like IDs: getById first, then getByKey.
|
[codex] Improve local plugin development workflow (#5821)
## Thinking Path
> - Paperclip is the control plane for autonomous AI-agent companies.
> - Plugins are the extension point for adding capabilities without
expanding the core product surface.
> - Local plugin development needed a tighter CLI-first loop so plugin
authors can scaffold, run, install, inspect, and reload plugins without
reaching into internal package paths.
> - The server plugin install path also needed local-path handling that
keeps plugin identity, dashboard routes, and development watchers
coherent.
> - This pull request adds the CLI scaffold/install workflow, fixes the
server and SDK edge cases that blocked that loop, and updates the
agent-facing plugin creation skill and docs.
> - The benefit is that contributors can develop plugins from local
folders with a documented, repeatable happy path.
## What Changed
- Added `paperclipai plugin init` coverage and CLI wiring for local
plugin scaffolding.
- Improved local plugin install handling, plugin key route resolution,
dashboard capability behavior, and dev watcher startup/reload behavior.
- Fixed plugin SDK worker entrypoint validation for symlinked package
layouts.
- Added targeted tests for plugin init, server plugin authz/watcher
behavior, SDK worker host validation, and the authoring smoke example.
- Added a short local plugin development guide and refreshed the plugin
authoring guide plus `paperclip-create-plugin` skill instructions.
## Verification
- `pnpm run preflight:workspace-links && pnpm --filter
@paperclipai/plugin-sdk build && pnpm --filter
@paperclipai/create-paperclip-plugin typecheck && pnpm --filter
paperclipai typecheck && pnpm --filter @paperclipai/plugin-sdk typecheck
&& pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run --project paperclipai
cli/src/__tests__/plugin-init.test.ts`
- `pnpm exec vitest run --project @paperclipai/plugin-sdk
packages/plugins/sdk/tests/worker-rpc-host.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-dev-watcher.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-routes-authz.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm --dir packages/plugins/examples/plugin-authoring-smoke-example
test`
- Confirmed `pnpm-lock.yaml` is not included in the PR diff.
## Risks
- Medium risk: this touches plugin install routing, CLI command
behavior, and the local development watcher.
- Local path plugin installs execute trusted local code by design; the
new docs call out that trust boundary.
- No database 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 coding agent, tool-enabled local shell and git
workflow, medium reasoning effort. Context window details were not
exposed in this 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
UI screenshots: not applicable; this PR changes CLI/server/plugin docs
and tests, not board UI rendering.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-12 17:38:24 -05:00
|
|
|
* - All non-UUID values: getByKey only, never getById. The persisted plugin
|
|
|
|
|
* ID column is a PostgreSQL UUID, so probing it with keys such as
|
|
|
|
|
* "acme.plugin" raises a database cast error before a key lookup can happen.
|
2026-03-13 16:22:34 -05:00
|
|
|
*
|
|
|
|
|
* @param registry - The plugin registry service instance
|
|
|
|
|
* @param pluginId - Either a database UUID or plugin key (manifest id)
|
|
|
|
|
* @returns Plugin record or null if not found
|
|
|
|
|
*/
|
|
|
|
|
async function resolvePlugin(
|
|
|
|
|
registry: ReturnType<typeof pluginRegistryService>,
|
|
|
|
|
pluginId: string,
|
|
|
|
|
) {
|
|
|
|
|
const isUuid = UUID_REGEX.test(pluginId);
|
|
|
|
|
|
[codex] Improve local plugin development workflow (#5821)
## Thinking Path
> - Paperclip is the control plane for autonomous AI-agent companies.
> - Plugins are the extension point for adding capabilities without
expanding the core product surface.
> - Local plugin development needed a tighter CLI-first loop so plugin
authors can scaffold, run, install, inspect, and reload plugins without
reaching into internal package paths.
> - The server plugin install path also needed local-path handling that
keeps plugin identity, dashboard routes, and development watchers
coherent.
> - This pull request adds the CLI scaffold/install workflow, fixes the
server and SDK edge cases that blocked that loop, and updates the
agent-facing plugin creation skill and docs.
> - The benefit is that contributors can develop plugins from local
folders with a documented, repeatable happy path.
## What Changed
- Added `paperclipai plugin init` coverage and CLI wiring for local
plugin scaffolding.
- Improved local plugin install handling, plugin key route resolution,
dashboard capability behavior, and dev watcher startup/reload behavior.
- Fixed plugin SDK worker entrypoint validation for symlinked package
layouts.
- Added targeted tests for plugin init, server plugin authz/watcher
behavior, SDK worker host validation, and the authoring smoke example.
- Added a short local plugin development guide and refreshed the plugin
authoring guide plus `paperclip-create-plugin` skill instructions.
## Verification
- `pnpm run preflight:workspace-links && pnpm --filter
@paperclipai/plugin-sdk build && pnpm --filter
@paperclipai/create-paperclip-plugin typecheck && pnpm --filter
paperclipai typecheck && pnpm --filter @paperclipai/plugin-sdk typecheck
&& pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run --project paperclipai
cli/src/__tests__/plugin-init.test.ts`
- `pnpm exec vitest run --project @paperclipai/plugin-sdk
packages/plugins/sdk/tests/worker-rpc-host.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-dev-watcher.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-routes-authz.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm --dir packages/plugins/examples/plugin-authoring-smoke-example
test`
- Confirmed `pnpm-lock.yaml` is not included in the PR diff.
## Risks
- Medium risk: this touches plugin install routing, CLI command
behavior, and the local development watcher.
- Local path plugin installs execute trusted local code by design; the
new docs call out that trust boundary.
- No database 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 coding agent, tool-enabled local shell and git
workflow, medium reasoning effort. Context window details were not
exposed in this 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
UI screenshots: not applicable; this PR changes CLI/server/plugin docs
and tests, not board UI rendering.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-12 17:38:24 -05:00
|
|
|
if (!isUuid) {
|
2026-03-13 16:22:34 -05:00
|
|
|
return registry.getByKey(pluginId);
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Improve local plugin development workflow (#5821)
## Thinking Path
> - Paperclip is the control plane for autonomous AI-agent companies.
> - Plugins are the extension point for adding capabilities without
expanding the core product surface.
> - Local plugin development needed a tighter CLI-first loop so plugin
authors can scaffold, run, install, inspect, and reload plugins without
reaching into internal package paths.
> - The server plugin install path also needed local-path handling that
keeps plugin identity, dashboard routes, and development watchers
coherent.
> - This pull request adds the CLI scaffold/install workflow, fixes the
server and SDK edge cases that blocked that loop, and updates the
agent-facing plugin creation skill and docs.
> - The benefit is that contributors can develop plugins from local
folders with a documented, repeatable happy path.
## What Changed
- Added `paperclipai plugin init` coverage and CLI wiring for local
plugin scaffolding.
- Improved local plugin install handling, plugin key route resolution,
dashboard capability behavior, and dev watcher startup/reload behavior.
- Fixed plugin SDK worker entrypoint validation for symlinked package
layouts.
- Added targeted tests for plugin init, server plugin authz/watcher
behavior, SDK worker host validation, and the authoring smoke example.
- Added a short local plugin development guide and refreshed the plugin
authoring guide plus `paperclip-create-plugin` skill instructions.
## Verification
- `pnpm run preflight:workspace-links && pnpm --filter
@paperclipai/plugin-sdk build && pnpm --filter
@paperclipai/create-paperclip-plugin typecheck && pnpm --filter
paperclipai typecheck && pnpm --filter @paperclipai/plugin-sdk typecheck
&& pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run --project paperclipai
cli/src/__tests__/plugin-init.test.ts`
- `pnpm exec vitest run --project @paperclipai/plugin-sdk
packages/plugins/sdk/tests/worker-rpc-host.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-dev-watcher.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/plugin-routes-authz.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `pnpm --dir packages/plugins/examples/plugin-authoring-smoke-example
test`
- Confirmed `pnpm-lock.yaml` is not included in the PR diff.
## Risks
- Medium risk: this touches plugin install routing, CLI command
behavior, and the local development watcher.
- Local path plugin installs execute trusted local code by design; the
new docs call out that trust boundary.
- No database 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 coding agent, tool-enabled local shell and git
workflow, medium reasoning effort. Context window details were not
exposed in this 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
UI screenshots: not applicable; this PR changes CLI/server/plugin docs
and tests, not board UI rendering.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-12 17:38:24 -05:00
|
|
|
const byId = await registry.getById(pluginId);
|
|
|
|
|
if (byId) return byId;
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
return registry.getByKey(pluginId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optional dependencies for plugin job scheduling routes.
|
|
|
|
|
*
|
|
|
|
|
* When provided, job-related routes (list jobs, list runs, trigger job) are
|
|
|
|
|
* mounted. When omitted, the routes return 501 Not Implemented.
|
|
|
|
|
*/
|
|
|
|
|
export interface PluginRouteJobDeps {
|
|
|
|
|
/** The job scheduler instance. */
|
|
|
|
|
scheduler: PluginJobScheduler;
|
|
|
|
|
/** The job persistence store. */
|
|
|
|
|
jobStore: PluginJobStore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optional dependencies for plugin webhook routes.
|
|
|
|
|
*
|
|
|
|
|
* When provided, the webhook ingestion route is enabled. When omitted,
|
|
|
|
|
* webhook POST requests return 501 Not Implemented.
|
|
|
|
|
*/
|
|
|
|
|
export interface PluginRouteWebhookDeps {
|
|
|
|
|
/** The worker manager for dispatching handleWebhook RPC calls. */
|
|
|
|
|
workerManager: PluginWorkerManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optional dependencies for plugin tool routes.
|
|
|
|
|
*
|
|
|
|
|
* When provided, tool discovery and execution routes are enabled.
|
|
|
|
|
* When omitted, the tool routes return 501 Not Implemented.
|
|
|
|
|
*/
|
|
|
|
|
export interface PluginRouteToolDeps {
|
|
|
|
|
/** The tool dispatcher for listing and executing plugin tools. */
|
|
|
|
|
toolDispatcher: PluginToolDispatcher;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optional dependencies for plugin UI bridge routes.
|
|
|
|
|
*
|
|
|
|
|
* When provided, the getData and performAction bridge proxy routes are enabled,
|
|
|
|
|
* allowing plugin UI components to communicate with their worker backend via
|
|
|
|
|
* `usePluginData()` and `usePluginAction()` hooks.
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.8 — `getData`
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.9 — `performAction`
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
export interface PluginRouteBridgeDeps {
|
|
|
|
|
/** The worker manager for dispatching getData/performAction RPC calls. */
|
|
|
|
|
workerManager: PluginWorkerManager;
|
|
|
|
|
/** Optional stream bus for SSE push from worker to UI. */
|
|
|
|
|
streamBus?: PluginStreamBus;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
interface PluginScopedApiRequest {
|
|
|
|
|
routeKey: string;
|
|
|
|
|
method: string;
|
|
|
|
|
path: string;
|
|
|
|
|
params: Record<string, string>;
|
|
|
|
|
query: Record<string, string | string[]>;
|
|
|
|
|
body: unknown;
|
|
|
|
|
actor: {
|
|
|
|
|
actorType: "user" | "agent";
|
|
|
|
|
actorId: string;
|
|
|
|
|
agentId?: string | null;
|
|
|
|
|
userId?: string | null;
|
|
|
|
|
runId?: string | null;
|
|
|
|
|
};
|
|
|
|
|
companyId: string;
|
|
|
|
|
headers: Record<string, string>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PluginScopedApiResponse {
|
|
|
|
|
status?: number;
|
|
|
|
|
headers?: Record<string, string>;
|
|
|
|
|
body?: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
/** Request body for POST /api/plugins/tools/execute */
|
|
|
|
|
interface PluginToolExecuteRequest {
|
|
|
|
|
/** Fully namespaced tool name (e.g., "acme.linear:search-issues"). */
|
|
|
|
|
tool: string;
|
|
|
|
|
/** Parameters matching the tool's declared JSON Schema. */
|
|
|
|
|
parameters?: unknown;
|
|
|
|
|
/** Agent run context. */
|
|
|
|
|
runContext: ToolRunContext;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create Express router for plugin management API.
|
|
|
|
|
*
|
|
|
|
|
* Routes provided:
|
|
|
|
|
*
|
|
|
|
|
* | Method | Path | Description |
|
|
|
|
|
* |--------|------|-------------|
|
|
|
|
|
* | GET | /plugins | List all plugins (optional ?status= filter) |
|
|
|
|
|
* | GET | /plugins/ui-contributions | Get UI slots from ready plugins |
|
|
|
|
|
* | GET | /plugins/:pluginId | Get single plugin by ID or key |
|
|
|
|
|
* | POST | /plugins/install | Install from npm or local path |
|
|
|
|
|
* | DELETE | /plugins/:pluginId | Uninstall (optional ?purge=true) |
|
|
|
|
|
* | POST | /plugins/:pluginId/enable | Enable a plugin |
|
|
|
|
|
* | POST | /plugins/:pluginId/disable | Disable a plugin |
|
|
|
|
|
* | GET | /plugins/:pluginId/health | Run health diagnostics |
|
|
|
|
|
* | POST | /plugins/:pluginId/upgrade | Upgrade to newer version |
|
|
|
|
|
* | GET | /plugins/:pluginId/jobs | List jobs for a plugin |
|
|
|
|
|
* | GET | /plugins/:pluginId/jobs/:jobId/runs | List runs for a job |
|
|
|
|
|
* | POST | /plugins/:pluginId/jobs/:jobId/trigger | Manually trigger a job |
|
|
|
|
|
* | POST | /plugins/:pluginId/webhooks/:endpointKey | Receive inbound webhook |
|
|
|
|
|
* | GET | /plugins/tools | List all available plugin tools |
|
|
|
|
|
* | GET | /plugins/tools?pluginId=... | List tools for a specific plugin |
|
|
|
|
|
* | POST | /plugins/tools/execute | Execute a plugin tool |
|
|
|
|
|
* | GET | /plugins/:pluginId/config | Get current plugin config |
|
|
|
|
|
* | POST | /plugins/:pluginId/config | Save (upsert) plugin config |
|
|
|
|
|
* | POST | /plugins/:pluginId/config/test | Test config via validateConfig RPC |
|
|
|
|
|
* | POST | /plugins/:pluginId/bridge/data | Proxy getData to plugin worker |
|
|
|
|
|
* | POST | /plugins/:pluginId/bridge/action | Proxy performAction to plugin worker |
|
|
|
|
|
* | POST | /plugins/:pluginId/data/:key | Proxy getData to plugin worker (key in URL) |
|
|
|
|
|
* | POST | /plugins/:pluginId/actions/:key | Proxy performAction to plugin worker (key in URL) |
|
|
|
|
|
* | GET | /plugins/:pluginId/bridge/stream/:channel | SSE stream from worker to UI |
|
|
|
|
|
* | GET | /plugins/:pluginId/dashboard | Aggregated health dashboard data |
|
|
|
|
|
*
|
|
|
|
|
* **Route Ordering Note:** Static routes (like /ui-contributions, /tools) must be
|
|
|
|
|
* registered before parameterized routes (like /:pluginId) to prevent Express from
|
|
|
|
|
* matching them as a plugin ID.
|
|
|
|
|
*
|
|
|
|
|
* @param db - Database connection instance
|
|
|
|
|
* @param jobDeps - Optional job scheduling dependencies
|
|
|
|
|
* @param webhookDeps - Optional webhook ingestion dependencies
|
|
|
|
|
* @param toolDeps - Optional tool dispatcher dependencies
|
|
|
|
|
* @param bridgeDeps - Optional bridge proxy dependencies for getData/performAction
|
|
|
|
|
* @returns Express router with plugin routes mounted
|
|
|
|
|
*/
|
|
|
|
|
export function pluginRoutes(
|
|
|
|
|
db: Db,
|
|
|
|
|
loader: ReturnType<typeof pluginLoader>,
|
|
|
|
|
jobDeps?: PluginRouteJobDeps,
|
|
|
|
|
webhookDeps?: PluginRouteWebhookDeps,
|
|
|
|
|
toolDeps?: PluginRouteToolDeps,
|
|
|
|
|
bridgeDeps?: PluginRouteBridgeDeps,
|
|
|
|
|
) {
|
|
|
|
|
const router = Router();
|
|
|
|
|
const registry = pluginRegistryService(db);
|
|
|
|
|
const lifecycle = pluginLifecycleManager(db, {
|
|
|
|
|
loader,
|
|
|
|
|
workerManager: bridgeDeps?.workerManager ?? webhookDeps?.workerManager,
|
|
|
|
|
});
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
const issuesSvc = issueService(db);
|
|
|
|
|
|
|
|
|
|
function matchScopedApiRoute(route: PluginApiRouteDeclaration, method: string, requestPath: string) {
|
|
|
|
|
if (route.method !== method) return null;
|
|
|
|
|
const normalize = (value: string) => value.replace(/\/+$/, "") || "/";
|
|
|
|
|
const routeSegments = normalize(route.path).split("/").filter(Boolean);
|
|
|
|
|
const requestSegments = normalize(requestPath).split("/").filter(Boolean);
|
|
|
|
|
if (routeSegments.length !== requestSegments.length) return null;
|
|
|
|
|
const params: Record<string, string> = {};
|
|
|
|
|
for (let i = 0; i < routeSegments.length; i += 1) {
|
|
|
|
|
const routeSegment = routeSegments[i]!;
|
|
|
|
|
const requestSegment = requestSegments[i]!;
|
|
|
|
|
if (routeSegment.startsWith(":")) {
|
|
|
|
|
params[routeSegment.slice(1)] = decodeURIComponent(requestSegment);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (routeSegment !== requestSegment) return null;
|
|
|
|
|
}
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sanitizePluginRequestHeaders(req: Request): Record<string, string> {
|
|
|
|
|
const safeHeaderNames = new Set([
|
|
|
|
|
"accept",
|
|
|
|
|
"content-type",
|
|
|
|
|
"user-agent",
|
|
|
|
|
"x-paperclip-run-id",
|
|
|
|
|
"x-request-id",
|
|
|
|
|
]);
|
|
|
|
|
const headers: Record<string, string> = {};
|
|
|
|
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
|
|
|
const lower = name.toLowerCase();
|
|
|
|
|
if (!safeHeaderNames.has(lower)) continue;
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
headers[lower] = value.join(", ");
|
|
|
|
|
} else if (typeof value === "string") {
|
|
|
|
|
headers[lower] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return headers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyPluginScopedApiResponseHeaders(
|
|
|
|
|
res: Response,
|
|
|
|
|
headers: Record<string, string> | undefined,
|
|
|
|
|
): void {
|
|
|
|
|
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
|
|
|
const lower = name.toLowerCase();
|
|
|
|
|
if (!PLUGIN_SCOPED_API_RESPONSE_HEADER_ALLOWLIST.has(lower)) continue;
|
|
|
|
|
res.setHeader(lower, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeQuery(query: Request["query"]): Record<string, string | string[]> {
|
|
|
|
|
const normalized: Record<string, string | string[]> = {};
|
|
|
|
|
for (const [key, value] of Object.entries(query)) {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
normalized[key] = value;
|
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
|
normalized[key] = value.map((entry) => String(entry));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function resolveScopedApiCompanyId(
|
|
|
|
|
route: PluginApiRouteDeclaration,
|
|
|
|
|
params: Record<string, string>,
|
|
|
|
|
req: Request,
|
|
|
|
|
) {
|
|
|
|
|
const resolution = route.companyResolution;
|
|
|
|
|
if (!resolution) {
|
|
|
|
|
if (req.actor.type === "agent" && req.actor.companyId) return req.actor.companyId;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resolution.from === "body") {
|
|
|
|
|
const body = req.body as Record<string, unknown> | undefined;
|
|
|
|
|
const companyId = body?.[resolution.key ?? ""];
|
|
|
|
|
return typeof companyId === "string" ? companyId : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resolution.from === "query") {
|
|
|
|
|
const value = req.query[resolution.key ?? ""];
|
|
|
|
|
return typeof value === "string" ? value : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const issueId = params[resolution.param ?? ""];
|
|
|
|
|
if (!issueId) return null;
|
|
|
|
|
const issue = await issuesSvc.getById(issueId);
|
|
|
|
|
return issue?.companyId ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertScopedApiAuth(req: Request, route: PluginApiRouteDeclaration) {
|
|
|
|
|
if (route.auth === "board") {
|
|
|
|
|
assertBoard(req);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (route.auth === "agent") {
|
|
|
|
|
assertAuthenticated(req);
|
|
|
|
|
if (req.actor.type !== "agent") throw forbidden("Agent access required");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (route.auth === "webhook") {
|
|
|
|
|
throw unprocessable("Webhook-scoped plugin API routes require a signature verifier and are not enabled");
|
|
|
|
|
}
|
|
|
|
|
assertAuthenticated(req);
|
|
|
|
|
if (req.actor.type !== "board" && req.actor.type !== "agent") {
|
|
|
|
|
throw forbidden("Board or agent access required");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function enforceScopedApiCheckout(
|
|
|
|
|
req: Request,
|
|
|
|
|
route: PluginApiRouteDeclaration,
|
|
|
|
|
params: Record<string, string>,
|
|
|
|
|
companyId: string,
|
|
|
|
|
) {
|
|
|
|
|
const policy = route.checkoutPolicy ?? "none";
|
|
|
|
|
if (policy === "none" || req.actor.type !== "agent") return;
|
|
|
|
|
const issueId = params.issueId;
|
|
|
|
|
if (!issueId) {
|
|
|
|
|
throw unprocessable("Checkout-protected plugin API routes require an issueId route parameter");
|
|
|
|
|
}
|
|
|
|
|
const issue = await issuesSvc.getById(issueId);
|
|
|
|
|
if (!issue || issue.companyId !== companyId) {
|
|
|
|
|
throw notFound("Issue not found");
|
|
|
|
|
}
|
|
|
|
|
if (policy === "required-for-agent-in-progress") {
|
|
|
|
|
if (issue.status !== "in_progress" || issue.assigneeAgentId !== req.actor.agentId) return;
|
|
|
|
|
}
|
|
|
|
|
const runId = req.actor.runId?.trim();
|
|
|
|
|
if (!runId) {
|
|
|
|
|
throw unauthorized("Agent run id required");
|
|
|
|
|
}
|
|
|
|
|
if (!req.actor.agentId) {
|
|
|
|
|
throw forbidden("Agent authentication required");
|
|
|
|
|
}
|
|
|
|
|
await issuesSvc.assertCheckoutOwner(issueId, req.actor.agentId, runId);
|
|
|
|
|
}
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
async function resolvePluginAuditCompanyIds(req: Request): Promise<string[]> {
|
|
|
|
|
if (typeof (db as { select?: unknown }).select === "function") {
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select({ id: companies.id })
|
|
|
|
|
.from(companies);
|
|
|
|
|
return rows.map((row) => row.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.actor.type === "agent" && req.actor.companyId) {
|
|
|
|
|
return [req.actor.companyId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.actor.type === "board") {
|
|
|
|
|
return req.actor.companyIds ?? [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function logPluginMutationActivity(
|
|
|
|
|
req: Request,
|
|
|
|
|
action: string,
|
|
|
|
|
entityId: string,
|
|
|
|
|
details: Record<string, unknown>,
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const companyIds = await resolvePluginAuditCompanyIds(req);
|
|
|
|
|
if (companyIds.length === 0) return;
|
|
|
|
|
|
|
|
|
|
const actor = getActorInfo(req);
|
|
|
|
|
await Promise.all(companyIds.map((companyId) =>
|
|
|
|
|
logActivity(db, {
|
|
|
|
|
companyId,
|
|
|
|
|
actorType: actor.actorType,
|
|
|
|
|
actorId: actor.actorId,
|
|
|
|
|
agentId: actor.agentId,
|
|
|
|
|
runId: actor.runId,
|
|
|
|
|
action,
|
|
|
|
|
entityType: "plugin",
|
|
|
|
|
entityId,
|
|
|
|
|
details,
|
|
|
|
|
})));
|
|
|
|
|
}
|
|
|
|
|
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
function assertPluginBridgeScope(req: Request, companyId: unknown): string | undefined {
|
|
|
|
|
if (companyId === undefined || companyId === null) {
|
|
|
|
|
assertInstanceAdmin(req);
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
if (typeof companyId !== "string" || companyId.trim().length === 0) {
|
|
|
|
|
throw badRequest('"companyId" must be a non-empty string when provided');
|
|
|
|
|
}
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
return companyId;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 09:16:24 -05:00
|
|
|
function performActionActorContext(req: Request, companyId: string | undefined): PluginPerformActionActorContext {
|
|
|
|
|
const scopedCompanyId = companyId ?? null;
|
|
|
|
|
if (req.actor.type === "agent") {
|
|
|
|
|
return {
|
|
|
|
|
type: "agent",
|
|
|
|
|
userId: null,
|
|
|
|
|
agentId: req.actor.agentId ?? null,
|
|
|
|
|
runId: req.actor.runId ?? null,
|
|
|
|
|
companyId: scopedCompanyId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (req.actor.type === "board") {
|
|
|
|
|
return {
|
|
|
|
|
type: "user",
|
|
|
|
|
userId: req.actor.userId ?? null,
|
|
|
|
|
agentId: null,
|
|
|
|
|
runId: req.actor.runId ?? null,
|
|
|
|
|
companyId: scopedCompanyId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
type: "system",
|
|
|
|
|
userId: null,
|
|
|
|
|
agentId: null,
|
|
|
|
|
runId: req.actor.runId ?? null,
|
|
|
|
|
companyId: scopedCompanyId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function actionParamsWithAuthorizedCompanyScope(
|
|
|
|
|
params: Record<string, unknown> | undefined,
|
|
|
|
|
companyId: string | undefined,
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
const base = params ?? {};
|
|
|
|
|
return companyId === undefined ? base : { ...base, companyId };
|
|
|
|
|
}
|
|
|
|
|
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
async function validateToolRunContextScope(runContext: ToolRunContext): Promise<string | null> {
|
|
|
|
|
const [agent] = await db
|
|
|
|
|
.select({ companyId: agents.companyId })
|
|
|
|
|
.from(agents)
|
|
|
|
|
.where(eq(agents.id, runContext.agentId))
|
|
|
|
|
.limit(1);
|
|
|
|
|
if (!agent || agent.companyId !== runContext.companyId) {
|
|
|
|
|
return '"runContext.agentId" does not belong to "runContext.companyId"';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [run] = await db
|
|
|
|
|
.select({ companyId: heartbeatRuns.companyId, agentId: heartbeatRuns.agentId })
|
|
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(eq(heartbeatRuns.id, runContext.runId))
|
|
|
|
|
.limit(1);
|
|
|
|
|
if (!run || run.companyId !== runContext.companyId) {
|
|
|
|
|
return '"runContext.runId" does not belong to "runContext.companyId"';
|
|
|
|
|
}
|
|
|
|
|
if (run.agentId !== runContext.agentId) {
|
|
|
|
|
return '"runContext.runId" does not belong to "runContext.agentId"';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [project] = await db
|
|
|
|
|
.select({ companyId: projects.companyId })
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(eq(projects.id, runContext.projectId))
|
|
|
|
|
.limit(1);
|
|
|
|
|
if (!project || project.companyId !== runContext.companyId) {
|
|
|
|
|
return '"runContext.projectId" does not belong to "runContext.companyId"';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
/**
|
|
|
|
|
* GET /api/plugins
|
|
|
|
|
*
|
|
|
|
|
* List all installed plugins, optionally filtered by lifecycle status.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - `status` (optional): Filter by lifecycle status. Must be one of the
|
|
|
|
|
* values in `PLUGIN_STATUSES` (`installed`, `ready`, `error`,
|
|
|
|
|
* `upgrade_pending`, `uninstalled`). Returns HTTP 400 if the value is
|
|
|
|
|
* not a recognised status string.
|
|
|
|
|
*
|
|
|
|
|
* Response: `PluginRecord[]`
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const rawStatus = req.query.status;
|
|
|
|
|
if (rawStatus !== undefined) {
|
|
|
|
|
if (typeof rawStatus !== "string" || !(PLUGIN_STATUSES as readonly string[]).includes(rawStatus)) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: `Invalid status '${String(rawStatus)}'. Must be one of: ${PLUGIN_STATUSES.join(", ")}`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const status = rawStatus as PluginStatus | undefined;
|
|
|
|
|
const plugins = status
|
|
|
|
|
? await registry.listByStatus(status)
|
|
|
|
|
: await registry.listInstalled();
|
|
|
|
|
res.json(plugins);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/examples
|
|
|
|
|
*
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
* Return plugin packages bundled in this repo, if present.
|
2026-03-13 16:22:34 -05:00
|
|
|
* These can be installed through the normal local-path install flow.
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/examples", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
[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>
2026-05-26 08:32:45 -05:00
|
|
|
res.json(await listBundledPlugins());
|
2026-03-13 16:22:34 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// IMPORTANT: Static routes must come before parameterized routes
|
|
|
|
|
// to avoid Express matching "ui-contributions" as a :pluginId
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/ui-contributions
|
|
|
|
|
*
|
|
|
|
|
* Return UI contributions from all plugins in 'ready' state.
|
|
|
|
|
* Used by the frontend to discover plugin UI slots and launcher metadata.
|
|
|
|
|
*
|
|
|
|
|
* The response is normalized for the frontend slot host:
|
|
|
|
|
* - Only includes plugins with at least one declared UI slot or launcher
|
|
|
|
|
* - Excludes plugins with null/missing manifestJson (defensive)
|
|
|
|
|
* - Slots are extracted from manifest.ui.slots
|
|
|
|
|
* - Launchers are aggregated from legacy manifest.launchers and manifest.ui.launchers
|
|
|
|
|
*
|
|
|
|
|
* Example response:
|
|
|
|
|
* ```json
|
|
|
|
|
* [
|
|
|
|
|
* {
|
|
|
|
|
* "pluginId": "plg_123",
|
|
|
|
|
* "pluginKey": "paperclip.claude-usage",
|
|
|
|
|
* "displayName": "Claude Usage",
|
|
|
|
|
* "version": "1.0.0",
|
|
|
|
|
* "uiEntryFile": "index.js",
|
|
|
|
|
* "slots": [],
|
|
|
|
|
* "launchers": [
|
|
|
|
|
* {
|
|
|
|
|
* "id": "claude-usage-toolbar",
|
|
|
|
|
* "displayName": "Claude Usage",
|
|
|
|
|
* "placementZone": "toolbarButton",
|
|
|
|
|
* "action": { "type": "openModal", "target": "ClaudeUsageView" },
|
|
|
|
|
* "render": { "environment": "hostOverlay", "bounds": "wide" }
|
|
|
|
|
* }
|
|
|
|
|
* ]
|
|
|
|
|
* }
|
|
|
|
|
* ]
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginUiContribution[]
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/ui-contributions", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const plugins = await registry.listByStatus("ready");
|
|
|
|
|
|
|
|
|
|
const contributions: PluginUiContribution[] = plugins
|
|
|
|
|
.map((plugin) => {
|
|
|
|
|
// Safety check: manifestJson should always exist for ready plugins, but guard against null
|
|
|
|
|
const manifest = plugin.manifestJson;
|
|
|
|
|
if (!manifest) return null;
|
|
|
|
|
|
|
|
|
|
const uiMetadata = getPluginUiContributionMetadata(manifest);
|
|
|
|
|
if (!uiMetadata) return null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
displayName: manifest.displayName,
|
|
|
|
|
version: plugin.version,
|
|
|
|
|
updatedAt: plugin.updatedAt.toISOString(),
|
|
|
|
|
uiEntryFile: uiMetadata.uiEntryFile,
|
|
|
|
|
slots: uiMetadata.slots,
|
|
|
|
|
launchers: uiMetadata.launchers,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((item): item is PluginUiContribution => item !== null);
|
|
|
|
|
res.json(contributions);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// Tool discovery and execution routes
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/tools
|
|
|
|
|
*
|
|
|
|
|
* List all available plugin-contributed tools in an agent-friendly format.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - `pluginId` (optional): Filter to tools from a specific plugin
|
|
|
|
|
*
|
|
|
|
|
* Response: `AgentToolDescriptor[]`
|
|
|
|
|
* Errors: 501 if tool dispatcher is not configured
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/tools", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!toolDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin tool dispatch is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pluginId = req.query.pluginId as string | undefined;
|
|
|
|
|
const filter = pluginId ? { pluginId } : undefined;
|
|
|
|
|
const tools = toolDeps.toolDispatcher.listToolsForAgent(filter);
|
2026-03-13 16:58:29 -05:00
|
|
|
res.json(tools);
|
2026-03-13 16:22:34 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/tools/execute
|
|
|
|
|
*
|
|
|
|
|
* Execute a plugin-contributed tool by its namespaced name.
|
|
|
|
|
*
|
|
|
|
|
* This is the primary endpoint used by the agent service to invoke
|
|
|
|
|
* plugin tools during an agent run.
|
|
|
|
|
*
|
|
|
|
|
* Request body:
|
|
|
|
|
* - `tool`: Fully namespaced tool name (e.g., "acme.linear:search-issues")
|
|
|
|
|
* - `parameters`: Parameters matching the tool's declared JSON Schema
|
|
|
|
|
* - `runContext`: Agent run context with agentId, runId, companyId, projectId
|
|
|
|
|
*
|
|
|
|
|
* Response: `ToolExecutionResult`
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if request validation fails
|
|
|
|
|
* - 404 if tool is not found
|
|
|
|
|
* - 501 if tool dispatcher is not configured
|
|
|
|
|
* - 502 if the plugin worker is unavailable or the RPC call fails
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/tools/execute", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!toolDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin tool dispatch is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = (req.body as PluginToolExecuteRequest | undefined);
|
|
|
|
|
if (!body) {
|
|
|
|
|
res.status(400).json({ error: "Request body is required" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { tool, parameters, runContext } = body;
|
|
|
|
|
|
|
|
|
|
// Validate required fields
|
|
|
|
|
if (!tool || typeof tool !== "string") {
|
|
|
|
|
res.status(400).json({ error: '"tool" is required and must be a string' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!runContext || typeof runContext !== "object") {
|
|
|
|
|
res.status(400).json({ error: '"runContext" is required and must be an object' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!runContext.agentId || !runContext.runId || !runContext.companyId || !runContext.projectId) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: '"runContext" must include agentId, runId, companyId, and projectId',
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assertCompanyAccess(req, runContext.companyId);
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
const scopeError = await validateToolRunContextScope(runContext);
|
|
|
|
|
if (scopeError) {
|
|
|
|
|
res.status(403).json({ error: scopeError });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
// Verify the tool exists
|
|
|
|
|
const registeredTool = toolDeps.toolDispatcher.getTool(tool);
|
|
|
|
|
if (!registeredTool) {
|
|
|
|
|
res.status(404).json({ error: `Tool "${tool}" not found` });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await toolDeps.toolDispatcher.executeTool(
|
|
|
|
|
tool,
|
|
|
|
|
parameters ?? {},
|
|
|
|
|
runContext,
|
|
|
|
|
);
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
|
|
|
|
|
// Distinguish between "worker not running" (502) and other errors (500)
|
|
|
|
|
if (message.includes("not running") || message.includes("worker")) {
|
|
|
|
|
res.status(502).json({ error: message });
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/install
|
|
|
|
|
*
|
|
|
|
|
* Install a plugin from npm or a local filesystem path.
|
|
|
|
|
*
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [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-04-15 08:41:15 -05:00
|
|
|
* Instance-wide plugin installation is restricted to instance admins because
|
|
|
|
|
* the install flow fetches and inspects package contents on the host.
|
|
|
|
|
*
|
2026-03-13 16:22:34 -05:00
|
|
|
* Request body:
|
|
|
|
|
* - packageName: npm package name or local path (required)
|
|
|
|
|
* - version: Target version for npm packages (optional)
|
|
|
|
|
* - isLocalPath: Set true if packageName is a local path
|
|
|
|
|
*
|
|
|
|
|
* The installer:
|
|
|
|
|
* 1. Downloads from npm or loads from local path
|
|
|
|
|
* 2. Validates the manifest (schema + capability consistency)
|
|
|
|
|
* 3. Registers in the database
|
|
|
|
|
* 4. Transitions to `ready` state if no new capability approval is needed
|
|
|
|
|
*
|
|
|
|
|
* Response: `PluginRecord`
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - `400` — validation failure or install error (package not found, bad manifest, etc.)
|
|
|
|
|
* - `500` — installation succeeded but manifest is missing (indicates a loader bug)
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/install", async (req, res) => {
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [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-04-15 08:41:15 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { packageName, version, isLocalPath } = req.body as PluginInstallRequest;
|
|
|
|
|
|
|
|
|
|
// Input validation
|
|
|
|
|
if (!packageName || typeof packageName !== "string") {
|
|
|
|
|
res.status(400).json({ error: "packageName is required and must be a string" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (version !== undefined && typeof version !== "string") {
|
|
|
|
|
res.status(400).json({ error: "version must be a string if provided" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isLocalPath !== undefined && typeof isLocalPath !== "boolean") {
|
|
|
|
|
res.status(400).json({ error: "isLocalPath must be a boolean if provided" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate package name format
|
|
|
|
|
const trimmedPackage = packageName.trim();
|
|
|
|
|
if (trimmedPackage.length === 0) {
|
|
|
|
|
res.status(400).json({ error: "packageName cannot be empty" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Basic security check for package name (prevent injection)
|
|
|
|
|
if (!isLocalPath && /[<>:"|?*]/.test(trimmedPackage)) {
|
|
|
|
|
res.status(400).json({ error: "packageName contains invalid characters" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const installOptions = isLocalPath
|
|
|
|
|
? { localPath: trimmedPackage }
|
|
|
|
|
: { packageName: trimmedPackage, version: version?.trim() };
|
|
|
|
|
|
|
|
|
|
const discovered = await loader.installPlugin(installOptions);
|
|
|
|
|
|
|
|
|
|
if (!discovered.manifest) {
|
|
|
|
|
res.status(500).json({ error: "Plugin installed but manifest is missing" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transition to ready state
|
|
|
|
|
const existingPlugin = await registry.getByKey(discovered.manifest.id);
|
|
|
|
|
if (existingPlugin) {
|
|
|
|
|
await lifecycle.load(existingPlugin.id);
|
|
|
|
|
const updated = await registry.getById(existingPlugin.id);
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.installed", existingPlugin.id, {
|
|
|
|
|
pluginId: existingPlugin.id,
|
|
|
|
|
pluginKey: existingPlugin.pluginKey,
|
|
|
|
|
packageName: updated?.packageName ?? existingPlugin.packageName,
|
|
|
|
|
version: updated?.version ?? existingPlugin.version,
|
|
|
|
|
source: isLocalPath ? "local_path" : "npm",
|
|
|
|
|
});
|
|
|
|
|
publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: existingPlugin.id, action: "installed" } });
|
|
|
|
|
res.json(updated);
|
|
|
|
|
} else {
|
|
|
|
|
// This shouldn't happen since installPlugin already registers in the DB
|
|
|
|
|
res.status(500).json({ error: "Plugin installed but not found in registry" });
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// UI Bridge proxy routes (getData / performAction)
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/** Request body for POST /api/plugins/:pluginId/bridge/data */
|
|
|
|
|
interface PluginBridgeDataRequest {
|
|
|
|
|
/** Plugin-defined data key (e.g. `"sync-health"`). */
|
|
|
|
|
key: string;
|
2026-03-13 16:58:29 -05:00
|
|
|
/** Optional company scope for authorizing company-context bridge calls. */
|
2026-03-13 16:22:34 -05:00
|
|
|
companyId?: string;
|
|
|
|
|
/** Optional context and query parameters from the UI. */
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
/** Optional host launcher/render metadata for the worker bridge call. */
|
|
|
|
|
renderEnvironment?: PluginLauncherRenderContextSnapshot | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Request body for POST /api/plugins/:pluginId/bridge/action */
|
|
|
|
|
interface PluginBridgeActionRequest {
|
|
|
|
|
/** Plugin-defined action key (e.g. `"resync"`). */
|
|
|
|
|
key: string;
|
2026-03-13 16:58:29 -05:00
|
|
|
/** Optional company scope for authorizing company-context bridge calls. */
|
2026-03-13 16:22:34 -05:00
|
|
|
companyId?: string;
|
|
|
|
|
/** Optional parameters from the UI. */
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
/** Optional host launcher/render metadata for the worker bridge call. */
|
|
|
|
|
renderEnvironment?: PluginLauncherRenderContextSnapshot | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Response envelope for bridge errors. */
|
|
|
|
|
interface PluginBridgeErrorResponse {
|
|
|
|
|
code: PluginBridgeErrorCode;
|
|
|
|
|
message: string;
|
|
|
|
|
details?: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Map a worker RPC error to a bridge-level error code.
|
|
|
|
|
*
|
|
|
|
|
* JsonRpcCallError carries numeric codes from the plugin RPC error code space.
|
|
|
|
|
* This helper maps them to the string error codes defined in PluginBridgeErrorCode.
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
function mapRpcErrorToBridgeError(err: unknown): PluginBridgeErrorResponse {
|
|
|
|
|
if (err instanceof JsonRpcCallError) {
|
|
|
|
|
switch (err.code) {
|
|
|
|
|
case PLUGIN_RPC_ERROR_CODES.WORKER_UNAVAILABLE:
|
|
|
|
|
return {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
|
|
|
|
case PLUGIN_RPC_ERROR_CODES.CAPABILITY_DENIED:
|
|
|
|
|
return {
|
|
|
|
|
code: "CAPABILITY_DENIED",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
2026-05-22 09:16:24 -05:00
|
|
|
case PLUGIN_RPC_ERROR_CODES.INVOCATION_SCOPE_DENIED:
|
|
|
|
|
return {
|
|
|
|
|
code: "INVOCATION_SCOPE_DENIED",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
2026-03-13 16:22:34 -05:00
|
|
|
case PLUGIN_RPC_ERROR_CODES.TIMEOUT:
|
|
|
|
|
return {
|
|
|
|
|
code: "TIMEOUT",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
|
|
|
|
case PLUGIN_RPC_ERROR_CODES.WORKER_ERROR:
|
|
|
|
|
return {
|
|
|
|
|
code: "WORKER_ERROR",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
|
|
|
|
default:
|
|
|
|
|
return {
|
|
|
|
|
code: "UNKNOWN",
|
|
|
|
|
message: err.message,
|
|
|
|
|
details: err.data,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
|
|
|
|
|
// Worker not running — surface as WORKER_UNAVAILABLE
|
|
|
|
|
if (message.includes("not running") || message.includes("not registered")) {
|
|
|
|
|
return {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
code: "UNKNOWN",
|
|
|
|
|
message,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 15:52:39 -05:00
|
|
|
function attachPluginBridgeErrorContext(
|
|
|
|
|
req: Request,
|
|
|
|
|
res: Response,
|
|
|
|
|
err: unknown,
|
|
|
|
|
bridgeError: PluginBridgeErrorResponse,
|
|
|
|
|
metadata: Record<string, unknown>,
|
|
|
|
|
): void {
|
|
|
|
|
const rootError = err instanceof Error ? err : new Error(String(err));
|
|
|
|
|
(res as any).__errorContext = {
|
|
|
|
|
error: {
|
|
|
|
|
message: bridgeError.message,
|
|
|
|
|
stack: rootError.stack,
|
|
|
|
|
name: rootError.name,
|
|
|
|
|
details: {
|
|
|
|
|
...metadata,
|
|
|
|
|
bridgeCode: bridgeError.code,
|
|
|
|
|
bridgeDetails: bridgeError.details,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
method: req.method,
|
|
|
|
|
url: req.originalUrl,
|
|
|
|
|
reqBody: req.body,
|
|
|
|
|
reqParams: req.params,
|
|
|
|
|
reqQuery: req.query,
|
|
|
|
|
};
|
|
|
|
|
(res as any).err = rootError;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/bridge/data
|
|
|
|
|
*
|
|
|
|
|
* Proxy a `getData` call from the plugin UI to the plugin worker.
|
|
|
|
|
*
|
|
|
|
|
* This is the server-side half of the `usePluginData(key, params)` bridge hook.
|
|
|
|
|
* The frontend sends a POST with the data key and optional params; the host
|
|
|
|
|
* forwards the call to the worker via the `getData` RPC method and returns
|
|
|
|
|
* the result.
|
|
|
|
|
*
|
|
|
|
|
* Request body:
|
|
|
|
|
* - `key`: Plugin-defined data key (e.g. `"sync-health"`)
|
|
|
|
|
* - `params`: Optional query parameters forwarded to the worker handler
|
|
|
|
|
*
|
|
|
|
|
* Response: The raw result from the worker's `getData` handler
|
|
|
|
|
*
|
|
|
|
|
* Error response body follows the `PluginBridgeError` shape:
|
|
|
|
|
* `{ code: PluginBridgeErrorCode, message: string, details?: unknown }`
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if request validation fails
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps are not configured
|
|
|
|
|
* - 502 if the worker is unavailable or returns an error
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.8 — `getData`
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/bridge/data", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
// Resolve plugin
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate plugin is in ready state
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
const bridgeError: PluginBridgeErrorResponse = {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
};
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "getData",
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate request body
|
|
|
|
|
const body = req.body as PluginBridgeDataRequest | undefined;
|
|
|
|
|
if (!body || !body.key || typeof body.key !== "string") {
|
|
|
|
|
res.status(400).json({ error: '"key" is required and must be a string' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
const companyId = assertPluginBridgeScope(req, body.companyId);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"getData",
|
|
|
|
|
{
|
|
|
|
|
key: body.key,
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
...(companyId ? { companyId } : {}),
|
2026-03-13 16:22:34 -05:00
|
|
|
params: body.params ?? {},
|
|
|
|
|
renderEnvironment: body.renderEnvironment ?? null,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
res.json({ data: result });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const bridgeError = mapRpcErrorToBridgeError(err);
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "getData",
|
|
|
|
|
dataKey: body.key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/bridge/action
|
|
|
|
|
*
|
|
|
|
|
* Proxy a `performAction` call from the plugin UI to the plugin worker.
|
|
|
|
|
*
|
|
|
|
|
* This is the server-side half of the `usePluginAction(key)` bridge hook.
|
|
|
|
|
* The frontend sends a POST with the action key and optional params; the host
|
|
|
|
|
* forwards the call to the worker via the `performAction` RPC method and
|
|
|
|
|
* returns the result.
|
|
|
|
|
*
|
|
|
|
|
* Request body:
|
|
|
|
|
* - `key`: Plugin-defined action key (e.g. `"resync"`)
|
|
|
|
|
* - `params`: Optional parameters forwarded to the worker handler
|
|
|
|
|
*
|
|
|
|
|
* Response: The raw result from the worker's `performAction` handler
|
|
|
|
|
*
|
|
|
|
|
* Error response body follows the `PluginBridgeError` shape:
|
|
|
|
|
* `{ code: PluginBridgeErrorCode, message: string, details?: unknown }`
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if request validation fails
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps are not configured
|
|
|
|
|
* - 502 if the worker is unavailable or returns an error
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.9 — `performAction`
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/bridge/action", async (req, res) => {
|
2026-05-22 09:16:24 -05:00
|
|
|
assertAuthenticated(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
// Resolve plugin
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate plugin is in ready state
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
const bridgeError: PluginBridgeErrorResponse = {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
};
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "performAction",
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate request body
|
|
|
|
|
const body = req.body as PluginBridgeActionRequest | undefined;
|
|
|
|
|
if (!body || !body.key || typeof body.key !== "string") {
|
|
|
|
|
res.status(400).json({ error: '"key" is required and must be a string' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
const companyId = assertPluginBridgeScope(req, body.companyId);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"performAction",
|
|
|
|
|
{
|
|
|
|
|
key: body.key,
|
2026-05-22 09:16:24 -05:00
|
|
|
params: actionParamsWithAuthorizedCompanyScope(body.params, companyId),
|
|
|
|
|
actorContext: performActionActorContext(req, companyId),
|
2026-03-13 16:22:34 -05:00
|
|
|
renderEnvironment: body.renderEnvironment ?? null,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
res.json({ data: result });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const bridgeError = mapRpcErrorToBridgeError(err);
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "performAction",
|
|
|
|
|
actionKey: body.key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// URL-keyed bridge routes (key as path parameter)
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/data/:key
|
|
|
|
|
*
|
|
|
|
|
* Proxy a `getData` call from the plugin UI to the plugin worker, with the
|
|
|
|
|
* data key specified as a URL path parameter instead of in the request body.
|
|
|
|
|
*
|
|
|
|
|
* This is a REST-friendly alternative to `POST /plugins/:pluginId/bridge/data`.
|
|
|
|
|
* The frontend bridge hooks use this endpoint for cleaner URLs.
|
|
|
|
|
*
|
|
|
|
|
* Request body (optional):
|
|
|
|
|
* - `params`: Optional query parameters forwarded to the worker handler
|
|
|
|
|
*
|
|
|
|
|
* Response: The raw result from the worker's `getData` handler wrapped as `{ data: T }`
|
|
|
|
|
*
|
|
|
|
|
* Error response body follows the `PluginBridgeError` shape:
|
|
|
|
|
* `{ code: PluginBridgeErrorCode, message: string, details?: unknown }`
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps are not configured
|
|
|
|
|
* - 502 if the worker is unavailable or returns an error
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.8 — `getData`
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/data/:key", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, key } = req.params;
|
|
|
|
|
|
|
|
|
|
// Resolve plugin
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate plugin is in ready state
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
const bridgeError: PluginBridgeErrorResponse = {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
};
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "getData",
|
|
|
|
|
dataKey: key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as {
|
|
|
|
|
companyId?: string;
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
renderEnvironment?: PluginLauncherRenderContextSnapshot | null;
|
|
|
|
|
} | undefined;
|
|
|
|
|
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
const companyId = assertPluginBridgeScope(req, body?.companyId);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"getData",
|
|
|
|
|
{
|
|
|
|
|
key,
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
...(companyId ? { companyId } : {}),
|
2026-03-13 16:22:34 -05:00
|
|
|
params: body?.params ?? {},
|
|
|
|
|
renderEnvironment: body?.renderEnvironment ?? null,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
res.json({ data: result });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const bridgeError = mapRpcErrorToBridgeError(err);
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "getData",
|
|
|
|
|
dataKey: key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/actions/:key
|
|
|
|
|
*
|
|
|
|
|
* Proxy a `performAction` call from the plugin UI to the plugin worker, with
|
|
|
|
|
* the action key specified as a URL path parameter instead of in the request body.
|
|
|
|
|
*
|
|
|
|
|
* This is a REST-friendly alternative to `POST /plugins/:pluginId/bridge/action`.
|
|
|
|
|
* The frontend bridge hooks use this endpoint for cleaner URLs.
|
|
|
|
|
*
|
|
|
|
|
* Request body (optional):
|
|
|
|
|
* - `params`: Optional parameters forwarded to the worker handler
|
|
|
|
|
*
|
|
|
|
|
* Response: The raw result from the worker's `performAction` handler wrapped as `{ data: T }`
|
|
|
|
|
*
|
|
|
|
|
* Error response body follows the `PluginBridgeError` shape:
|
|
|
|
|
* `{ code: PluginBridgeErrorCode, message: string, details?: unknown }`
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps are not configured
|
|
|
|
|
* - 502 if the worker is unavailable or returns an error
|
|
|
|
|
*
|
|
|
|
|
* @see PLUGIN_SPEC.md §13.9 — `performAction`
|
|
|
|
|
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/actions/:key", async (req, res) => {
|
2026-05-22 09:16:24 -05:00
|
|
|
assertAuthenticated(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, key } = req.params;
|
|
|
|
|
|
|
|
|
|
// Resolve plugin
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate plugin is in ready state
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
const bridgeError: PluginBridgeErrorResponse = {
|
|
|
|
|
code: "WORKER_UNAVAILABLE",
|
|
|
|
|
message: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
};
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, new Error(bridgeError.message), bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "performAction",
|
|
|
|
|
actionKey: key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as {
|
|
|
|
|
companyId?: string;
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
renderEnvironment?: PluginLauncherRenderContextSnapshot | null;
|
|
|
|
|
} | undefined;
|
|
|
|
|
|
[codex] Add agent permissions and controls plan (#6386)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies by keeping
task ownership, approvals, and operator control inside one control
plane.
> - Agent permissions and plugin-hosted company settings sit on the
boundary between autonomy and governance.
> - V1 needs scoped task assignment rules, plugin extension points, and
clearer company access surfaces without weakening company boundaries.
> - The branch builds the core authorization service, plugin SDK/host
APIs, and UI simplifications needed to support those controls.
> - Paperclip EE plugin surfaces were intentionally moved out of this
core PR per review direction, so this PR now carries only the public
core/plugin infrastructure work.
> - The latest updates preserve the PAP-9937 branch changes that belong
in this PR, remove the `design/` artifacts, and exclude the experimental
`plugin-briefs` package.
> - Greptile feedback was applied through the authorization/audit paths
and the final cleanup commit was re-reviewed at 5/5 with no unresolved
Greptile threads.
> - The benefit is safer assignment control with extension hooks for
richer permission products while preserving simple defaults for normal
operators.
## What Changed
- Added scoped task-assignment authorization decisions and routed
issue/agent assignment mutations through the authorization service.
- Added plugin SDK and host APIs for company settings slots,
authorization policy/grant management, assignment previews, and bridge
invocation scope propagation.
- Simplified core company access UI and moved advanced controls behind
plugin-provided settings surfaces.
- Added retry-now affordances for blocked issue next-step notices.
- Added protected-assignment enforcement for persisted
agent/project/issue policies, including explicit-grant fallback
behavior.
- Added incremental principal-access compatibility backfill for active
agent memberships and role-default human permission grants.
- Added the Markdown code block wrap action fix from the latest branch
changes.
- Removed `design/` artifacts from the PR and removed
`packages/plugins/plugin-briefs` from the final diff.
- Addressed Greptile feedback for plugin actor sanitization, legacy
membership handling, audit pagination, unknown grant-scope metadata, and
startup test mocks.
## Verification
- `pnpm exec vitest run server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 2 files passed, 54
tests passed.
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/company-portability.test.ts` -> 3 files passed, 62
tests passed.
- `pnpm exec vitest run
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/plugin-access-authorization-host-services.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts` -> 3 files
passed, 28 tests passed.
- `pnpm --filter @paperclipai/server typecheck` -> passed.
- `git diff --check` -> passed.
- `node ./scripts/check-docker-deps-stage.mjs` -> passed.
- `CI=true pnpm install --frozen-lockfile --ignore-scripts` -> passed
with no lockfile update.
- `pnpm exec vitest run
ui/src/components/MarkdownBody.interaction.test.tsx` -> 1 test passed.
- `git ls-files design packages/plugins/plugin-briefs | wc -l` -> 0.
- GitHub CI on `40cd83b53` -> all checks passed, merge state `CLEAN`.
- Greptile on `40cd83b53` -> 5/5, 102 files reviewed, 0
comments/annotations added, 0 unresolved review threads.
- Confirmed the PR diff contains no `design/`,
`packages/plugins/plugin-briefs`, `pnpm-lock.yaml`, or
`.github/workflows` changes.
## Risks
- Medium: task assignment authorization paths are behaviorally stricter
for protected/private policy data, so existing plugin-authored policies
may block assignment until explicit grants or approval flows are
configured.
- Medium: plugin-host authorization APIs expand the surface area
available to trusted plugins and need careful review for company
scoping.
- Low: startup now performs a principal-access compatibility backfill,
but the migration and runtime backfill use conflict-tolerant inserts.
> 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 coding agent, tool-enabled workflow with shell,
git, and GitHub CLI access.
## 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-22 08:12:52 -05:00
|
|
|
const companyId = assertPluginBridgeScope(req, body?.companyId);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"performAction",
|
|
|
|
|
{
|
|
|
|
|
key,
|
2026-05-22 09:16:24 -05:00
|
|
|
params: actionParamsWithAuthorizedCompanyScope(body?.params, companyId),
|
|
|
|
|
actorContext: performActionActorContext(req, companyId),
|
2026-03-13 16:22:34 -05:00
|
|
|
renderEnvironment: body?.renderEnvironment ?? null,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
res.json({ data: result });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const bridgeError = mapRpcErrorToBridgeError(err);
|
2026-05-19 15:52:39 -05:00
|
|
|
attachPluginBridgeErrorContext(req, res, err, bridgeError, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
bridgeMethod: "performAction",
|
|
|
|
|
actionKey: key,
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// SSE stream bridge route
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/bridge/stream/:channel
|
|
|
|
|
*
|
|
|
|
|
* Server-Sent Events endpoint for real-time streaming from plugin worker to UI.
|
|
|
|
|
*
|
|
|
|
|
* The worker pushes events via `ctx.streams.emit(channel, event)` which arrive
|
|
|
|
|
* as JSON-RPC notifications to the host, get published on the PluginStreamBus,
|
|
|
|
|
* and are fanned out to all connected SSE clients matching (pluginId, channel,
|
|
|
|
|
* companyId).
|
|
|
|
|
*
|
|
|
|
|
* Query parameters:
|
|
|
|
|
* - `companyId` (required): Scope events to a specific company
|
|
|
|
|
*
|
|
|
|
|
* SSE event types:
|
|
|
|
|
* - `message`: A data event from the worker (default)
|
|
|
|
|
* - `open`: The worker opened the stream channel
|
|
|
|
|
* - `close`: The worker closed the stream channel — client should disconnect
|
|
|
|
|
*
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if companyId is missing
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps or stream bus are not configured
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/bridge/stream/:channel", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps?.streamBus) {
|
|
|
|
|
res.status(501).json({ error: "Plugin stream bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, channel } = req.params;
|
|
|
|
|
const companyId = req.query.companyId as string | undefined;
|
|
|
|
|
|
|
|
|
|
if (!companyId) {
|
|
|
|
|
res.status(400).json({ error: '"companyId" query parameter is required' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
|
|
|
|
|
// Set SSE headers
|
|
|
|
|
res.writeHead(200, {
|
|
|
|
|
"Content-Type": "text/event-stream",
|
|
|
|
|
"Cache-Control": "no-cache",
|
|
|
|
|
"Connection": "keep-alive",
|
|
|
|
|
"X-Accel-Buffering": "no",
|
|
|
|
|
});
|
|
|
|
|
res.flushHeaders();
|
|
|
|
|
|
|
|
|
|
// Send initial comment to establish the connection
|
|
|
|
|
res.write(":ok\n\n");
|
|
|
|
|
|
|
|
|
|
let unsubscribed = false;
|
|
|
|
|
const safeUnsubscribe = () => {
|
|
|
|
|
if (!unsubscribed) {
|
|
|
|
|
unsubscribed = true;
|
|
|
|
|
unsubscribe();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const unsubscribe = bridgeDeps.streamBus.subscribe(
|
|
|
|
|
plugin.id,
|
|
|
|
|
channel,
|
|
|
|
|
companyId,
|
|
|
|
|
(event, eventType) => {
|
|
|
|
|
if (unsubscribed || !res.writable) return;
|
|
|
|
|
try {
|
|
|
|
|
if (eventType !== "message") {
|
|
|
|
|
res.write(`event: ${eventType}\n`);
|
|
|
|
|
}
|
|
|
|
|
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
|
|
|
} catch {
|
|
|
|
|
// Connection closed or write error — stop delivering
|
|
|
|
|
safeUnsubscribe();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
req.on("close", safeUnsubscribe);
|
|
|
|
|
res.on("error", safeUnsubscribe);
|
|
|
|
|
});
|
|
|
|
|
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
router.use("/plugins/:pluginId/api", async (req, res) => {
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin scoped API routes are not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
res.status(503).json({ error: `Plugin is not ready (current status: ${plugin.status})` });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const isWorkerRunning = typeof bridgeDeps.workerManager.isRunning === "function"
|
|
|
|
|
? bridgeDeps.workerManager.isRunning(plugin.id)
|
|
|
|
|
: true;
|
|
|
|
|
if (!isWorkerRunning) {
|
|
|
|
|
res.status(503).json({ error: "Plugin worker is not running" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!plugin.manifestJson.capabilities.includes("api.routes.register")) {
|
|
|
|
|
res.status(404).json({ error: "Plugin does not expose scoped API routes" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const requestPath = req.path || "/";
|
|
|
|
|
const routes = plugin.manifestJson.apiRoutes ?? [];
|
|
|
|
|
const match = routes
|
|
|
|
|
.map((route) => ({ route, params: matchScopedApiRoute(route, req.method, requestPath) }))
|
|
|
|
|
.find((candidate) => candidate.params !== null);
|
|
|
|
|
if (!match || !match.params) {
|
|
|
|
|
res.status(404).json({ error: "Plugin API route not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
assertScopedApiAuth(req, match.route);
|
|
|
|
|
const companyId = await resolveScopedApiCompanyId(match.route, match.params, req);
|
|
|
|
|
if (!companyId) {
|
|
|
|
|
res.status(400).json({ error: "Unable to resolve company for plugin API route" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
await enforceScopedApiCheckout(req, match.route, match.params, companyId);
|
|
|
|
|
if (req.method !== "GET" && req.headers["content-type"] && !req.is("application/json")) {
|
|
|
|
|
res.status(415).json({ error: "Plugin API routes accept JSON requests only" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const requestBody = req.body ?? null;
|
|
|
|
|
const bodySize = Buffer.byteLength(JSON.stringify(requestBody));
|
|
|
|
|
if (bodySize > PLUGIN_API_BODY_LIMIT_BYTES) {
|
|
|
|
|
res.status(413).json({ error: "Plugin API request body is too large" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const actor = getActorInfo(req);
|
|
|
|
|
const input: PluginScopedApiRequest = {
|
|
|
|
|
routeKey: match.route.routeKey,
|
|
|
|
|
method: req.method,
|
|
|
|
|
path: requestPath,
|
|
|
|
|
params: match.params,
|
|
|
|
|
query: normalizeQuery(req.query),
|
|
|
|
|
body: requestBody,
|
|
|
|
|
actor: {
|
|
|
|
|
actorType: actor.actorType,
|
|
|
|
|
actorId: actor.actorId,
|
|
|
|
|
agentId: actor.agentId,
|
|
|
|
|
userId: actor.actorType === "user" ? actor.actorId : null,
|
|
|
|
|
runId: actor.runId,
|
|
|
|
|
},
|
|
|
|
|
companyId,
|
|
|
|
|
headers: sanitizePluginRequestHeaders(req),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"handleApiRequest",
|
|
|
|
|
input,
|
|
|
|
|
) as PluginScopedApiResponse;
|
|
|
|
|
const status = Number.isInteger(result.status) && Number(result.status) >= 200 && Number(result.status) <= 599
|
|
|
|
|
? Number(result.status)
|
|
|
|
|
: 200;
|
|
|
|
|
applyPluginScopedApiResponseHeaders(res, result.headers);
|
|
|
|
|
if (status === 204) {
|
|
|
|
|
res.status(status).end();
|
|
|
|
|
} else {
|
|
|
|
|
res.status(status).json(result.body ?? null);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const status = typeof (err as { status?: unknown }).status === "number"
|
|
|
|
|
? (err as { status: number }).status
|
2026-05-22 09:16:24 -05:00
|
|
|
: err instanceof JsonRpcCallError && (
|
|
|
|
|
err.code === PLUGIN_RPC_ERROR_CODES.CAPABILITY_DENIED ||
|
|
|
|
|
err.code === PLUGIN_RPC_ERROR_CODES.INVOCATION_SCOPE_DENIED
|
|
|
|
|
)
|
[codex] Add plugin orchestration host APIs (#4114)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The plugin system is the extension path for optional capabilities
that should not require core product changes for every integration.
> - Plugins need scoped host APIs for issue orchestration, documents,
wakeups, summaries, activity attribution, and isolated database state.
> - Without those host APIs, richer plugins either cannot coordinate
Paperclip work safely or need privileged core-side special cases.
> - This pull request adds the plugin orchestration host surface, scoped
route dispatch, a database namespace layer, and a smoke plugin that
exercises the contract.
> - The benefit is a broader plugin API that remains company-scoped,
auditable, and covered by tests.
## What Changed
- Added plugin orchestration host APIs for issue creation, document
access, wakeups, summaries, plugin-origin activity, and scoped API route
dispatch.
- Added plugin database namespace tables, schema exports, migration
checks, and idempotent replay coverage under migration
`0059_plugin_database_namespaces`.
- Added shared plugin route/API types and validators used by server and
SDK boundaries.
- Expanded plugin SDK types, protocol helpers, worker RPC host behavior,
and testing utilities for orchestration flows.
- Added the `plugin-orchestration-smoke-example` package to exercise
scoped routes, restricted database namespaces, issue orchestration,
documents, wakeups, summaries, and UI status surfaces.
- Kept the new orchestration smoke fixture out of the root pnpm
workspace importer so this PR preserves the repository policy of not
committing `pnpm-lock.yaml`.
- Updated plugin docs and database docs for the new orchestration and
database namespace surfaces.
- Rebased the branch onto `public-gh/master`, resolved conflicts, and
removed `pnpm-lock.yaml` from the final PR diff.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm exec vitest run packages/db/src/client.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-scoped-api-routes.test.ts
server/src/__tests__/plugin-sdk-orchestration-contract.test.ts`
- From `packages/plugins/examples/plugin-orchestration-smoke-example`:
`pnpm exec vitest run --config ./vitest.config.ts`
- `pnpm --dir
packages/plugins/examples/plugin-orchestration-smoke-example run
typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- PR CI on latest head `293fc67c`: `policy`, `verify`, `e2e`, and
`security/snyk` all passed.
## Risks
- Medium risk: this expands plugin host authority, so route auth,
company scoping, and plugin-origin activity attribution need careful
review.
- Medium risk: database namespace migration behavior must remain
idempotent for environments that may have seen earlier branch versions.
- Medium risk: the orchestration smoke fixture is intentionally excluded
from the root workspace importer to avoid a `pnpm-lock.yaml` PR diff;
direct fixture verification remains listed above.
- Low operational risk from the PR setup itself: the branch is rebased
onto current `master`, the migration is ordered after upstream
`0057`/`0058`, and `pnpm-lock.yaml` is not in the final diff.
> 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`.
Roadmap checked: this work aligns with the completed Plugin system
milestone and extends the plugin surface rather than duplicating an
unrelated planned core feature.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in a tool-enabled CLI
environment. Exact hosted model build and context-window size are not
exposed by the runtime; reasoning/tool use were enabled for repository
inspection, editing, testing, git operations, and PR creation.
## 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 (N/A: no core UI screen change; example plugin UI contract
is covered by tests)
- [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-04-20 08:52:51 -05:00
|
|
|
? 403
|
|
|
|
|
: err instanceof JsonRpcCallError && err.code === PLUGIN_RPC_ERROR_CODES.METHOD_NOT_IMPLEMENTED
|
|
|
|
|
? 501
|
|
|
|
|
: err instanceof JsonRpcCallError
|
|
|
|
|
? 502
|
|
|
|
|
: 500;
|
|
|
|
|
res.status(status).json({
|
|
|
|
|
error: err instanceof Error ? err.message : String(err),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId
|
|
|
|
|
*
|
|
|
|
|
* Get detailed information about a single plugin.
|
|
|
|
|
*
|
|
|
|
|
* The :pluginId parameter accepts either:
|
|
|
|
|
* - Database UUID (e.g., "abc123-def456")
|
|
|
|
|
* - Plugin key (e.g., "acme.linear")
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginRecord
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enrich with worker capabilities when available
|
|
|
|
|
const worker = bridgeDeps?.workerManager.getWorker(plugin.id);
|
|
|
|
|
const supportsConfigTest = worker
|
|
|
|
|
? worker.supportedMethods.includes("validateConfig")
|
|
|
|
|
: false;
|
|
|
|
|
|
|
|
|
|
res.json({ ...plugin, supportsConfigTest });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE /api/plugins/:pluginId
|
|
|
|
|
*
|
|
|
|
|
* Uninstall a plugin.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - purge: If "true", permanently delete all plugin data (hard delete)
|
|
|
|
|
* Otherwise, soft-delete with 30-day data retention
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginRecord (the deleted record)
|
|
|
|
|
* Errors: 404 if plugin not found, 400 for lifecycle errors
|
|
|
|
|
*/
|
|
|
|
|
router.delete("/plugins/:pluginId", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const purge = req.query.purge === "true";
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await lifecycle.unload(plugin.id, purge);
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.uninstalled", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
purge,
|
|
|
|
|
});
|
|
|
|
|
publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: plugin.id, action: "uninstalled" } });
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/enable
|
|
|
|
|
*
|
|
|
|
|
* Enable a plugin that is currently disabled or in error state.
|
|
|
|
|
*
|
|
|
|
|
* Transitions the plugin to 'ready' state after loading and validation.
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginRecord
|
|
|
|
|
* Errors: 404 if plugin not found, 400 for lifecycle errors
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/enable", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await lifecycle.enable(plugin.id);
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.enabled", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
version: result?.version ?? plugin.version,
|
|
|
|
|
});
|
|
|
|
|
publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: plugin.id, action: "enabled" } });
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/disable
|
|
|
|
|
*
|
|
|
|
|
* Disable a running plugin.
|
|
|
|
|
*
|
|
|
|
|
* Request body (optional):
|
|
|
|
|
* - reason: Human-readable reason for disabling
|
|
|
|
|
*
|
|
|
|
|
* The plugin transitions to 'installed' state and stops processing events.
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginRecord
|
|
|
|
|
* Errors: 404 if plugin not found, 400 for lifecycle errors
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/disable", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const body = req.body as { reason?: string } | undefined;
|
|
|
|
|
const reason = body?.reason;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await lifecycle.disable(plugin.id, reason);
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.disabled", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
reason: reason ?? null,
|
|
|
|
|
});
|
|
|
|
|
publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: plugin.id, action: "disabled" } });
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/health
|
|
|
|
|
*
|
|
|
|
|
* Run health diagnostics on a plugin.
|
|
|
|
|
*
|
|
|
|
|
* Performs the following checks:
|
|
|
|
|
* 1. Registry: Plugin is registered in the database
|
|
|
|
|
* 2. Manifest: Manifest is valid and parseable
|
|
|
|
|
* 3. Status: Plugin is in 'ready' state
|
|
|
|
|
* 4. Error state: Plugin has no unhandled errors
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginHealthCheckResult
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/health", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const checks: PluginHealthCheckResult["checks"] = [];
|
|
|
|
|
|
|
|
|
|
// Check 1: Plugin is registered
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "registry",
|
|
|
|
|
passed: true,
|
|
|
|
|
message: "Plugin found in registry",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check 2: Manifest is valid
|
|
|
|
|
const hasValidManifest = Boolean(plugin.manifestJson?.id);
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "manifest",
|
|
|
|
|
passed: hasValidManifest,
|
|
|
|
|
message: hasValidManifest ? "Manifest is valid" : "Manifest is invalid or missing",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check 3: Plugin status
|
|
|
|
|
const isHealthy = plugin.status === "ready";
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "status",
|
|
|
|
|
passed: isHealthy,
|
|
|
|
|
message: `Current status: ${plugin.status}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check 4: No last error
|
|
|
|
|
const hasNoError = !plugin.lastError;
|
|
|
|
|
if (!hasNoError) {
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "error_state",
|
|
|
|
|
passed: false,
|
|
|
|
|
message: plugin.lastError ?? undefined,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result: PluginHealthCheckResult = {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
status: plugin.status,
|
|
|
|
|
healthy: isHealthy && hasValidManifest && hasNoError,
|
|
|
|
|
checks,
|
|
|
|
|
lastError: plugin.lastError ?? undefined,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.json(result);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/logs
|
|
|
|
|
*
|
|
|
|
|
* Query recent log entries for a plugin.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - limit: Maximum number of entries (default 25, max 500)
|
|
|
|
|
* - level: Filter by log level (info, warn, error, debug)
|
|
|
|
|
* - since: ISO timestamp to filter logs newer than this time
|
|
|
|
|
*
|
|
|
|
|
* Response: Array of log entries, newest first.
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/logs", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const limit = Math.min(Math.max(parseInt(req.query.limit as string, 10) || 25, 1), 500);
|
|
|
|
|
const level = req.query.level as string | undefined;
|
|
|
|
|
const since = req.query.since as string | undefined;
|
|
|
|
|
|
|
|
|
|
const conditions = [eq(pluginLogs.pluginId, plugin.id)];
|
|
|
|
|
if (level) {
|
|
|
|
|
conditions.push(eq(pluginLogs.level, level));
|
|
|
|
|
}
|
|
|
|
|
if (since) {
|
|
|
|
|
const sinceDate = new Date(since);
|
|
|
|
|
if (!isNaN(sinceDate.getTime())) {
|
|
|
|
|
conditions.push(gte(pluginLogs.createdAt, sinceDate));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(pluginLogs)
|
|
|
|
|
.where(and(...conditions))
|
|
|
|
|
.orderBy(desc(pluginLogs.createdAt))
|
|
|
|
|
.limit(limit);
|
|
|
|
|
|
|
|
|
|
res.json(rows);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/upgrade
|
|
|
|
|
*
|
|
|
|
|
* Upgrade a plugin to a newer version.
|
|
|
|
|
*
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [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-04-15 08:41:15 -05:00
|
|
|
* Upgrades are restricted to instance admins because they fetch and inspect
|
|
|
|
|
* new package contents on the host before activation.
|
|
|
|
|
*
|
2026-03-13 16:22:34 -05:00
|
|
|
* Request body (optional):
|
|
|
|
|
* - version: Target version (defaults to latest)
|
|
|
|
|
*
|
|
|
|
|
* If the upgrade adds new capabilities, the plugin transitions to
|
|
|
|
|
* 'upgrade_pending' state for board approval. Otherwise, it goes
|
|
|
|
|
* directly to 'ready'.
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginRecord
|
|
|
|
|
* Errors: 404 if plugin not found, 400 for lifecycle errors
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/upgrade", async (req, res) => {
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [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-04-15 08:41:15 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const body = req.body as { version?: string } | undefined;
|
|
|
|
|
const version = body?.version;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Upgrade the plugin - this would typically:
|
|
|
|
|
// 1. Download the new version
|
|
|
|
|
// 2. Compare capabilities
|
|
|
|
|
// 3. If new capabilities, mark as upgrade_pending
|
|
|
|
|
// 4. Otherwise, transition to ready
|
|
|
|
|
const result = await lifecycle.upgrade(plugin.id, version);
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.upgraded", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
previousVersion: plugin.version,
|
|
|
|
|
version: result?.version ?? plugin.version,
|
|
|
|
|
targetVersion: version ?? null,
|
|
|
|
|
});
|
|
|
|
|
publishGlobalLiveEvent({ type: "plugin.ui.updated", payload: { pluginId: plugin.id, action: "upgraded" } });
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// Plugin configuration routes
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/config
|
|
|
|
|
*
|
|
|
|
|
* Retrieve the current instance configuration for a plugin.
|
|
|
|
|
*
|
|
|
|
|
* Returns the `PluginConfig` record if one exists, or `null` if the plugin
|
|
|
|
|
* has not yet been configured.
|
|
|
|
|
*
|
|
|
|
|
* Response: `PluginConfig | null`
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/config", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const config = await registry.getConfig(plugin.id);
|
|
|
|
|
res.json(config);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/config
|
|
|
|
|
*
|
|
|
|
|
* Save (create or replace) the instance configuration for a plugin.
|
|
|
|
|
*
|
|
|
|
|
* The caller provides the full `configJson` object. The server persists it
|
|
|
|
|
* via `registry.upsertConfig()`.
|
|
|
|
|
*
|
|
|
|
|
* Request body:
|
|
|
|
|
* - `configJson`: Configuration values matching the plugin's `instanceConfigSchema`
|
|
|
|
|
*
|
|
|
|
|
* Response: `PluginConfig`
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if request validation fails
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/config", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as { configJson?: Record<string, unknown> } | undefined;
|
|
|
|
|
if (!body?.configJson || typeof body.configJson !== "object") {
|
|
|
|
|
res.status(400).json({ error: '"configJson" is required and must be an object' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Strip devUiUrl unless the caller is an instance admin. devUiUrl activates
|
|
|
|
|
// a dev-proxy in the static file route that could be abused for SSRF if any
|
|
|
|
|
// board-level user were allowed to set it.
|
|
|
|
|
if (
|
|
|
|
|
"devUiUrl" in body.configJson &&
|
|
|
|
|
!(req.actor.type === "board" && req.actor.isInstanceAdmin)
|
|
|
|
|
) {
|
|
|
|
|
delete body.configJson.devUiUrl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate configJson against the plugin's instanceConfigSchema (if declared).
|
|
|
|
|
// This ensures CLI/API callers get the same validation the UI performs client-side.
|
|
|
|
|
const schema = plugin.manifestJson?.instanceConfigSchema;
|
|
|
|
|
if (schema && Object.keys(schema).length > 0) {
|
|
|
|
|
const validation = validateInstanceConfig(body.configJson, schema);
|
|
|
|
|
if (!validation.valid) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: "Configuration does not match the plugin's instanceConfigSchema",
|
|
|
|
|
fieldErrors: validation.errors,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
Add secrets provider vaults and remote import (#5429)
## Thinking Path
> - Paperclip orchestrates AI-agent companies and needs secrets handling
to work across local development, hosted operators, and governed agent
execution.
> - The affected subsystem is the company-scoped secrets control plane:
database schema, server services/routes, CLI workflows, and the Secrets
settings UI.
> - The gap was that secrets were local-only and operators could not
manage provider vaults or import existing remote references without
exposing plaintext.
> - This branch adds provider vault configuration plus an AWS Secrets
Manager remote-import path while preserving company boundaries, binding
context, and audit trails.
> - I kept the PR to a single branch PR, removed unrelated
lockfile/package drift, rebased the full branch onto the current
`public-gh/master`, and addressed fresh Greptile findings.
> - The benefit is a reviewable implementation of provider-backed
secrets with focused tests covering provider selection, import
conflicts, deleted secret reuse, rotation guards, and AWS signing
behavior.
## What Changed
- Added provider vault support for company secrets, including provider
config storage, default vault handling, health checks, binding usage,
access events, and remote import preview/commit.
- Added an AWS Secrets Manager provider using SigV4 request signing,
bounded request timeouts, namespace guardrails, cached runtime
credential resolution, and external-reference linking without plaintext
reads.
- Added Secrets UI surfaces for vault management and remote import, plus
CLI/API documentation for setup and operations.
- Stabilized routine webhook secret binding paths and SSH
environment-driver fixture bindings discovered during verification.
- Addressed Greptile and CI findings: no lockfile/package drift,
monotonic migration metadata, disabled-vault default races, soft-deleted
secret hiding/recreate behavior, remove behavior with disabled vaults,
soft-deleted external-reference re-import, non-active rotation guards,
managed-secret soft deletion through PATCH, and per-call AWS SDK
credential client churn.
- Rebased this branch onto `public-gh/master` at `0e1a5828` and
force-pushed with lease to keep this as the single PR for the branch.
## Verification
- `git fetch public-gh master`
- `git rebase public-gh/master`
- `git diff --name-only public-gh/master...HEAD | grep
'^pnpm-lock\.yaml$' || true` confirmed `pnpm-lock.yaml` is not in the PR
diff.
- Confirmed migration ordering: master ends at `0081_optimal_dormammu`;
this PR adds `0082_dry_vision` and
`0083_company_secret_provider_configs`.
- Inspected migrations for repeat safety: new tables/indexes use `IF NOT
EXISTS`; foreign keys are guarded by `DO $$ ... IF NOT EXISTS`; column
additions use `ADD COLUMN IF NOT EXISTS`.
- `pnpm -r typecheck` passed before the Greptile follow-up commits.
- `pnpm test:run` ran the full stable Vitest path before the Greptile
follow-up commits; it completed with 3 timing-related failures under
parallel load: `codex-local-execute.test.ts`,
`cursor-local-execute.test.ts`, and `environment-service.test.ts`.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/codex-local-execute.test.ts
src/__tests__/cursor-local-execute.test.ts
src/__tests__/environment-service.test.ts` passed on targeted rerun
(`24/24`).
- `pnpm build` passed before the Greptile follow-up commits. Vite
reported existing chunk-size/dynamic-import warnings.
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/secrets-service.test.ts` passed (`26/26`).
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
exec vitest run src/__tests__/aws-secrets-manager-provider.test.ts
src/__tests__/secrets-service.test.ts` passed (`39/39`).
- After Greptile follow-up commits: `pnpm --filter @paperclipai/server
typecheck` passed.
- Captured Storybook screenshots from `ui/storybook-static` for visual
review.
- Latest PR checks on `5ca3a5cf`: `policy`, serialized server suites
1/4-4/4, `Canary Dry Run`, `e2e`, `security/snyk`, and `Greptile Review`
pass; aggregate `verify` is still registering the completed child
checks.
- Greptile review loop continued through the latest requested pass; all
Greptile review threads are resolved and the latest `Greptile Review`
check on `5ca3a5cf` passed with 0 comments added.
## Screenshots
Before: the provider-vault and remote-import surfaces did not exist on
`master`; these are after-state screenshots from the Storybook fixtures.



## Risks
- Migration risk: this adds new secret provider tables and extends
existing secret rows. The migrations were checked for monotonic ordering
and idempotent guards, but reviewers should still inspect upgrade
behavior carefully.
- Provider risk: AWS support uses direct SigV4 requests. Automated tests
cover signing, request timeouts, vault-config selection, namespace
guardrails, pending-version archival, sanitized provider errors, and
service-level cleanup paths. A real-vault AWS smoke test remains
deployment validation for an operator with AWS credentials rather than
an unverified merge blocker in this local branch.
- UI risk: the Secrets page and import dialog are large new surfaces;
screenshots are included above for reviewer inspection.
- Verification risk: the full local stable test command hit
parallel-load timing failures, although the exact failed files passed
when rerun directly.
- Operational risk: remote import intentionally avoids plaintext reads;
operators must understand that imported external references resolve at
runtime and may fail if AWS permissions change.
> 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 coding agent with local shell/tool use in the
Paperclip worktree. Exact context-window size 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
- [ ] 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>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:22:17 -05:00
|
|
|
const secretRefsByPath = extractSecretRefPathsFromConfig(body.configJson, schema);
|
|
|
|
|
if (secretRefsByPath.size > 0) {
|
|
|
|
|
res.status(422).json({ error: PLUGIN_SECRET_REFS_DISABLED_MESSAGE });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
const result = await registry.upsertConfig(plugin.id, {
|
|
|
|
|
configJson: body.configJson,
|
|
|
|
|
});
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.config.updated", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
configKeyCount: Object.keys(body.configJson).length,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Notify the running worker about the config change (PLUGIN_SPEC §25.4.4).
|
|
|
|
|
// If the worker implements onConfigChanged, send the new config via RPC.
|
|
|
|
|
// If it doesn't (METHOD_NOT_IMPLEMENTED), restart the worker so it picks
|
|
|
|
|
// up the new config on re-initialize. If no worker is running, skip.
|
|
|
|
|
if (bridgeDeps?.workerManager.isRunning(plugin.id)) {
|
|
|
|
|
try {
|
|
|
|
|
await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"configChanged",
|
|
|
|
|
{ config: body.configJson },
|
|
|
|
|
);
|
|
|
|
|
} catch (rpcErr) {
|
|
|
|
|
if (
|
|
|
|
|
rpcErr instanceof JsonRpcCallError &&
|
|
|
|
|
rpcErr.code === PLUGIN_RPC_ERROR_CODES.METHOD_NOT_IMPLEMENTED
|
|
|
|
|
) {
|
|
|
|
|
// Worker doesn't handle live config — restart it.
|
|
|
|
|
try {
|
|
|
|
|
await lifecycle.restartWorker(plugin.id);
|
|
|
|
|
} catch {
|
|
|
|
|
// Restart failure is non-fatal for the config save response.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Other RPC errors (timeout, unavailable) are non-fatal — config is
|
|
|
|
|
// already persisted and will take effect on next worker restart.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/config/test
|
|
|
|
|
*
|
|
|
|
|
* Test a plugin configuration without persisting it by calling the plugin
|
|
|
|
|
* worker's `validateConfig` RPC method.
|
|
|
|
|
*
|
|
|
|
|
* Only works when the plugin's worker implements `onValidateConfig`.
|
|
|
|
|
* If the worker does not implement the method, returns
|
|
|
|
|
* `{ valid: false, supported: false, message: "..." }` with HTTP 200.
|
|
|
|
|
*
|
|
|
|
|
* Request body:
|
|
|
|
|
* - `configJson`: Configuration values to validate
|
|
|
|
|
*
|
|
|
|
|
* Response: `{ valid: boolean; message?: string; supported?: boolean }`
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 400 if request validation fails
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 501 if bridge deps (worker manager) are not configured
|
|
|
|
|
* - 502 if the worker is unavailable
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/config/test", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
|
|
|
|
|
if (!bridgeDeps) {
|
|
|
|
|
res.status(501).json({ error: "Plugin bridge is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as { configJson?: Record<string, unknown> } | undefined;
|
|
|
|
|
if (!body?.configJson || typeof body.configJson !== "object") {
|
|
|
|
|
res.status(400).json({ error: '"configJson" is required and must be an object' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fast schema-level rejection before hitting the worker RPC.
|
|
|
|
|
const schema = plugin.manifestJson?.instanceConfigSchema;
|
|
|
|
|
if (schema && Object.keys(schema).length > 0) {
|
|
|
|
|
const validation = validateInstanceConfig(body.configJson, schema);
|
|
|
|
|
if (!validation.valid) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: "Configuration does not match the plugin's instanceConfigSchema",
|
|
|
|
|
fieldErrors: validation.errors,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridgeDeps.workerManager.call(
|
|
|
|
|
plugin.id,
|
|
|
|
|
"validateConfig",
|
|
|
|
|
{ config: body.configJson },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// The worker returns PluginConfigValidationResult { ok, warnings?, errors? }
|
|
|
|
|
// Map to the frontend-expected shape { valid, message? }
|
|
|
|
|
if (result.ok) {
|
|
|
|
|
const warningText = result.warnings?.length
|
|
|
|
|
? `Warnings: ${result.warnings.join("; ")}`
|
|
|
|
|
: undefined;
|
|
|
|
|
res.json({ valid: true, message: warningText });
|
|
|
|
|
} else {
|
|
|
|
|
const errorText = result.errors?.length
|
|
|
|
|
? result.errors.join("; ")
|
|
|
|
|
: "Configuration validation failed.";
|
|
|
|
|
res.json({ valid: false, message: errorText });
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// If the worker does not implement validateConfig, return a structured response
|
|
|
|
|
if (
|
|
|
|
|
err instanceof JsonRpcCallError &&
|
|
|
|
|
err.code === PLUGIN_RPC_ERROR_CODES.METHOD_NOT_IMPLEMENTED
|
|
|
|
|
) {
|
|
|
|
|
res.json({
|
|
|
|
|
valid: false,
|
|
|
|
|
supported: false,
|
|
|
|
|
message: "This plugin does not support configuration testing.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Worker unavailable or other RPC errors
|
|
|
|
|
const bridgeError = mapRpcErrorToBridgeError(err);
|
|
|
|
|
res.status(502).json(bridgeError);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// Job scheduling routes
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/jobs
|
|
|
|
|
*
|
|
|
|
|
* List all scheduled jobs for a plugin.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - `status` (optional): Filter by job status (`active`, `paused`, `failed`)
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginJobRecord[]
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/jobs", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
if (!jobDeps) {
|
|
|
|
|
res.status(501).json({ error: "Job scheduling is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rawStatus = req.query.status as string | undefined;
|
|
|
|
|
const validStatuses = ["active", "paused", "failed"];
|
|
|
|
|
if (rawStatus !== undefined && !validStatuses.includes(rawStatus)) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: `Invalid status '${rawStatus}'. Must be one of: ${validStatuses.join(", ")}`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const jobs = await jobDeps.jobStore.listJobs(
|
|
|
|
|
plugin.id,
|
|
|
|
|
rawStatus as "active" | "paused" | "failed" | undefined,
|
|
|
|
|
);
|
|
|
|
|
res.json(jobs);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(500).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/jobs/:jobId/runs
|
|
|
|
|
*
|
|
|
|
|
* List execution history for a specific job.
|
|
|
|
|
*
|
|
|
|
|
* Query params:
|
|
|
|
|
* - `limit` (optional): Maximum number of runs to return (default: 50)
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginJobRunRecord[]
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/jobs/:jobId/runs", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
if (!jobDeps) {
|
|
|
|
|
res.status(501).json({ error: "Job scheduling is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, jobId } = req.params;
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const job = await jobDeps.jobStore.getJobByIdForPlugin(plugin.id, jobId);
|
|
|
|
|
if (!job) {
|
|
|
|
|
res.status(404).json({ error: "Job not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 25;
|
|
|
|
|
if (isNaN(limit) || limit < 1 || limit > 500) {
|
|
|
|
|
res.status(400).json({ error: "limit must be a number between 1 and 500" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const runs = await jobDeps.jobStore.listRunsByJob(jobId, limit);
|
|
|
|
|
res.json(runs);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(500).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/jobs/:jobId/trigger
|
|
|
|
|
*
|
|
|
|
|
* Manually trigger a job execution outside its cron schedule.
|
|
|
|
|
*
|
|
|
|
|
* Creates a run with `trigger: "manual"` and dispatches immediately.
|
|
|
|
|
* The response returns before the job completes (non-blocking).
|
|
|
|
|
*
|
|
|
|
|
* Response: `{ runId: string, jobId: string }`
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 404 if plugin not found
|
|
|
|
|
* - 400 if job not found, not active, already running, or worker unavailable
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/jobs/:jobId/trigger", async (req, res) => {
|
Harden API route authorization boundaries (#4122)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The REST API is the control-plane boundary for companies, agents,
plugins, adapters, costs, invites, and issue mutations.
> - Several routes still relied on broad board or company access checks
without consistently enforcing the narrower actor, company, and
active-checkout boundaries those operations require.
> - That can allow agents or non-admin users to mutate sensitive
resources outside the intended governance path.
> - This pull request hardens the route authorization layer and adds
regression coverage for the audited API surfaces.
> - The benefit is tighter multi-company isolation, safer plugin and
adapter administration, and stronger enforcement of active issue
ownership.
## What Changed
- Added route-level authorization checks for budgets, plugin
administration/scoped routes, adapter management, company import/export,
direct agent creation, invite test resolution, and issue mutation/write
surfaces.
- Enforced active checkout ownership for agent-authenticated issue
mutations, while preserving explicit management overrides for permitted
managers.
- Restricted sensitive adapter and plugin management operations to
instance-admin or properly scoped actors.
- Tightened company portability and invite probing routes so agents
cannot cross company boundaries.
- Updated access constants and the Company Access UI copy for the new
active-checkout management grant.
- Added focused regression tests covering cross-company denial, agent
self-mutation denial, admin-only operations, and active checkout
ownership.
- Rebased the branch onto `public-gh/master` and fixed validation
fallout from the rebase: heartbeat-context route ordering and a company
import/export e2e fixture that now opts out of direct-hire approval
before using direct agent creation.
- Updated onboarding and signoff e2e setup to create seed agents through
`/agent-hires` plus board approval, so they remain compatible with the
approval-gated new-agent default.
- Addressed Greptile feedback by removing a duplicate company export API
alias, avoiding N+1 reporting-chain lookups in active-checkout override
checks, allowing agent mutations on unassigned `in_progress` issues, and
blocking NAT64 invite-probe targets.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issues-goal-context-routes.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/adapter-routes-authz.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/invite-test-resolution-route.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/agent-adapter-validation-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/invite-test-resolution-route.test.ts`
- `pnpm -r typecheck`
- `pnpm --filter server typecheck`
- `pnpm --filter ui typecheck`
- `pnpm build`
- `pnpm test:e2e -- tests/e2e/onboarding.spec.ts
tests/e2e/signoff-policy.spec.ts`
- `pnpm test:e2e -- tests/e2e/signoff-policy.spec.ts`
- `pnpm test:run` was also run. It failed under default full-suite
parallelism with two order-dependent failures in
`plugin-routes-authz.test.ts` and `routines-e2e.test.ts`; both files
passed when rerun directly together with `pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/routines-e2e.test.ts`.
## Risks
- Medium risk: this changes authorization behavior across multiple
sensitive API surfaces, so callers that depended on broad board/company
access may now receive `403` or `409` until they use the correct
governance path.
- Direct agent creation now respects the company-level board-approval
requirement; integrations that need pending hires should use
`/api/companies/:companyId/agent-hires`.
- Active in-progress issue mutations now require checkout ownership or
an explicit management override, which may reveal workflow assumptions
in older automation.
> 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 coding agent, tool-using workflow with local shell,
Git, GitHub CLI, and repository tests.
## 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
- [ ] 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-04-20 10:56:48 -05:00
|
|
|
assertInstanceAdmin(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
if (!jobDeps) {
|
|
|
|
|
res.status(501).json({ error: "Job scheduling is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, jobId } = req.params;
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const job = await jobDeps.jobStore.getJobByIdForPlugin(plugin.id, jobId);
|
|
|
|
|
if (!job) {
|
|
|
|
|
res.status(404).json({ error: "Job not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await jobDeps.scheduler.triggerJob(jobId, "manual");
|
|
|
|
|
res.json(result);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
|
|
|
res.status(400).json({ error: message });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
// Webhook ingestion route
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/plugins/:pluginId/webhooks/:endpointKey
|
|
|
|
|
*
|
|
|
|
|
* Receive an inbound webhook delivery for a plugin.
|
|
|
|
|
*
|
|
|
|
|
* This route is called by external systems (e.g. GitHub, Linear, Stripe) to
|
|
|
|
|
* deliver webhook payloads to a plugin. The host validates that:
|
|
|
|
|
* 1. The plugin exists and is in 'ready' state
|
|
|
|
|
* 2. The plugin declares the `webhooks.receive` capability
|
|
|
|
|
* 3. The `endpointKey` matches a declared webhook in the manifest
|
|
|
|
|
*
|
|
|
|
|
* The delivery is recorded in the `plugin_webhook_deliveries` table and
|
|
|
|
|
* dispatched to the worker via the `handleWebhook` RPC method.
|
|
|
|
|
*
|
|
|
|
|
* **Note:** This route does NOT require board authentication — webhook
|
|
|
|
|
* endpoints must be publicly accessible for external callers. Signature
|
|
|
|
|
* verification is the plugin's responsibility.
|
|
|
|
|
*
|
|
|
|
|
* Response: `{ deliveryId: string, status: string }`
|
|
|
|
|
* Errors:
|
|
|
|
|
* - 404 if plugin not found or endpointKey not declared
|
|
|
|
|
* - 400 if plugin is not in ready state or lacks webhooks.receive capability
|
|
|
|
|
* - 502 if the worker is unavailable or the RPC call fails
|
|
|
|
|
*/
|
|
|
|
|
router.post("/plugins/:pluginId/webhooks/:endpointKey", async (req, res) => {
|
|
|
|
|
if (!webhookDeps) {
|
|
|
|
|
res.status(501).json({ error: "Webhook ingestion is not enabled" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { pluginId, endpointKey } = req.params;
|
|
|
|
|
|
|
|
|
|
// Step 1: Resolve the plugin
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 2: Validate the plugin is in 'ready' state
|
|
|
|
|
if (plugin.status !== "ready") {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: `Plugin is not ready (current status: ${plugin.status})`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 3: Validate the plugin has webhooks.receive capability
|
|
|
|
|
const manifest = plugin.manifestJson;
|
|
|
|
|
if (!manifest) {
|
|
|
|
|
res.status(400).json({ error: "Plugin manifest is missing" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const capabilities = manifest.capabilities ?? [];
|
|
|
|
|
if (!capabilities.includes("webhooks.receive")) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
error: "Plugin does not have the webhooks.receive capability",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 4: Validate the endpointKey exists in the manifest's webhook declarations
|
|
|
|
|
const declaredWebhooks = manifest.webhooks ?? [];
|
|
|
|
|
const webhookDecl = declaredWebhooks.find(
|
|
|
|
|
(w) => w.endpointKey === endpointKey,
|
|
|
|
|
);
|
|
|
|
|
if (!webhookDecl) {
|
|
|
|
|
res.status(404).json({
|
|
|
|
|
error: `Webhook endpoint '${endpointKey}' is not declared by this plugin`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 5: Extract request data
|
|
|
|
|
const requestId = randomUUID();
|
|
|
|
|
const rawHeaders: Record<string, string> = {};
|
|
|
|
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
rawHeaders[key] = value;
|
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
|
rawHeaders[key] = value.join(", ");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use the raw buffer stashed by the express.json() `verify` callback.
|
|
|
|
|
// This preserves the exact bytes the provider signed, whereas
|
|
|
|
|
// JSON.stringify(req.body) would re-serialize and break HMAC verification.
|
|
|
|
|
const stashedRaw = (req as unknown as { rawBody?: Buffer }).rawBody;
|
|
|
|
|
const rawBody = stashedRaw ? stashedRaw.toString("utf-8") : "";
|
|
|
|
|
const parsedBody = req.body as unknown;
|
|
|
|
|
const payload = (req.body as Record<string, unknown> | undefined) ?? {};
|
|
|
|
|
|
|
|
|
|
// Step 6: Record the delivery in the database
|
|
|
|
|
const startedAt = new Date();
|
|
|
|
|
const [delivery] = await db
|
|
|
|
|
.insert(pluginWebhookDeliveries)
|
|
|
|
|
.values({
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
webhookKey: endpointKey,
|
|
|
|
|
status: "pending",
|
|
|
|
|
payload,
|
|
|
|
|
headers: rawHeaders,
|
|
|
|
|
startedAt,
|
|
|
|
|
})
|
|
|
|
|
.returning({ id: pluginWebhookDeliveries.id });
|
|
|
|
|
|
|
|
|
|
// Step 7: Dispatch to the worker via handleWebhook RPC
|
|
|
|
|
try {
|
|
|
|
|
await webhookDeps.workerManager.call(plugin.id, "handleWebhook", {
|
|
|
|
|
endpointKey,
|
|
|
|
|
headers: req.headers as Record<string, string | string[]>,
|
|
|
|
|
rawBody,
|
|
|
|
|
parsedBody,
|
|
|
|
|
requestId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Step 8: Update delivery record to success
|
|
|
|
|
const finishedAt = new Date();
|
|
|
|
|
const durationMs = finishedAt.getTime() - startedAt.getTime();
|
|
|
|
|
await db
|
|
|
|
|
.update(pluginWebhookDeliveries)
|
|
|
|
|
.set({
|
|
|
|
|
status: "success",
|
|
|
|
|
durationMs,
|
|
|
|
|
finishedAt,
|
|
|
|
|
})
|
|
|
|
|
.where(eq(pluginWebhookDeliveries.id, delivery.id));
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
deliveryId: delivery.id,
|
|
|
|
|
status: "success",
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// Step 8 (error): Update delivery record to failed
|
|
|
|
|
const finishedAt = new Date();
|
|
|
|
|
const durationMs = finishedAt.getTime() - startedAt.getTime();
|
|
|
|
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
|
|
|
|
|
|
|
|
await db
|
|
|
|
|
.update(pluginWebhookDeliveries)
|
|
|
|
|
.set({
|
|
|
|
|
status: "failed",
|
|
|
|
|
durationMs,
|
|
|
|
|
error: errorMessage,
|
|
|
|
|
finishedAt,
|
|
|
|
|
})
|
|
|
|
|
.where(eq(pluginWebhookDeliveries.id, delivery.id));
|
|
|
|
|
|
|
|
|
|
res.status(502).json({
|
|
|
|
|
deliveryId: delivery.id,
|
|
|
|
|
status: "failed",
|
|
|
|
|
error: errorMessage,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Expand plugin host surface (#5205)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The plugin system is the extension boundary for optional product
capabilities
> - Rich plugins need more than a worker entrypoint: they need scoped
database storage, local project folders, managed agents/routines, host
navigation, and reusable UI components
> - The LLM Wiki work exposed those missing host surfaces while keeping
plugin code outside the core control plane
> - This pull request expands the core plugin host, SDK, server APIs,
and UI bridge so plugins can declare and use those surfaces
> - The benefit is that future plugins can integrate with Paperclip
through documented, validated contracts instead of bespoke server or UI
imports
## What Changed
- Added plugin-managed database namespaces and migration tracking,
including Drizzle schema/migration files and SQL validation for
namespace isolation.
- Added server support for plugin local folders, managed agents, managed
routines, scoped plugin APIs, and plugin operation visibility.
- Expanded shared plugin manifest/types/validators and SDK
host/testing/UI exports for richer plugin surfaces.
- Added reusable UI pieces for file trees, managed routines, resizable
sidebars, route sidebars, and plugin bridge initialization.
- Updated plugin docs and example plugins to use the expanded host and
SDK surface.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
packages/shared/src/validators/plugin.test.ts
server/src/__tests__/plugin-database.test.ts
server/src/__tests__/plugin-local-folders.test.ts
server/src/__tests__/plugin-managed-agents.test.ts
server/src/__tests__/plugin-managed-routines.test.ts
server/src/__tests__/plugin-orchestration-apis.test.ts
ui/src/api/plugins.test.ts ui/src/components/FileTree.test.tsx
ui/src/components/ResizableSidebarPane.test.tsx
ui/src/pages/PluginPage.test.tsx ui/src/plugins/bridge.test.ts` passed:
11 files, 67 tests.
- Confirmed this PR changes 89 files and does not include
`pnpm-lock.yaml` or `.github/workflows/*`.
## Risks
- Medium: this expands plugin host contracts across db/shared/server/ui
and includes a new core migration (`0076_useful_elektra.sql`).
- The plugin database namespace validator is intentionally restrictive;
plugin authors may need follow-up affordances for SQL patterns that
remain blocked.
- Merge this before the LLM Wiki plugin PR so the plugin can resolve the
new SDK and host APIs.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window size 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-05 07:42:57 -05:00
|
|
|
// ===========================================================================
|
|
|
|
|
// Company-scoped trusted local folders
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
router.get("/plugins/:pluginId/companies/:companyId/local-folders", async (req, res) => {
|
|
|
|
|
assertBoardOrgAccess(req);
|
|
|
|
|
const { pluginId, companyId } = req.params;
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const settings = await registry.getCompanySettings(plugin.id, companyId);
|
|
|
|
|
const storedFolders = getStoredLocalFolders(settings?.settingsJson);
|
|
|
|
|
const declarations = plugin.manifestJson.localFolders ?? [];
|
|
|
|
|
const folderKeys = declarations.map((declaration) => declaration.folderKey);
|
|
|
|
|
|
|
|
|
|
const statuses = await Promise.all(folderKeys.map((folderKey) =>
|
|
|
|
|
inspectPluginLocalFolder({
|
|
|
|
|
folderKey,
|
|
|
|
|
declaration: findLocalFolderDeclaration(declarations, folderKey),
|
|
|
|
|
storedConfig: storedFolders[folderKey] ?? null,
|
|
|
|
|
})));
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
companyId,
|
|
|
|
|
declarations,
|
|
|
|
|
folders: statuses,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.get("/plugins/:pluginId/companies/:companyId/local-folders/:folderKey/status", async (req, res) => {
|
|
|
|
|
assertBoardOrgAccess(req);
|
|
|
|
|
const { pluginId, companyId, folderKey } = req.params;
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const settings = await registry.getCompanySettings(plugin.id, companyId);
|
|
|
|
|
const storedFolders = getStoredLocalFolders(settings?.settingsJson);
|
|
|
|
|
const declarations = plugin.manifestJson.localFolders ?? [];
|
|
|
|
|
const declaration = requireLocalFolderDeclaration(declarations, folderKey);
|
|
|
|
|
const status = await inspectPluginLocalFolder({
|
|
|
|
|
folderKey,
|
|
|
|
|
declaration,
|
|
|
|
|
storedConfig: storedFolders[folderKey] ?? null,
|
|
|
|
|
});
|
|
|
|
|
res.json(status);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.post("/plugins/:pluginId/companies/:companyId/local-folders/:folderKey/validate", async (req, res) => {
|
|
|
|
|
assertBoardOrgAccess(req);
|
|
|
|
|
const { pluginId, companyId, folderKey } = req.params;
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as {
|
|
|
|
|
path?: unknown;
|
|
|
|
|
access?: "read" | "readWrite";
|
|
|
|
|
requiredDirectories?: string[];
|
|
|
|
|
requiredFiles?: string[];
|
|
|
|
|
} | undefined;
|
|
|
|
|
if (typeof body?.path !== "string" || body.path.trim().length === 0) {
|
|
|
|
|
res.status(400).json({ error: '"path" is required and must be a non-empty string' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const declaration = requireLocalFolderDeclaration(plugin.manifestJson.localFolders ?? [], folderKey);
|
|
|
|
|
const status = await inspectPluginLocalFolder({
|
|
|
|
|
folderKey,
|
|
|
|
|
declaration,
|
|
|
|
|
overrideConfig: {
|
|
|
|
|
path: body.path,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
res.json(status);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.put("/plugins/:pluginId/companies/:companyId/local-folders/:folderKey", async (req, res) => {
|
|
|
|
|
assertBoardOrgAccess(req);
|
|
|
|
|
const { pluginId, companyId, folderKey } = req.params;
|
|
|
|
|
assertCompanyAccess(req, companyId);
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = req.body as {
|
|
|
|
|
path?: unknown;
|
|
|
|
|
access?: "read" | "readWrite";
|
|
|
|
|
requiredDirectories?: string[];
|
|
|
|
|
requiredFiles?: string[];
|
|
|
|
|
} | undefined;
|
|
|
|
|
if (typeof body?.path !== "string" || body.path.trim().length === 0) {
|
|
|
|
|
res.status(400).json({ error: '"path" is required and must be a non-empty string' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await registry.getCompanySettings(plugin.id, companyId);
|
|
|
|
|
const declaration = requireLocalFolderDeclaration(plugin.manifestJson.localFolders ?? [], folderKey);
|
|
|
|
|
const status = await inspectPluginLocalFolder({
|
|
|
|
|
folderKey,
|
|
|
|
|
declaration,
|
|
|
|
|
storedConfig: getStoredLocalFolders(existing?.settingsJson)[folderKey] ?? null,
|
|
|
|
|
overrideConfig: {
|
|
|
|
|
path: body.path,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const nextSettings = setStoredLocalFolder(existing?.settingsJson, folderKey, {
|
|
|
|
|
path: body.path,
|
|
|
|
|
access: status.access,
|
|
|
|
|
requiredDirectories: status.requiredDirectories,
|
|
|
|
|
requiredFiles: status.requiredFiles,
|
|
|
|
|
});
|
|
|
|
|
await registry.upsertCompanySettings(plugin.id, companyId, {
|
|
|
|
|
enabled: existing?.enabled ?? true,
|
|
|
|
|
settingsJson: nextSettings,
|
|
|
|
|
lastError: status.healthy ? null : status.problems.map((item: { message: string }) => item.message).join("; "),
|
|
|
|
|
});
|
|
|
|
|
await logPluginMutationActivity(req, "plugin.local_folder.configured", plugin.id, {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
pluginKey: plugin.pluginKey,
|
|
|
|
|
companyId,
|
|
|
|
|
folderKey,
|
|
|
|
|
healthy: status.healthy,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json(status);
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
// ===========================================================================
|
|
|
|
|
// Plugin health dashboard — aggregated diagnostics for the settings page
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/plugins/:pluginId/dashboard
|
|
|
|
|
*
|
|
|
|
|
* Aggregated health dashboard data for a plugin's settings page.
|
|
|
|
|
*
|
|
|
|
|
* Returns worker diagnostics (status, uptime, crash history), recent job
|
|
|
|
|
* runs, recent webhook deliveries, and the current health check result —
|
|
|
|
|
* all in a single response to avoid multiple round-trips.
|
|
|
|
|
*
|
|
|
|
|
* Response: PluginDashboardData
|
|
|
|
|
* Errors: 404 if plugin not found
|
|
|
|
|
*/
|
|
|
|
|
router.get("/plugins/:pluginId/dashboard", async (req, res) => {
|
feat: implement multi-user access and invite flows (#3784)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.
## What Changed
- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.
## Verification
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.
## Risks
- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.
## Model Used
- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were 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 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
Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.
---------
Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00
|
|
|
assertBoardOrgAccess(req);
|
2026-03-13 16:22:34 -05:00
|
|
|
const { pluginId } = req.params;
|
|
|
|
|
|
|
|
|
|
const plugin = await resolvePlugin(registry, pluginId);
|
|
|
|
|
if (!plugin) {
|
|
|
|
|
res.status(404).json({ error: "Plugin not found" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Worker diagnostics ---
|
|
|
|
|
let worker: {
|
|
|
|
|
status: string;
|
|
|
|
|
pid: number | null;
|
|
|
|
|
uptime: number | null;
|
|
|
|
|
consecutiveCrashes: number;
|
|
|
|
|
totalCrashes: number;
|
|
|
|
|
pendingRequests: number;
|
|
|
|
|
lastCrashAt: number | null;
|
|
|
|
|
nextRestartAt: number | null;
|
|
|
|
|
} | null = null;
|
|
|
|
|
|
|
|
|
|
// Try bridgeDeps first (primary source for worker manager), fallback to webhookDeps
|
|
|
|
|
const wm = bridgeDeps?.workerManager ?? webhookDeps?.workerManager ?? null;
|
|
|
|
|
if (wm) {
|
|
|
|
|
const handle = wm.getWorker(plugin.id);
|
|
|
|
|
if (handle) {
|
|
|
|
|
const diag = handle.diagnostics();
|
|
|
|
|
worker = {
|
|
|
|
|
status: diag.status,
|
|
|
|
|
pid: diag.pid,
|
|
|
|
|
uptime: diag.uptime,
|
|
|
|
|
consecutiveCrashes: diag.consecutiveCrashes,
|
|
|
|
|
totalCrashes: diag.totalCrashes,
|
|
|
|
|
pendingRequests: diag.pendingRequests,
|
|
|
|
|
lastCrashAt: diag.lastCrashAt,
|
|
|
|
|
nextRestartAt: diag.nextRestartAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Recent job runs (last 10, newest first) ---
|
|
|
|
|
let recentJobRuns: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
jobId: string;
|
|
|
|
|
jobKey?: string;
|
|
|
|
|
trigger: string;
|
|
|
|
|
status: string;
|
|
|
|
|
durationMs: number | null;
|
|
|
|
|
error: string | null;
|
|
|
|
|
startedAt: string | null;
|
|
|
|
|
finishedAt: string | null;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
|
|
if (jobDeps) {
|
|
|
|
|
try {
|
|
|
|
|
const runs = await jobDeps.jobStore.listRunsByPlugin(plugin.id, undefined, 10);
|
|
|
|
|
// Also fetch job definitions so we can include jobKey
|
|
|
|
|
const jobs = await jobDeps.jobStore.listJobs(plugin.id);
|
|
|
|
|
const jobKeyMap = new Map(jobs.map((j) => [j.id, j.jobKey]));
|
|
|
|
|
|
|
|
|
|
recentJobRuns = runs
|
|
|
|
|
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
|
|
|
.map((r) => ({
|
|
|
|
|
id: r.id,
|
|
|
|
|
jobId: r.jobId,
|
|
|
|
|
jobKey: jobKeyMap.get(r.jobId) ?? undefined,
|
|
|
|
|
trigger: r.trigger,
|
|
|
|
|
status: r.status,
|
|
|
|
|
durationMs: r.durationMs,
|
|
|
|
|
error: r.error,
|
|
|
|
|
startedAt: r.startedAt ? new Date(r.startedAt).toISOString() : null,
|
|
|
|
|
finishedAt: r.finishedAt ? new Date(r.finishedAt).toISOString() : null,
|
|
|
|
|
createdAt: new Date(r.createdAt).toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
} catch {
|
|
|
|
|
// Job data unavailable — leave empty
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Recent webhook deliveries (last 10, newest first) ---
|
|
|
|
|
let recentWebhookDeliveries: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
webhookKey: string;
|
|
|
|
|
status: string;
|
|
|
|
|
durationMs: number | null;
|
|
|
|
|
error: string | null;
|
|
|
|
|
startedAt: string | null;
|
|
|
|
|
finishedAt: string | null;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const deliveries = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: pluginWebhookDeliveries.id,
|
|
|
|
|
webhookKey: pluginWebhookDeliveries.webhookKey,
|
|
|
|
|
status: pluginWebhookDeliveries.status,
|
|
|
|
|
durationMs: pluginWebhookDeliveries.durationMs,
|
|
|
|
|
error: pluginWebhookDeliveries.error,
|
|
|
|
|
startedAt: pluginWebhookDeliveries.startedAt,
|
|
|
|
|
finishedAt: pluginWebhookDeliveries.finishedAt,
|
|
|
|
|
createdAt: pluginWebhookDeliveries.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(pluginWebhookDeliveries)
|
|
|
|
|
.where(eq(pluginWebhookDeliveries.pluginId, plugin.id))
|
|
|
|
|
.orderBy(desc(pluginWebhookDeliveries.createdAt))
|
|
|
|
|
.limit(10);
|
|
|
|
|
|
|
|
|
|
recentWebhookDeliveries = deliveries.map((d) => ({
|
|
|
|
|
id: d.id,
|
|
|
|
|
webhookKey: d.webhookKey,
|
|
|
|
|
status: d.status,
|
|
|
|
|
durationMs: d.durationMs,
|
|
|
|
|
error: d.error,
|
|
|
|
|
startedAt: d.startedAt ? d.startedAt.toISOString() : null,
|
|
|
|
|
finishedAt: d.finishedAt ? d.finishedAt.toISOString() : null,
|
|
|
|
|
createdAt: d.createdAt.toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
} catch {
|
|
|
|
|
// Webhook data unavailable — leave empty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Health check (same logic as GET /health) ---
|
|
|
|
|
const checks: PluginHealthCheckResult["checks"] = [];
|
|
|
|
|
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "registry",
|
|
|
|
|
passed: true,
|
|
|
|
|
message: "Plugin found in registry",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const hasValidManifest = Boolean(plugin.manifestJson?.id);
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "manifest",
|
|
|
|
|
passed: hasValidManifest,
|
|
|
|
|
message: hasValidManifest ? "Manifest is valid" : "Manifest is invalid or missing",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isHealthy = plugin.status === "ready";
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "status",
|
|
|
|
|
passed: isHealthy,
|
|
|
|
|
message: `Current status: ${plugin.status}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const hasNoError = !plugin.lastError;
|
|
|
|
|
if (!hasNoError) {
|
|
|
|
|
checks.push({
|
|
|
|
|
name: "error_state",
|
|
|
|
|
passed: false,
|
|
|
|
|
message: plugin.lastError ?? undefined,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const health: PluginHealthCheckResult = {
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
status: plugin.status,
|
|
|
|
|
healthy: isHealthy && hasValidManifest && hasNoError,
|
|
|
|
|
checks,
|
|
|
|
|
lastError: plugin.lastError ?? undefined,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
pluginId: plugin.id,
|
|
|
|
|
worker,
|
|
|
|
|
recentJobRuns,
|
|
|
|
|
recentWebhookDeliveries,
|
|
|
|
|
health,
|
|
|
|
|
checkedAt: new Date().toISOString(),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return router;
|
|
|
|
|
}
|