Files
PaperClipAI/packages/plugins/sdk/src/index.ts
T
Dotta a1835cfa5e [codex] Harden plugin runtime invocation scope (#6547)
## Thinking Path

> - Paperclip orchestrates AI-agent companies through a company-scoped
control plane.
> - Plugins extend that control plane, but plugin workers still call
back into host APIs.
> - Those worker-to-host calls need the same company boundary guarantees
as normal API routes.
> - Plugin action handlers also need authenticated actor context from
the host instead of trusting caller-supplied params.
> - This pull request hardens plugin bridge/action scope and keeps
plugin operation issues out of normal issue surfaces.
> - The benefit is safer plugin execution with clearer authorization
boundaries and better test coverage.

## What Changed

- Added host-owned invocation context plumbing for nested plugin worker
calls.
- Added actor context to plugin `performAction` calls and test harness
helpers.
- Enforced company invocation scope on worker-to-host calls and filtered
company lists to the active invocation scope.
- Extended plugin action route tests for board and agent actor context,
spoofed company params, and cross-company rejection.
- Extended plugin worker manager coverage for invocation-scope
propagation.
- Filtered typed and legacy plugin operation issue origins from default
issue/inbox lists.

## Verification

- `pnpm --filter @paperclipai/plugin-sdk build`
- `NODE_ENV=test pnpm exec vitest run
packages/plugins/sdk/tests/host-client-factory.test.ts
packages/plugins/sdk/tests/testing-actions.test.ts
server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-worker-manager.test.ts
server/src/__tests__/issues-service.test.ts`

Note: embedded Postgres issue-service tests reported host-level Postgres
init skip for 47 tests; the non-embedded targeted tests passed.

## Risks

- Medium: plugin host authorization paths are sensitive, and external
plugins may rely on previously loose company params.
- Mitigation: the change only tightens calls when the host attached a
company invocation scope and includes explicit tests for board, agent,
and nested worker calls.

> 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 via `codex_local`, tool-enabled coding session;
exact context window not exposed by 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
2026-05-22 09:16:24 -05:00

393 lines
11 KiB
TypeScript

/**
* `@paperclipai/plugin-sdk` — Paperclip plugin worker-side SDK.
*
* This is the main entrypoint for plugin worker code. For plugin UI bundles,
* import from `@paperclipai/plugin-sdk/ui` instead.
*
* @example
* ```ts
* // Plugin worker entrypoint (dist/worker.ts)
* import { definePlugin, runWorker, z } from "@paperclipai/plugin-sdk";
*
* const plugin = definePlugin({
* async setup(ctx) {
* ctx.logger.info("Plugin starting up");
*
* ctx.events.on("issue.created", async (event) => {
* ctx.logger.info("Issue created", { issueId: event.entityId });
* });
*
* ctx.jobs.register("full-sync", async (job) => {
* ctx.logger.info("Starting full sync", { runId: job.runId });
* // ... sync implementation
* });
*
* ctx.data.register("sync-health", async ({ companyId }) => {
* const state = await ctx.state.get({
* scopeKind: "company",
* scopeId: String(companyId),
* stateKey: "last-sync-at",
* });
* return { lastSync: state };
* });
* },
*
* async onHealth() {
* return { status: "ok" };
* },
* });
*
* export default plugin;
* runWorker(plugin, import.meta.url);
* ```
*
* @see PLUGIN_SPEC.md §14 — SDK Surface
* @see PLUGIN_SPEC.md §29.2 — SDK Versioning
*/
// ---------------------------------------------------------------------------
// Main factory
// ---------------------------------------------------------------------------
export { definePlugin } from "./define-plugin.js";
export { createTestHarness, createEnvironmentTestHarness, createFakeEnvironmentDriver, filterEnvironmentEvents, assertEnvironmentEventOrder, assertLeaseLifecycle, assertWorkspaceRealizationLifecycle, assertExecutionLifecycle, assertEnvironmentError } from "./testing.js";
export { createPluginBundlerPresets } from "./bundlers.js";
export { startPluginDevServer, getUiBuildSnapshot } from "./dev-server.js";
export { startWorkerRpcHost, runWorker } from "./worker-rpc-host.js";
export {
createHostClientHandlers,
getRequiredCapability,
CapabilityDeniedError,
InvocationScopeDeniedError,
} from "./host-client-factory.js";
// JSON-RPC protocol helpers and constants
export {
JSONRPC_VERSION,
JSONRPC_ERROR_CODES,
PLUGIN_RPC_ERROR_CODES,
HOST_TO_WORKER_REQUIRED_METHODS,
HOST_TO_WORKER_OPTIONAL_METHODS,
MESSAGE_DELIMITER,
createRequest,
createSuccessResponse,
createErrorResponse,
createNotification,
isJsonRpcRequest,
isJsonRpcNotification,
isJsonRpcResponse,
isJsonRpcSuccessResponse,
isJsonRpcErrorResponse,
serializeMessage,
parseMessage,
JsonRpcParseError,
JsonRpcCallError,
_resetIdCounter,
} from "./protocol.js";
// ---------------------------------------------------------------------------
// Type exports
// ---------------------------------------------------------------------------
// Plugin definition and lifecycle types
export type {
PluginDefinition,
PaperclipPlugin,
PluginHealthDiagnostics,
PluginConfigValidationResult,
PluginWebhookInput,
PluginApiRequestInput,
PluginApiResponse,
} from "./define-plugin.js";
export type {
TestHarness,
TestHarnessOptions,
TestHarnessLogEntry,
EnvironmentTestHarness,
EnvironmentTestHarnessOptions,
EnvironmentEventRecord,
FakeEnvironmentDriverOptions,
} from "./testing.js";
export type {
PluginBundlerPresetInput,
PluginBundlerPresets,
EsbuildLikeOptions,
RollupLikeConfig,
} from "./bundlers.js";
export type { PluginDevServer, PluginDevServerOptions } from "./dev-server.js";
export type {
WorkerRpcHostOptions,
WorkerRpcHost,
RunWorkerOptions,
} from "./worker-rpc-host.js";
export type {
HostServices,
HostClientFactoryOptions,
HostClientHandlers,
} from "./host-client-factory.js";
// JSON-RPC protocol types
export type {
JsonRpcId,
JsonRpcRequest,
JsonRpcSuccessResponse,
JsonRpcError,
JsonRpcErrorResponse,
JsonRpcResponse,
JsonRpcNotification,
JsonRpcMessage,
JsonRpcErrorCode,
PluginRpcErrorCode,
PluginInvocationScope,
PluginInvocationContext,
WorkerHostCallContext,
InitializeParams,
InitializeResult,
ConfigChangedParams,
ValidateConfigParams,
OnEventParams,
RunJobParams,
GetDataParams,
PerformActionParams,
PluginPerformActionActorType,
PluginPerformActionActorContext,
PluginPerformActionContext,
ExecuteToolParams,
PluginEnvironmentDiagnostic,
PluginEnvironmentDriverBaseParams,
PluginEnvironmentValidateConfigParams,
PluginEnvironmentValidationResult,
PluginEnvironmentProbeParams,
PluginEnvironmentProbeResult,
PluginEnvironmentLease,
PluginEnvironmentAcquireLeaseParams,
PluginEnvironmentResumeLeaseParams,
PluginEnvironmentReleaseLeaseParams,
PluginEnvironmentDestroyLeaseParams,
PluginEnvironmentRealizeWorkspaceParams,
PluginEnvironmentRealizeWorkspaceResult,
PluginEnvironmentExecuteParams,
PluginEnvironmentExecuteResult,
PluginModalBoundsRequest,
PluginRenderCloseEvent,
PluginLauncherRenderContextSnapshot,
HostToWorkerMethods,
HostToWorkerMethodName,
WorkerToHostMethods,
WorkerToHostMethodName,
HostToWorkerRequest,
HostToWorkerResponse,
WorkerToHostRequest,
WorkerToHostResponse,
WorkerToHostNotifications,
WorkerToHostNotificationName,
} from "./protocol.js";
// Plugin context and all client interfaces
export type {
PluginContext,
PluginConfigClient,
PluginLocalFolderProblem,
PluginLocalFolderStatus,
PluginLocalFolderConfigureInput,
PluginLocalFolderListOptions,
PluginLocalFolderEntry,
PluginLocalFolderListing,
PluginLocalFoldersClient,
PluginEventsClient,
PluginJobsClient,
PluginLaunchersClient,
PluginHttpClient,
PluginSecretsClient,
PluginActivityClient,
PluginActivityLogEntry,
PluginStateClient,
PluginEntitiesClient,
PluginProjectsClient,
PluginExecutionWorkspacesClient,
PluginSkillsClient,
PluginCompaniesClient,
PluginIssuesClient,
PluginIssueMutationActor,
PluginIssueRelationsClient,
PluginIssueRelationSummary,
PluginIssueCheckoutOwnership,
PluginIssueWakeupResult,
PluginIssueWakeupBatchResult,
PluginIssueRunSummary,
PluginIssueApprovalSummary,
PluginIssueCostSummary,
PluginBudgetIncidentSummary,
PluginIssueInvocationBlockSummary,
PluginIssueOrchestrationSummary,
PluginIssueSubtreeOptions,
PluginIssueAssigneeSummary,
PluginIssueSubtree,
PluginIssueSummariesClient,
PluginAgentsClient,
PluginAccessClient,
PluginAccessMembersClient,
PluginAccessInvitesClient,
PluginAccessMember,
PluginAccessInvite,
PluginAuthorizationClient,
PluginAuthorizationPolicySummary,
PluginAuthorizationPolicyRecord,
PluginAssignmentPreviewInput,
PluginAuthorizationDecisionResult,
PluginAuthorizationAuditEntry,
PluginAgentSessionsClient,
AgentSession,
AgentSessionEvent,
AgentSessionSendResult,
PluginGoalsClient,
PluginDataClient,
PluginActionsClient,
PluginStreamsClient,
PluginToolsClient,
PluginMetricsClient,
PluginTelemetryClient,
PluginLogger,
} from "./types.js";
// Supporting types for context clients
export type {
ScopeKey,
EventFilter,
PluginEvent,
PluginJobContext,
PluginLauncherRegistration,
ToolRunContext,
ToolResult,
PluginEntityUpsert,
PluginEntityRecord,
PluginEntityQuery,
PluginWorkspace,
PluginExecutionWorkspaceMetadata,
Company,
Project,
Issue,
IssueComment,
IssueDocumentSummary,
Agent,
Goal,
PermissionKey,
PrincipalPermissionGrant,
PrincipalType,
PluginDatabaseClient,
HumanCompanyMembershipRole,
MembershipStatus,
} from "./types.js";
// Manifest and constant types re-exported from @paperclipai/shared
// Plugin authors import manifest types from here so they have a single
// dependency (@paperclipai/plugin-sdk) for all plugin authoring needs.
export type {
PaperclipPluginManifestV1,
PluginJobDeclaration,
PluginWebhookDeclaration,
PluginToolDeclaration,
PluginEnvironmentDriverDeclaration,
PluginManagedAgentDeclaration,
PluginManagedAgentResolution,
PluginManagedProjectDeclaration,
PluginManagedProjectResolution,
PluginManagedRoutineDeclaration,
PluginManagedRoutineResolution,
PluginManagedSkillDeclaration,
PluginManagedSkillFileDeclaration,
PluginManagedSkillResolution,
CompanySkill,
PluginManagedResourceKind,
PluginManagedResourceRef,
PluginUiSlotDeclaration,
PluginUiDeclaration,
PluginLauncherActionDeclaration,
PluginLauncherRenderDeclaration,
PluginLauncherDeclaration,
PluginMinimumHostVersion,
PluginDatabaseDeclaration,
PluginApiRouteCompanyResolution,
PluginApiRouteDeclaration,
PluginLocalFolderDeclaration,
PluginCompanySettings,
PluginRecord,
PluginDatabaseNamespaceRecord,
PluginMigrationRecord,
PluginConfig,
JsonSchema,
PluginStatus,
PluginCategory,
PluginCapability,
PluginUiSlotType,
PluginUiSlotEntityType,
PluginLauncherPlacementZone,
PluginLauncherAction,
PluginLauncherBounds,
PluginLauncherRenderEnvironment,
PluginStateScopeKind,
PluginJobStatus,
PluginJobRunStatus,
PluginJobRunTrigger,
PluginWebhookDeliveryStatus,
PluginDatabaseCoreReadTable,
PluginDatabaseMigrationStatus,
PluginDatabaseNamespaceMode,
PluginDatabaseNamespaceStatus,
PluginApiRouteAuthMode,
PluginApiRouteCheckoutPolicy,
PluginApiRouteMethod,
PluginEventType,
PluginBridgeErrorCode,
} from "./types.js";
// ---------------------------------------------------------------------------
// Zod re-export
// ---------------------------------------------------------------------------
/**
* Zod is re-exported for plugin authors to use when defining their
* `instanceConfigSchema` and tool `parametersSchema`.
*
* Plugin authors do not need to add a separate `zod` dependency.
*
* @see PLUGIN_SPEC.md §14.1 — Example SDK Shape
*
* @example
* ```ts
* import { z } from "@paperclipai/plugin-sdk";
*
* const configSchema = z.object({
* apiKey: z.string().describe("Your API key"),
* workspace: z.string().optional(),
* });
* ```
*/
export { z } from "zod";
// ---------------------------------------------------------------------------
// Constants re-exports (for plugin code that needs to check values at runtime)
// ---------------------------------------------------------------------------
export {
PLUGIN_API_VERSION,
PLUGIN_STATUSES,
PLUGIN_CATEGORIES,
PLUGIN_CAPABILITIES,
PLUGIN_UI_SLOT_TYPES,
PLUGIN_UI_SLOT_ENTITY_TYPES,
PLUGIN_RESERVED_COMPANY_SETTINGS_ROUTE_SEGMENTS,
PLUGIN_STATE_SCOPE_KINDS,
PLUGIN_JOB_STATUSES,
PLUGIN_JOB_RUN_STATUSES,
PLUGIN_JOB_RUN_TRIGGERS,
PLUGIN_WEBHOOK_DELIVERY_STATUSES,
PLUGIN_EVENT_TYPES,
PLUGIN_BRIDGE_ERROR_CODES,
PERMISSION_KEYS,
HUMAN_COMPANY_MEMBERSHIP_ROLES,
HUMAN_COMPANY_MEMBERSHIP_ROLE_LABELS,
MEMBERSHIP_STATUSES,
PRINCIPAL_TYPES,
} from "@paperclipai/shared";