@tickernelz/paperclip-pro-plugin-sdk 2026.925.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1307 -0
  3. package/dist/.paperclip-build-complete +1 -0
  4. package/dist/bundlers.d.ts +57 -0
  5. package/dist/bundlers.d.ts.map +1 -0
  6. package/dist/bundlers.js +106 -0
  7. package/dist/bundlers.js.map +1 -0
  8. package/dist/define-plugin.d.ts +396 -0
  9. package/dist/define-plugin.d.ts.map +1 -0
  10. package/dist/define-plugin.js +87 -0
  11. package/dist/define-plugin.js.map +1 -0
  12. package/dist/dev-cli.d.ts +3 -0
  13. package/dist/dev-cli.d.ts.map +1 -0
  14. package/dist/dev-cli.js +49 -0
  15. package/dist/dev-cli.js.map +1 -0
  16. package/dist/dev-server.d.ts +34 -0
  17. package/dist/dev-server.d.ts.map +1 -0
  18. package/dist/dev-server.js +194 -0
  19. package/dist/dev-server.js.map +1 -0
  20. package/dist/host-client-factory.d.ts +326 -0
  21. package/dist/host-client-factory.d.ts.map +1 -0
  22. package/dist/host-client-factory.js +688 -0
  23. package/dist/host-client-factory.js.map +1 -0
  24. package/dist/index.d.ts +85 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +86 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/protocol.d.ts +2333 -0
  29. package/dist/protocol.d.ts.map +1 -0
  30. package/dist/protocol.duplex-channel.test.d.ts +2 -0
  31. package/dist/protocol.duplex-channel.test.d.ts.map +1 -0
  32. package/dist/protocol.duplex-channel.test.js +229 -0
  33. package/dist/protocol.duplex-channel.test.js.map +1 -0
  34. package/dist/protocol.js +364 -0
  35. package/dist/protocol.js.map +1 -0
  36. package/dist/testing.d.ts +203 -0
  37. package/dist/testing.d.ts.map +1 -0
  38. package/dist/testing.js +2475 -0
  39. package/dist/testing.js.map +1 -0
  40. package/dist/types.d.ts +1837 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +23 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/ui/clipboard.d.ts +8 -0
  45. package/dist/ui/clipboard.d.ts.map +1 -0
  46. package/dist/ui/clipboard.js +12 -0
  47. package/dist/ui/clipboard.js.map +1 -0
  48. package/dist/ui/components.d.ts +518 -0
  49. package/dist/ui/components.d.ts.map +1 -0
  50. package/dist/ui/components.js +135 -0
  51. package/dist/ui/components.js.map +1 -0
  52. package/dist/ui/hooks.d.ts +155 -0
  53. package/dist/ui/hooks.d.ts.map +1 -0
  54. package/dist/ui/hooks.js +195 -0
  55. package/dist/ui/hooks.js.map +1 -0
  56. package/dist/ui/index.d.ts +55 -0
  57. package/dist/ui/index.d.ts.map +1 -0
  58. package/dist/ui/index.js +52 -0
  59. package/dist/ui/index.js.map +1 -0
  60. package/dist/ui/runtime.d.ts +3 -0
  61. package/dist/ui/runtime.d.ts.map +1 -0
  62. package/dist/ui/runtime.js +30 -0
  63. package/dist/ui/runtime.js.map +1 -0
  64. package/dist/ui/types.d.ts +423 -0
  65. package/dist/ui/types.d.ts.map +1 -0
  66. package/dist/ui/types.js +17 -0
  67. package/dist/ui/types.js.map +1 -0
  68. package/dist/worker-rpc-host.d.ts +128 -0
  69. package/dist/worker-rpc-host.d.ts.map +1 -0
  70. package/dist/worker-rpc-host.js +1877 -0
  71. package/dist/worker-rpc-host.js.map +1 -0
  72. package/package.json +92 -0
@@ -0,0 +1,1837 @@
1
+ /**
2
+ * Core types for the Paperclip plugin worker-side SDK.
3
+ *
4
+ * These types define the stable public API surface that plugin workers import
5
+ * from `@tickernelz/paperclip-pro-plugin-sdk`. The host provides a concrete implementation
6
+ * of `PluginContext` to the plugin at initialisation time.
7
+ *
8
+ * @see PLUGIN_SPEC.md §14 — SDK Surface
9
+ * @see PLUGIN_SPEC.md §29.2 — SDK Versioning
10
+ */
11
+ import type { PaperclipPluginManifestV1, PluginStateScopeKind, PluginEventType, PluginToolDeclaration, PluginLauncherDeclaration, Company, Project, Issue, IssueComment, IssueDocument, IssueDocumentSummary, IssueRelationIssueSummary, IssueAssigneeAdapterOverrides, IssueAttachment, IssueThreadInteraction, Approval, SuggestTasksInteraction, AskUserQuestionsInteraction, RequestConfirmationInteraction, RequestCheckboxConfirmationInteraction, CreateIssueThreadInteraction, PluginIssueOriginKind, IssueSurfaceVisibility, PluginManagedAgentResolution, PluginManagedProjectResolution, PluginManagedRoutineResolution, PluginManagedSkillResolution, Routine, RoutineRun, Agent, Goal, HumanCompanyMembershipRole, InviteJoinType, MembershipStatus, PermissionKey, PrincipalPermissionGrant, PrincipalType, EnvSecretRefBinding } from "@tickernelz/paperclip-pro-shared";
12
+ import type { PluginPerformActionContext } from "./protocol.js";
13
+ export type { PaperclipPluginManifestV1, PluginJobDeclaration, PluginWebhookDeclaration, PluginToolDeclaration, PluginEnvironmentDriverDeclaration, PluginEnvironmentTemplateConfigBinding, PluginManagedAgentDeclaration, PluginManagedAgentResolution, PluginManagedProjectDeclaration, PluginManagedProjectResolution, PluginManagedRoutineDeclaration, PluginManagedRoutineResolution, PluginManagedSkillDeclaration, PluginManagedSkillFileDeclaration, PluginManagedSkillResolution, CompanySkill, Routine, RoutineRun, PluginLocalFolderDeclaration, PluginCompanySettings, PluginManagedResourceKind, PluginManagedResourceRef, PluginUiSlotDeclaration, PluginUiDeclaration, PluginLauncherActionDeclaration, PluginLauncherRenderDeclaration, PluginLauncherDeclaration, PluginMinimumHostVersion, PluginDatabaseDeclaration, PluginApiRouteDeclaration, PluginApiRouteCompanyResolution, PluginObjectReferenceRefreshPolicy, PluginObjectReferenceProviderDeclaration, 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, Company, Project, Issue, IssueComment, IssueDocument, IssueDocumentSummary, IssueRelationIssueSummary, IssueThreadInteraction, ConnectionIntentInteraction, ConnectionIntentPayload, ConnectionIntentResult, ConnectionIntentSetupOptions, ConnectionRequestResult, ConnectionsSearchResult, SuggestTasksInteraction, AskUserQuestionsInteraction, RequestConfirmationInteraction, RequestCheckboxConfirmationInteraction, CreateIssueThreadInteraction, PluginIssueOriginKind, IssueSurfaceVisibility, Agent, Goal, HumanCompanyMembershipRole, InviteJoinType, MembershipStatus, PermissionKey, PrincipalPermissionGrant, PrincipalType, EnvSecretRefBinding, } from "@tickernelz/paperclip-pro-shared";
14
+ /**
15
+ * A scope key identifies the exact location where plugin state is stored.
16
+ * Scope is partitioned by `scopeKind` and optional `scopeId`.
17
+ *
18
+ * Examples:
19
+ * - `{ scopeKind: "instance" }` — single global value for the whole instance
20
+ * - `{ scopeKind: "project", scopeId: "proj-uuid" }` — per-project state
21
+ * - `{ scopeKind: "issue", scopeId: "iss-uuid" }` — per-issue state
22
+ *
23
+ * @see PLUGIN_SPEC.md §21.3 `plugin_state`
24
+ */
25
+ export interface ScopeKey {
26
+ /** What kind of Paperclip object this state is scoped to. */
27
+ scopeKind: PluginStateScopeKind;
28
+ /** UUID or text identifier for the scoped object. Omit for `instance` scope. */
29
+ scopeId?: string;
30
+ /** Optional sub-namespace within the scope to avoid key collisions. Defaults to `"default"`. */
31
+ namespace?: string;
32
+ /** The state key within the namespace. */
33
+ stateKey: string;
34
+ }
35
+ /**
36
+ * Optional filter applied when subscribing to an event. The host evaluates
37
+ * the filter server-side so filtered-out events never cross the process boundary.
38
+ *
39
+ * All filter fields are optional. If omitted the plugin receives every event
40
+ * of the subscribed type.
41
+ *
42
+ * @see PLUGIN_SPEC.md §16.1 — Event Filtering
43
+ */
44
+ export interface EventFilter {
45
+ /** Only receive events for this project. */
46
+ projectId?: string;
47
+ /** Only receive events for this company. */
48
+ companyId?: string;
49
+ /** Only receive events for this agent. */
50
+ agentId?: string;
51
+ /** Additional arbitrary filter fields. */
52
+ [key: string]: unknown;
53
+ }
54
+ /**
55
+ * Envelope wrapping every domain event delivered to a plugin worker.
56
+ *
57
+ * @see PLUGIN_SPEC.md §16 — Event System
58
+ */
59
+ export interface PluginEvent<TPayload = unknown> {
60
+ /** Unique event identifier (UUID). */
61
+ eventId: string;
62
+ /** The event type (e.g. `"issue.created"`). */
63
+ eventType: PluginEventType | `plugin.${string}`;
64
+ /** ISO 8601 timestamp when the event occurred. */
65
+ occurredAt: string;
66
+ /** ID of the actor that caused the event, if applicable. */
67
+ actorId?: string;
68
+ /** Type of actor: `"user"`, `"agent"`, `"system"`, or `"plugin"`. */
69
+ actorType?: "user" | "agent" | "system" | "plugin";
70
+ /** Primary entity involved in the event. */
71
+ entityId?: string;
72
+ /** Type of the primary entity. */
73
+ entityType?: string;
74
+ /** UUID of the company this event belongs to. */
75
+ companyId: string;
76
+ /** Typed event payload. */
77
+ payload: TPayload;
78
+ }
79
+ /**
80
+ * Context passed to a plugin job handler when the host triggers a scheduled run.
81
+ *
82
+ * @see PLUGIN_SPEC.md §13.6 — `runJob`
83
+ */
84
+ export interface PluginJobContext {
85
+ /** Stable job key matching the declaration in the manifest. */
86
+ jobKey: string;
87
+ /** UUID for this specific job run instance. */
88
+ runId: string;
89
+ /** What triggered this run. */
90
+ trigger: "schedule" | "manual" | "retry";
91
+ /** ISO 8601 timestamp when the run was scheduled to start. */
92
+ scheduledAt: string;
93
+ }
94
+ /**
95
+ * Run context passed to a plugin tool handler when an agent invokes the tool.
96
+ *
97
+ * @see PLUGIN_SPEC.md §13.10 — `executeTool`
98
+ */
99
+ export interface ToolRunContext {
100
+ /** UUID of the agent invoking the tool. */
101
+ agentId: string;
102
+ /** UUID of the current agent run. */
103
+ runId: string;
104
+ /** UUID of the company the run belongs to. */
105
+ companyId: string;
106
+ /** UUID of the project the run belongs to. */
107
+ projectId: string;
108
+ }
109
+ /**
110
+ * Result returned from a plugin tool handler.
111
+ *
112
+ * @see PLUGIN_SPEC.md §13.10 — `executeTool`
113
+ */
114
+ export interface ToolResult {
115
+ /** String content returned to the agent. Required for success responses. */
116
+ content?: string;
117
+ /** Structured data returned alongside or instead of string content. */
118
+ data?: unknown;
119
+ /** If present, indicates the tool call failed. */
120
+ error?: string;
121
+ }
122
+ /**
123
+ * Input for creating or updating a plugin-owned entity.
124
+ *
125
+ * @see PLUGIN_SPEC.md §21.3 `plugin_entities`
126
+ */
127
+ export interface PluginEntityUpsert {
128
+ /** Plugin-defined entity type (e.g. `"linear-issue"`, `"github-pr"`). */
129
+ entityType: string;
130
+ /** Scope where this entity lives. */
131
+ scopeKind: PluginStateScopeKind;
132
+ /** Optional scope ID. */
133
+ scopeId?: string;
134
+ /** External identifier in the remote system (e.g. Linear issue ID). */
135
+ externalId?: string;
136
+ /** Human-readable title for display in the Paperclip UI. */
137
+ title?: string;
138
+ /** Optional status string. */
139
+ status?: string;
140
+ /** Full entity data blob. Must be JSON-serializable. */
141
+ data: Record<string, unknown>;
142
+ }
143
+ /**
144
+ * A plugin-owned entity record as returned by `ctx.entities.list()`.
145
+ *
146
+ * @see PLUGIN_SPEC.md §21.3 `plugin_entities`
147
+ */
148
+ export interface PluginEntityRecord {
149
+ /** UUID primary key. */
150
+ id: string;
151
+ /** Plugin-defined entity type. */
152
+ entityType: string;
153
+ /** Scope kind. */
154
+ scopeKind: PluginStateScopeKind;
155
+ /** Scope ID, if any. */
156
+ scopeId: string | null;
157
+ /** External identifier, if any. */
158
+ externalId: string | null;
159
+ /** Human-readable title. */
160
+ title: string | null;
161
+ /** Status string. */
162
+ status: string | null;
163
+ /** Full entity data. */
164
+ data: Record<string, unknown>;
165
+ /** ISO 8601 creation timestamp. */
166
+ createdAt: string;
167
+ /** ISO 8601 last-updated timestamp. */
168
+ updatedAt: string;
169
+ }
170
+ /**
171
+ * Query parameters for `ctx.entities.list()`.
172
+ */
173
+ export interface PluginEntityQuery {
174
+ /** Filter by entity type. */
175
+ entityType?: string;
176
+ /** Filter by scope kind. */
177
+ scopeKind?: PluginStateScopeKind;
178
+ /** Filter by scope ID. */
179
+ scopeId?: string;
180
+ /** Filter by external ID. */
181
+ externalId?: string;
182
+ /** Maximum number of results to return. */
183
+ limit?: number;
184
+ /** Number of results to skip (for pagination). */
185
+ offset?: number;
186
+ }
187
+ /**
188
+ * Workspace metadata provided by the host. Plugins use this to resolve local
189
+ * filesystem paths for file browsing, git, terminal, and process operations.
190
+ *
191
+ * @see PLUGIN_SPEC.md §7 — Project Workspaces
192
+ * @see PLUGIN_SPEC.md §20 — Local Tooling
193
+ */
194
+ export interface PluginWorkspace {
195
+ /** UUID primary key. */
196
+ id: string;
197
+ /** UUID of the parent project. */
198
+ projectId: string;
199
+ /** Display name for this workspace. */
200
+ name: string;
201
+ /** Absolute filesystem path to the workspace directory. */
202
+ path: string;
203
+ /** Repository URL, when known. */
204
+ repoUrl: string | null;
205
+ /** Checkout/ref requested for the workspace, when known. */
206
+ repoRef: string | null;
207
+ /** Default comparison ref for workspace tooling, when known. */
208
+ defaultRef: string | null;
209
+ /** Whether this is the project's primary workspace. */
210
+ isPrimary: boolean;
211
+ /** ISO 8601 creation timestamp. */
212
+ createdAt: string;
213
+ /** ISO 8601 last-updated timestamp. */
214
+ updatedAt: string;
215
+ }
216
+ /**
217
+ * Plugin-safe execution workspace metadata provided by the host. This exposes
218
+ * the local/repository coordinates plugins need for workspace tooling without
219
+ * giving the SDK a host-owned diff engine.
220
+ */
221
+ export interface PluginExecutionWorkspaceMetadata {
222
+ /** UUID primary key. */
223
+ id: string;
224
+ /** UUID of the owning company. */
225
+ companyId: string;
226
+ /** UUID of the parent project. */
227
+ projectId: string;
228
+ /** UUID of the backing project workspace, when present. */
229
+ projectWorkspaceId: string | null;
230
+ /** Absolute filesystem path to the workspace when locally realized. */
231
+ path: string | null;
232
+ /** Current working directory for local workspace tooling. */
233
+ cwd: string | null;
234
+ /** Repository URL, when known. */
235
+ repoUrl: string | null;
236
+ /** Base ref configured for the workspace, when known. */
237
+ baseRef: string | null;
238
+ /** Branch name configured for the workspace, when known. */
239
+ branchName: string | null;
240
+ /** Host provider type for the realized workspace. */
241
+ providerType: string | null;
242
+ /** Provider metadata already safe for plugin consumption. */
243
+ providerMetadata: Record<string, unknown> | null;
244
+ }
245
+ /**
246
+ * `ctx.config` — read resolved operator configuration for this plugin.
247
+ *
248
+ * Plugin workers receive the resolved config at initialisation. Use `get()`
249
+ * to access the current configuration at any time. The host calls
250
+ * `configChanged` on the worker when the operator updates config at runtime.
251
+ *
252
+ * @see PLUGIN_SPEC.md §13.3 — `validateConfig`
253
+ * @see PLUGIN_SPEC.md §13.4 — `configChanged`
254
+ */
255
+ export interface PluginConfigClient {
256
+ /**
257
+ * Returns the resolved operator configuration for this plugin in a company.
258
+ * When called during a host-scoped invocation, the host may derive the
259
+ * companyId; otherwise callers must pass it explicitly.
260
+ */
261
+ get(companyId?: string): Promise<Record<string, unknown>>;
262
+ }
263
+ export interface PluginLocalFolderProblem {
264
+ code: "not_configured" | "not_absolute" | "missing" | "not_directory" | "not_readable" | "not_writable" | "missing_directory" | "missing_file" | "path_traversal" | "symlink_escape" | "atomic_write_failed";
265
+ message: string;
266
+ path?: string;
267
+ }
268
+ export interface PluginLocalFolderStatus {
269
+ folderKey: string;
270
+ configured: boolean;
271
+ path: string | null;
272
+ realPath: string | null;
273
+ access: "read" | "readWrite";
274
+ readable: boolean;
275
+ writable: boolean;
276
+ requiredDirectories: string[];
277
+ requiredFiles: string[];
278
+ missingDirectories: string[];
279
+ missingFiles: string[];
280
+ healthy: boolean;
281
+ problems: PluginLocalFolderProblem[];
282
+ checkedAt: string;
283
+ }
284
+ export interface PluginLocalFolderConfigureInput {
285
+ companyId: string;
286
+ folderKey: string;
287
+ path: string;
288
+ access?: "read" | "readWrite";
289
+ requiredDirectories?: string[];
290
+ requiredFiles?: string[];
291
+ }
292
+ export interface PluginLocalFolderListOptions {
293
+ relativePath?: string | null;
294
+ recursive?: boolean;
295
+ maxEntries?: number;
296
+ }
297
+ export interface PluginLocalFolderEntry {
298
+ path: string;
299
+ name: string;
300
+ kind: "file" | "directory";
301
+ size: number | null;
302
+ modifiedAt: string | null;
303
+ }
304
+ export interface PluginLocalFolderListing {
305
+ folderKey: string;
306
+ relativePath: string | null;
307
+ entries: PluginLocalFolderEntry[];
308
+ truncated: boolean;
309
+ }
310
+ export interface PluginLocalFoldersClient {
311
+ /** Manifest-declared local folders for this plugin. */
312
+ declarations(): import("@tickernelz/paperclip-pro-shared").PluginLocalFolderDeclaration[];
313
+ /** Persist a company-scoped local folder path after validating it. */
314
+ configure(input: PluginLocalFolderConfigureInput): Promise<PluginLocalFolderStatus>;
315
+ /** Check the stored folder readiness for a company and folder key. */
316
+ status(companyId: string, folderKey: string): Promise<PluginLocalFolderStatus>;
317
+ /** List entries below a configured folder after containment checks. */
318
+ list(companyId: string, folderKey: string, options?: PluginLocalFolderListOptions): Promise<PluginLocalFolderListing>;
319
+ /** Read a UTF-8 text file below a configured folder after containment checks. */
320
+ readText(companyId: string, folderKey: string, relativePath: string): Promise<string>;
321
+ /** Write a UTF-8 text file below a configured folder using atomic rename. */
322
+ writeTextAtomic(companyId: string, folderKey: string, relativePath: string, contents: string): Promise<PluginLocalFolderStatus>;
323
+ /** Delete a file below a configured folder after containment checks. Missing files are treated as already deleted. */
324
+ deleteFile(companyId: string, folderKey: string, relativePath: string): Promise<PluginLocalFolderStatus>;
325
+ }
326
+ /**
327
+ * `ctx.events` — subscribe to and emit Paperclip domain events.
328
+ *
329
+ * Requires `events.subscribe` capability for `on()`.
330
+ * Requires `events.emit` capability for `emit()`.
331
+ *
332
+ * @see PLUGIN_SPEC.md §16 — Event System
333
+ */
334
+ export interface PluginEventsClient {
335
+ /**
336
+ * Subscribe to a core Paperclip domain event or a plugin-namespaced event.
337
+ *
338
+ * @param name - Event type, e.g. `"issue.created"` or `"plugin.@acme/linear.sync-done"`
339
+ * @param fn - Async event handler
340
+ */
341
+ on(name: PluginEventType | `plugin.${string}`, fn: (event: PluginEvent) => Promise<void>): () => void;
342
+ /**
343
+ * Subscribe to an event with an optional server-side filter.
344
+ *
345
+ * @param name - Event type
346
+ * @param filter - Server-side filter evaluated before dispatching to the worker
347
+ * @param fn - Async event handler
348
+ * @returns An unsubscribe function that removes the handler
349
+ */
350
+ on(name: PluginEventType | `plugin.${string}`, filter: EventFilter, fn: (event: PluginEvent) => Promise<void>): () => void;
351
+ /**
352
+ * Emit a plugin-namespaced event. Other plugins with `events.subscribe` can
353
+ * subscribe to it using `"plugin.<pluginId>.<eventName>"`.
354
+ *
355
+ * Requires the `events.emit` capability.
356
+ *
357
+ * Plugin-emitted events are automatically namespaced: if the plugin ID is
358
+ * `"acme.linear"` and the event name is `"sync-done"`, the full event type
359
+ * becomes `"plugin.acme.linear.sync-done"`.
360
+ *
361
+ * @see PLUGIN_SPEC.md §16.2 — Plugin-to-Plugin Events
362
+ *
363
+ * @param name - Bare event name (e.g. `"sync-done"`)
364
+ * @param companyId - UUID of the company this event belongs to
365
+ * @param payload - JSON-serializable event payload
366
+ */
367
+ emit(name: string, companyId: string, payload: unknown): Promise<void>;
368
+ }
369
+ /**
370
+ * `ctx.jobs` — register handlers for scheduled jobs declared in the manifest.
371
+ *
372
+ * Requires `jobs.schedule` capability.
373
+ *
374
+ * @see PLUGIN_SPEC.md §17 — Scheduled Jobs
375
+ */
376
+ export interface PluginJobsClient {
377
+ /**
378
+ * Register a handler for a scheduled job.
379
+ *
380
+ * The `key` must match a `jobKey` declared in the plugin manifest.
381
+ * The host calls this handler according to the job's declared `schedule`.
382
+ *
383
+ * @param key - Job key matching the manifest declaration
384
+ * @param fn - Async job handler
385
+ */
386
+ register(key: string, fn: (job: PluginJobContext) => Promise<void>): void;
387
+ }
388
+ /**
389
+ * A runtime launcher registration uses the same declaration shape as a
390
+ * manifest launcher entry.
391
+ */
392
+ export type PluginLauncherRegistration = PluginLauncherDeclaration;
393
+ /**
394
+ * `ctx.launchers` — register launcher declarations at runtime.
395
+ */
396
+ export interface PluginLaunchersClient {
397
+ /**
398
+ * Register launcher metadata for host discovery.
399
+ *
400
+ * If a launcher with the same id is registered more than once, the latest
401
+ * declaration replaces the previous one.
402
+ */
403
+ register(launcher: PluginLauncherRegistration): void;
404
+ }
405
+ export interface PluginDatabaseClient {
406
+ /** Host-derived PostgreSQL schema name for this plugin's namespace. */
407
+ namespace: string;
408
+ /** Run a restricted SELECT against the plugin namespace and whitelisted core tables. */
409
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
410
+ /** Run a restricted INSERT, UPDATE, or DELETE against the plugin namespace. */
411
+ execute(sql: string, params?: unknown[]): Promise<{
412
+ rowCount: number;
413
+ }>;
414
+ }
415
+ /**
416
+ * `ctx.http` — make outbound HTTP requests.
417
+ *
418
+ * Requires `http.outbound` capability.
419
+ *
420
+ * @see PLUGIN_SPEC.md §15.1 — Capabilities: Runtime/Integration
421
+ */
422
+ export interface PluginHttpClient {
423
+ /**
424
+ * Perform an outbound HTTP request.
425
+ *
426
+ * The host enforces `http.outbound` capability before allowing the call.
427
+ * Plugins may also use standard Node `fetch` or other libraries directly —
428
+ * this client exists for host-managed tracing and audit logging.
429
+ *
430
+ * @param url - Target URL
431
+ * @param init - Standard `RequestInit` options
432
+ * @returns The response
433
+ */
434
+ fetch(url: string, init?: RequestInit): Promise<Response>;
435
+ }
436
+ /**
437
+ * `ctx.secrets` — resolve secret references.
438
+ *
439
+ * Requires `secrets.read-ref` capability.
440
+ *
441
+ * Plugins store shared `{ type: "secret_ref", secretId, version? }` bindings in
442
+ * company-scoped config. This client resolves a bound ref through the
443
+ * Paperclip secret provider system at execution time.
444
+ *
445
+ * @see PLUGIN_SPEC.md §22 — Secrets
446
+ */
447
+ export interface PluginSecretsClient {
448
+ /**
449
+ * Resolve a secret reference to its current value.
450
+ *
451
+ * The reference must be the shared `secret_ref` object shape from plugin
452
+ * config. Legacy string UUID references fail closed.
453
+ *
454
+ * Secret values are resolved at call time and must never be cached or
455
+ * written to logs, config, or other persistent storage.
456
+ *
457
+ * @param secretRef - The secret reference object from plugin config
458
+ * @returns The resolved secret value
459
+ */
460
+ resolve(secretRef: string | EnvSecretRefBinding, options?: {
461
+ companyId?: string;
462
+ configPath?: string;
463
+ }): Promise<string>;
464
+ }
465
+ /**
466
+ * Input for writing a plugin activity log entry.
467
+ *
468
+ * @see PLUGIN_SPEC.md §21.4 — Activity Log Changes
469
+ */
470
+ export interface PluginActivityLogEntry {
471
+ /** UUID of the company this activity belongs to. Required for auditing. */
472
+ companyId: string;
473
+ /** Human-readable description of the activity. */
474
+ message: string;
475
+ /** Optional entity type this activity relates to. */
476
+ entityType?: string;
477
+ /** Optional entity ID this activity relates to. */
478
+ entityId?: string;
479
+ /** Optional additional metadata. */
480
+ metadata?: Record<string, unknown>;
481
+ }
482
+ /**
483
+ * `ctx.activity` — write plugin-originated activity log entries.
484
+ *
485
+ * Requires `activity.log.write` capability.
486
+ *
487
+ * @see PLUGIN_SPEC.md §21.4 — Activity Log Changes
488
+ */
489
+ export interface PluginActivityClient {
490
+ /**
491
+ * Write an activity log entry attributed to this plugin.
492
+ *
493
+ * The host writes the entry with `actor_type = plugin` and
494
+ * `actor_id = <pluginId>`.
495
+ *
496
+ * @param entry - The activity log entry to write
497
+ */
498
+ log(entry: PluginActivityLogEntry): Promise<void>;
499
+ }
500
+ /**
501
+ * `ctx.state` — read and write plugin-scoped key-value state.
502
+ *
503
+ * Each plugin gets an isolated namespace: state written by plugin A can never
504
+ * be read or overwritten by plugin B. Within a plugin, state is partitioned by
505
+ * a five-part composite key: `(pluginId, scopeKind, scopeId, namespace, stateKey)`.
506
+ *
507
+ * **Scope kinds**
508
+ *
509
+ * | `scopeKind` | `scopeId` | Typical use |
510
+ * |-------------|-----------|-------------|
511
+ * | `"instance"` | omit | Global flags, last full-sync timestamps |
512
+ * | `"company"` | company UUID | Per-company sync cursors |
513
+ * | `"project"` | project UUID | Per-project settings, branch tracking |
514
+ * | `"project_workspace"` | workspace UUID | Per-workspace state |
515
+ * | `"agent"` | agent UUID | Per-agent memory |
516
+ * | `"issue"` | issue UUID | Idempotency keys, linked external IDs |
517
+ * | `"goal"` | goal UUID | Per-goal progress |
518
+ * | `"run"` | run UUID | Per-run checkpoints |
519
+ *
520
+ * **Namespaces**
521
+ *
522
+ * The optional `namespace` field (default: `"default"`) lets you group related
523
+ * keys within a scope without risking collisions between different logical
524
+ * subsystems inside the same plugin.
525
+ *
526
+ * **Security**
527
+ *
528
+ * Never store resolved secret values. Store only secret references and resolve
529
+ * them at call time via `ctx.secrets.resolve()`.
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * // Instance-global flag
534
+ * await ctx.state.set({ scopeKind: "instance", stateKey: "schema-version" }, 2);
535
+ *
536
+ * // Idempotency key per issue
537
+ * const synced = await ctx.state.get({ scopeKind: "issue", scopeId: issueId, stateKey: "synced-to-linear" });
538
+ * if (!synced) {
539
+ * await syncToLinear(issueId);
540
+ * await ctx.state.set({ scopeKind: "issue", scopeId: issueId, stateKey: "synced-to-linear" }, true);
541
+ * }
542
+ *
543
+ * // Per-project, namespaced for two integrations
544
+ * await ctx.state.set({ scopeKind: "project", scopeId: projectId, namespace: "linear", stateKey: "cursor" }, cursor);
545
+ * await ctx.state.set({ scopeKind: "project", scopeId: projectId, namespace: "github", stateKey: "last-event" }, eventId);
546
+ * ```
547
+ *
548
+ * `plugin.state.read` capability required for `get()`.
549
+ * `plugin.state.write` capability required for `set()` and `delete()`.
550
+ *
551
+ * @see PLUGIN_SPEC.md §21.3 `plugin_state`
552
+ */
553
+ export interface PluginStateClient {
554
+ /**
555
+ * Read a state value.
556
+ *
557
+ * Returns the stored JSON value as-is, or `null` if no entry has been set
558
+ * for this scope+key combination. Falsy values (`false`, `0`, `""`) are
559
+ * returned correctly and are not confused with "not set".
560
+ *
561
+ * @param input - Scope key identifying the entry to read
562
+ * @returns The stored JSON value, or `null` if no value has been set
563
+ */
564
+ get(input: ScopeKey): Promise<unknown>;
565
+ /**
566
+ * Write a state value. Creates the row if it does not exist; replaces it
567
+ * atomically (upsert) if it does. Safe to call concurrently.
568
+ *
569
+ * Any JSON-serializable value is accepted: objects, arrays, strings,
570
+ * numbers, booleans, and `null`.
571
+ *
572
+ * @param input - Scope key identifying the entry to write
573
+ * @param value - JSON-serializable value to store
574
+ */
575
+ set(input: ScopeKey, value: unknown): Promise<void>;
576
+ /**
577
+ * Delete a state value. No-ops silently if the entry does not exist
578
+ * (idempotent by design — safe to call without prior `get()`).
579
+ *
580
+ * @param input - Scope key identifying the entry to delete
581
+ */
582
+ delete(input: ScopeKey): Promise<void>;
583
+ }
584
+ /**
585
+ * `ctx.entities` — create and query plugin-owned entity records.
586
+ *
587
+ * @see PLUGIN_SPEC.md §21.3 `plugin_entities`
588
+ */
589
+ export interface PluginEntitiesClient {
590
+ /**
591
+ * Create or update a plugin entity record (upsert by `externalId` within
592
+ * the given scope, or by `id` if provided).
593
+ *
594
+ * @param input - Entity data to upsert
595
+ */
596
+ upsert(input: PluginEntityUpsert): Promise<PluginEntityRecord>;
597
+ /**
598
+ * Query plugin entity records.
599
+ *
600
+ * @param query - Filter criteria
601
+ * @returns Matching entity records
602
+ */
603
+ list(query: PluginEntityQuery): Promise<PluginEntityRecord[]>;
604
+ }
605
+ /**
606
+ * `ctx.projects` — read project and workspace metadata.
607
+ *
608
+ * Requires `projects.read` capability.
609
+ * Requires `project.workspaces.read` capability for workspace operations.
610
+ *
611
+ * @see PLUGIN_SPEC.md §7 — Project Workspaces
612
+ */
613
+ export interface PluginProjectsClient {
614
+ /**
615
+ * List projects visible to the plugin.
616
+ *
617
+ * Requires the `projects.read` capability.
618
+ */
619
+ list(input: {
620
+ companyId: string;
621
+ limit?: number;
622
+ offset?: number;
623
+ }): Promise<Project[]>;
624
+ /**
625
+ * Get a single project by ID.
626
+ *
627
+ * Requires the `projects.read` capability.
628
+ */
629
+ get(projectId: string, companyId: string): Promise<Project | null>;
630
+ /**
631
+ * List all workspaces attached to a project.
632
+ *
633
+ * @param projectId - UUID of the project
634
+ * @param companyId - UUID of the company that owns the project
635
+ * @returns All workspaces for the project, ordered with primary first
636
+ */
637
+ listWorkspaces(projectId: string, companyId: string): Promise<PluginWorkspace[]>;
638
+ /**
639
+ * Get the primary workspace for a project.
640
+ *
641
+ * @param projectId - UUID of the project
642
+ * @param companyId - UUID of the company that owns the project
643
+ * @returns The primary workspace, or `null` if no workspace is configured
644
+ */
645
+ getPrimaryWorkspace(projectId: string, companyId: string): Promise<PluginWorkspace | null>;
646
+ /**
647
+ * Resolve the primary workspace for an issue by looking up the issue's
648
+ * project and returning its primary workspace.
649
+ *
650
+ * This is a convenience method that combines `issues.get()` and
651
+ * `getPrimaryWorkspace()` in a single RPC call.
652
+ *
653
+ * @param issueId - UUID of the issue
654
+ * @param companyId - UUID of the company that owns the issue
655
+ * @returns The primary workspace for the issue's project, or `null` if
656
+ * the issue has no project or the project has no workspace
657
+ *
658
+ * @see PLUGIN_SPEC.md §20 — Local Tooling
659
+ */
660
+ getWorkspaceForIssue(issueId: string, companyId: string): Promise<PluginWorkspace | null>;
661
+ /** Resolve and reconcile manifest-declared plugin-managed projects by stable key. Requires `projects.managed`. */
662
+ managed: {
663
+ get(projectKey: string, companyId: string): Promise<PluginManagedProjectResolution>;
664
+ reconcile(projectKey: string, companyId: string): Promise<PluginManagedProjectResolution>;
665
+ reset(projectKey: string, companyId: string): Promise<PluginManagedProjectResolution>;
666
+ };
667
+ }
668
+ /**
669
+ * `ctx.executionWorkspaces` — read execution workspace metadata.
670
+ *
671
+ * Requires `execution.workspaces.read`.
672
+ */
673
+ export interface PluginExecutionWorkspacesClient {
674
+ /**
675
+ * Return plugin-safe metadata for an execution workspace. The host enforces
676
+ * company access before returning any workspace coordinates.
677
+ */
678
+ get(workspaceId: string, companyId: string): Promise<PluginExecutionWorkspaceMetadata | null>;
679
+ }
680
+ /**
681
+ * `ctx.routines` — resolve and reconcile plugin-managed Paperclip routines.
682
+ *
683
+ * Requires `routines.managed` capability.
684
+ */
685
+ export interface PluginRoutinesClient {
686
+ managed: {
687
+ get(routineKey: string, companyId: string): Promise<PluginManagedRoutineResolution>;
688
+ reconcile(routineKey: string, companyId: string, overrides?: {
689
+ assigneeAgentId?: string | null;
690
+ projectId?: string | null;
691
+ }): Promise<PluginManagedRoutineResolution>;
692
+ reset(routineKey: string, companyId: string, overrides?: {
693
+ assigneeAgentId?: string | null;
694
+ projectId?: string | null;
695
+ }): Promise<PluginManagedRoutineResolution>;
696
+ update(routineKey: string, companyId: string, patch: {
697
+ status?: string;
698
+ }): Promise<Routine>;
699
+ run(routineKey: string, companyId: string, overrides?: {
700
+ assigneeAgentId?: string | null;
701
+ projectId?: string | null;
702
+ }): Promise<RoutineRun>;
703
+ };
704
+ }
705
+ /**
706
+ * `ctx.skills` — resolve and reconcile plugin-managed company skills.
707
+ *
708
+ * Requires `skills.managed` capability.
709
+ */
710
+ export interface PluginSkillsClient {
711
+ managed: {
712
+ get(skillKey: string, companyId: string): Promise<PluginManagedSkillResolution>;
713
+ reconcile(skillKey: string, companyId: string): Promise<PluginManagedSkillResolution>;
714
+ reset(skillKey: string, companyId: string): Promise<PluginManagedSkillResolution>;
715
+ };
716
+ }
717
+ /**
718
+ * `ctx.data` — register `getData` handlers that back `usePluginData()` in the
719
+ * plugin's frontend components.
720
+ *
721
+ * The plugin's UI calls `usePluginData(key, params)` which routes through the
722
+ * host bridge to the worker's registered handler.
723
+ *
724
+ * @see PLUGIN_SPEC.md §13.8 — `getData`
725
+ */
726
+ export interface PluginDataClient {
727
+ /**
728
+ * Register a handler for a plugin-defined data key.
729
+ *
730
+ * @param key - Stable string identifier for this data type (e.g. `"sync-health"`)
731
+ * @param handler - Async function that receives request params and returns JSON-serializable data
732
+ */
733
+ register(key: string, handler: (params: Record<string, unknown>) => Promise<unknown>): void;
734
+ }
735
+ /**
736
+ * `ctx.actions` — register `performAction` handlers that back
737
+ * `usePluginAction()` in the plugin's frontend components.
738
+ *
739
+ * @see PLUGIN_SPEC.md §13.9 — `performAction`
740
+ */
741
+ export interface PluginActionsClient {
742
+ /**
743
+ * Register a handler for a plugin-defined action key.
744
+ *
745
+ * @param key - Stable string identifier for this action (e.g. `"resync"`)
746
+ * @param handler - Async function that receives action params plus immutable host actor context and returns a result
747
+ */
748
+ register(key: string, handler: (params: Record<string, unknown>, context: PluginPerformActionContext) => Promise<unknown>): void;
749
+ }
750
+ /**
751
+ * `ctx.tools` — register handlers for agent tools declared in the manifest.
752
+ *
753
+ * Requires `agent.tools.register` capability.
754
+ *
755
+ * Tool names are automatically namespaced by plugin ID at runtime.
756
+ *
757
+ * @see PLUGIN_SPEC.md §11 — Agent Tools
758
+ */
759
+ export interface PluginToolsClient {
760
+ /**
761
+ * Register a handler for a plugin-contributed agent tool.
762
+ *
763
+ * @param name - Tool name matching the manifest declaration (without namespace prefix)
764
+ * @param declaration - Tool metadata (displayName, description, parametersSchema)
765
+ * @param fn - Async handler that executes the tool
766
+ */
767
+ register(name: string, declaration: Pick<PluginToolDeclaration, "displayName" | "description" | "parametersSchema">, fn: (params: unknown, runCtx: ToolRunContext) => Promise<ToolResult>): void;
768
+ }
769
+ /**
770
+ * `ctx.logger` — structured logging from the plugin worker.
771
+ *
772
+ * Log output is captured by the host, stored, and surfaced in the plugin
773
+ * health dashboard.
774
+ *
775
+ * @see PLUGIN_SPEC.md §26.1 — Logging
776
+ */
777
+ export interface PluginLogger {
778
+ /** Log an informational message. */
779
+ info(message: string, meta?: Record<string, unknown>): void;
780
+ /** Log a warning. */
781
+ warn(message: string, meta?: Record<string, unknown>): void;
782
+ /** Log an error. */
783
+ error(message: string, meta?: Record<string, unknown>): void;
784
+ /** Log a debug message (may be suppressed in production). */
785
+ debug(message: string, meta?: Record<string, unknown>): void;
786
+ }
787
+ /**
788
+ * `ctx.tracer` — a minimal, OpenTelemetry-free span contract. The plugin worker
789
+ * builds a real span through this surface; the host records it through the real
790
+ * tracer. The shape is a subset of the `@opentelemetry/api` `Span` shape, so the
791
+ * plugin SDK never imports `@opentelemetry/api`.
792
+ *
793
+ * A span with no active host trace context is a no-op: it accepts the calls and
794
+ * ends without an effect. So a lifecycle hook can always open a span, and the
795
+ * span records nothing until tracing is on.
796
+ */
797
+ export interface PluginSpan {
798
+ /** Set one bounded attribute. The host re-clamps every attribute at its trust
799
+ * boundary, so an out-of-allowlist attribute never reaches a recorded span. */
800
+ setAttribute(key: string, value: string | number | boolean): void;
801
+ /** Set the span status. The host maps it onto the recorded span. */
802
+ setStatus(status: {
803
+ code: number;
804
+ message?: string;
805
+ }): void;
806
+ /** End the span. The worker sends the span data to the host once, here. */
807
+ end(): void;
808
+ }
809
+ /**
810
+ * `ctx.tracer` — a minimal, OpenTelemetry-free tracer contract. The plugin uses
811
+ * it the same way as `ctx.logger`. The default is a no-op that never throws, so
812
+ * a plugin span changes nothing until the host injects a live tracer and an
813
+ * active host trace context.
814
+ */
815
+ export interface PluginTracer {
816
+ /** Start one span. `options.attributes` seeds the span attributes. */
817
+ startSpan(name: string, options?: {
818
+ attributes?: Record<string, string | number | boolean>;
819
+ }): PluginSpan;
820
+ }
821
+ /** A shared no-op span. It satisfies the span contract and does nothing, so a
822
+ * plugin with no injected tracer changes no behavior. */
823
+ export declare const NOOP_PLUGIN_SPAN: PluginSpan;
824
+ /** The default tracer. It opens no real span, so a lifecycle hook that wraps
825
+ * work in a span behaves exactly as before when no live tracer is injected. */
826
+ export declare const NOOP_PLUGIN_TRACER: PluginTracer;
827
+ /**
828
+ * `ctx.metrics` — write plugin-contributed metrics.
829
+ *
830
+ * Requires `metrics.write` capability.
831
+ *
832
+ * @see PLUGIN_SPEC.md §15.1 — Capabilities: Data Write
833
+ */
834
+ export interface PluginMetricsClient {
835
+ /**
836
+ * Write a numeric metric data point.
837
+ *
838
+ * @param name - Metric name (plugin-namespaced by the host)
839
+ * @param value - Numeric value
840
+ * @param tags - Optional key-value tags for filtering
841
+ */
842
+ write(name: string, value: number, tags?: Record<string, string>): Promise<void>;
843
+ }
844
+ /**
845
+ * `ctx.telemetry` — emit plugin-scoped telemetry to the host's external
846
+ * telemetry pipeline.
847
+ *
848
+ * Requires `telemetry.track` capability.
849
+ */
850
+ export interface PluginTelemetryClient {
851
+ /**
852
+ * Track a plugin telemetry event.
853
+ *
854
+ * The host prefixes the final event name as `plugin.<pluginId>.<eventName>`
855
+ * before forwarding it to the shared telemetry client.
856
+ *
857
+ * @param eventName - Bare plugin event slug (for example `"sync_completed"`)
858
+ * @param dimensions - Optional structured dimensions
859
+ */
860
+ track(eventName: string, dimensions?: Record<string, string | number | boolean>): Promise<void>;
861
+ }
862
+ /**
863
+ * `ctx.companies` — read company metadata.
864
+ *
865
+ * Requires `companies.read` capability.
866
+ */
867
+ export interface PluginCompaniesClient {
868
+ /**
869
+ * List companies visible to this plugin.
870
+ */
871
+ list(input?: {
872
+ limit?: number;
873
+ offset?: number;
874
+ }): Promise<Company[]>;
875
+ /**
876
+ * Get one company by ID.
877
+ */
878
+ get(companyId: string): Promise<Company | null>;
879
+ }
880
+ /**
881
+ * `ctx.issues.documents` — read and write issue documents.
882
+ *
883
+ * Requires:
884
+ * - `issue.documents.read` for `list` and `get`
885
+ * - `issue.documents.write` for `upsert` and `delete`
886
+ *
887
+ * @see PLUGIN_SPEC.md §14 — SDK Surface
888
+ */
889
+ export interface PluginIssueDocumentsClient {
890
+ /**
891
+ * List all documents attached to an issue.
892
+ *
893
+ * Returns summary metadata (id, key, title, format, timestamps) without
894
+ * the full document body. Use `get()` to fetch a specific document's body.
895
+ *
896
+ * Requires the `issue.documents.read` capability.
897
+ */
898
+ list(issueId: string, companyId: string): Promise<IssueDocumentSummary[]>;
899
+ /**
900
+ * Get a single document by key, including its full body content.
901
+ *
902
+ * Returns `null` if no document exists with the given key.
903
+ *
904
+ * Requires the `issue.documents.read` capability.
905
+ *
906
+ * @param issueId - UUID of the issue
907
+ * @param key - Document key (e.g. `"plan"`, `"design-spec"`)
908
+ * @param companyId - UUID of the company
909
+ */
910
+ get(issueId: string, key: string, companyId: string): Promise<IssueDocument | null>;
911
+ /**
912
+ * Create or update a document on an issue.
913
+ *
914
+ * If a document with the given key already exists, it is updated and a new
915
+ * revision is created. If it does not exist, it is created.
916
+ *
917
+ * Requires the `issue.documents.write` capability.
918
+ *
919
+ * @param input - Document data including issueId, key, body, and optional title/format/changeSummary
920
+ */
921
+ upsert(input: {
922
+ issueId: string;
923
+ key: string;
924
+ body: string;
925
+ companyId: string;
926
+ title?: string;
927
+ format?: string;
928
+ changeSummary?: string;
929
+ }): Promise<IssueDocument>;
930
+ /**
931
+ * Delete a document and all its revisions.
932
+ *
933
+ * No-ops silently if the document does not exist (idempotent).
934
+ *
935
+ * Requires the `issue.documents.write` capability.
936
+ *
937
+ * @param issueId - UUID of the issue
938
+ * @param key - Document key to delete
939
+ * @param companyId - UUID of the company
940
+ */
941
+ delete(issueId: string, key: string, companyId: string): Promise<void>;
942
+ }
943
+ export interface PluginIssueMutationActor {
944
+ /** Agent that initiated the plugin operation, when the plugin is acting from an agent run. */
945
+ actorAgentId?: string | null;
946
+ /** Board/user that initiated the plugin operation, when known. */
947
+ actorUserId?: string | null;
948
+ /** Heartbeat run that initiated the operation. Required for checkout-aware agent actions. */
949
+ actorRunId?: string | null;
950
+ }
951
+ export interface PluginIssueRelationSummary {
952
+ blockedBy: IssueRelationIssueSummary[];
953
+ blocks: IssueRelationIssueSummary[];
954
+ }
955
+ export interface PluginIssueRelationsClient {
956
+ /** Read blocker relationships for an issue. Requires `issue.relations.read`. */
957
+ get(issueId: string, companyId: string): Promise<PluginIssueRelationSummary>;
958
+ /** Replace the issue's blocked-by relation set. Requires `issue.relations.write`. */
959
+ setBlockedBy(issueId: string, blockedByIssueIds: string[], companyId: string, actor?: PluginIssueMutationActor): Promise<PluginIssueRelationSummary>;
960
+ /** Add one or more blockers while preserving existing blockers. Requires `issue.relations.write`. */
961
+ addBlockers(issueId: string, blockerIssueIds: string[], companyId: string, actor?: PluginIssueMutationActor): Promise<PluginIssueRelationSummary>;
962
+ /** Remove one or more blockers while preserving all other blockers. Requires `issue.relations.write`. */
963
+ removeBlockers(issueId: string, blockerIssueIds: string[], companyId: string, actor?: PluginIssueMutationActor): Promise<PluginIssueRelationSummary>;
964
+ }
965
+ export interface PluginIssueCheckoutOwnership {
966
+ issueId: string;
967
+ status: Issue["status"];
968
+ assigneeAgentId: string | null;
969
+ checkoutRunId: string | null;
970
+ adoptedFromRunId: string | null;
971
+ }
972
+ export interface PluginIssueWakeupResult {
973
+ queued: boolean;
974
+ runId: string | null;
975
+ }
976
+ export interface PluginIssueWakeupBatchResult {
977
+ issueId: string;
978
+ queued: boolean;
979
+ runId: string | null;
980
+ }
981
+ export interface PluginIssueRunSummary {
982
+ id: string;
983
+ issueId: string | null;
984
+ agentId: string;
985
+ status: string;
986
+ invocationSource: string;
987
+ triggerDetail: string | null;
988
+ startedAt: string | null;
989
+ finishedAt: string | null;
990
+ error: string | null;
991
+ createdAt: string;
992
+ }
993
+ export interface PluginIssueApprovalSummary {
994
+ issueId: string;
995
+ id: string;
996
+ type: string;
997
+ status: string;
998
+ requestedByAgentId: string | null;
999
+ requestedByUserId: string | null;
1000
+ decidedByUserId: string | null;
1001
+ decidedAt: string | null;
1002
+ createdAt: string;
1003
+ }
1004
+ export interface PluginIssueCostSummary {
1005
+ costCents: number;
1006
+ inputTokens: number;
1007
+ cachedInputTokens: number;
1008
+ outputTokens: number;
1009
+ billingCode: string | null;
1010
+ }
1011
+ export interface PluginBudgetIncidentSummary {
1012
+ id: string;
1013
+ scopeType: string;
1014
+ scopeId: string;
1015
+ metric: string;
1016
+ windowKind: string;
1017
+ thresholdType: string;
1018
+ amountLimit: number;
1019
+ amountObserved: number;
1020
+ status: string;
1021
+ approvalId: string | null;
1022
+ createdAt: string;
1023
+ }
1024
+ export interface PluginIssueInvocationBlockSummary {
1025
+ issueId: string;
1026
+ agentId: string;
1027
+ scopeType: "company" | "agent" | "project";
1028
+ scopeId: string;
1029
+ scopeName: string;
1030
+ reason: string;
1031
+ }
1032
+ export interface PluginIssueOrchestrationSummary {
1033
+ issueId: string;
1034
+ companyId: string;
1035
+ subtreeIssueIds: string[];
1036
+ relations: Record<string, PluginIssueRelationSummary>;
1037
+ approvals: PluginIssueApprovalSummary[];
1038
+ runs: PluginIssueRunSummary[];
1039
+ costs: PluginIssueCostSummary;
1040
+ openBudgetIncidents: PluginBudgetIncidentSummary[];
1041
+ invocationBlocks: PluginIssueInvocationBlockSummary[];
1042
+ }
1043
+ export interface PluginIssueSubtreeOptions {
1044
+ /** Include the root issue in the result. Defaults to true. */
1045
+ includeRoot?: boolean;
1046
+ /** Include blocker relationship summaries keyed by issue ID. */
1047
+ includeRelations?: boolean;
1048
+ /** Include issue document summaries keyed by issue ID. */
1049
+ includeDocuments?: boolean;
1050
+ /** Include queued/running heartbeat runs keyed by issue ID. */
1051
+ includeActiveRuns?: boolean;
1052
+ /** Include assignee summaries keyed by agent ID. */
1053
+ includeAssignees?: boolean;
1054
+ }
1055
+ export interface PluginIssueAssigneeSummary {
1056
+ id: string;
1057
+ name: string;
1058
+ role: string;
1059
+ title: string | null;
1060
+ status: Agent["status"];
1061
+ }
1062
+ export interface PluginIssueSubtree {
1063
+ rootIssueId: string;
1064
+ companyId: string;
1065
+ issueIds: string[];
1066
+ issues: Issue[];
1067
+ relations?: Record<string, PluginIssueRelationSummary>;
1068
+ documents?: Record<string, IssueDocumentSummary[]>;
1069
+ activeRuns?: Record<string, PluginIssueRunSummary[]>;
1070
+ assignees?: Record<string, PluginIssueAssigneeSummary>;
1071
+ }
1072
+ export interface PluginIssueSummariesClient {
1073
+ /**
1074
+ * Read the compact orchestration inputs a workflow plugin needs for an
1075
+ * issue or issue subtree. Requires `issues.orchestration.read`.
1076
+ */
1077
+ getOrchestration(input: {
1078
+ issueId: string;
1079
+ companyId: string;
1080
+ includeSubtree?: boolean;
1081
+ billingCode?: string | null;
1082
+ }): Promise<PluginIssueOrchestrationSummary>;
1083
+ }
1084
+ /**
1085
+ * Attachment content bytes returned by `ctx.issues.getAttachmentContent`.
1086
+ * Bytes are base64-encoded; there is no URL surface.
1087
+ */
1088
+ export interface PluginIssueAttachmentContent {
1089
+ attachmentId: string;
1090
+ contentType: string;
1091
+ byteSize: number;
1092
+ sha256: string;
1093
+ originalFilename: string | null;
1094
+ /** The attachment's raw bytes, base64-encoded. */
1095
+ contentBase64: string;
1096
+ }
1097
+ /**
1098
+ * `ctx.issues` — read and mutate issues plus comments.
1099
+ *
1100
+ * Requires:
1101
+ * - `issues.read` for read operations
1102
+ * - `issues.create` for create
1103
+ * - `issues.update` for update
1104
+ * - `issues.checkout` for checkout ownership assertions
1105
+ * - `issues.wakeup` for assignment wakeup requests
1106
+ * - `issues.orchestration.read` for orchestration summaries
1107
+ * - `issue.comments.read` for `listComments`
1108
+ * - `issue.comments.create` for `createComment`
1109
+ * - `issue.comments.create_human_attributed` for `createComment` calls that pass `actorUserId`
1110
+ * - `issue.interactions.create` for `createInteraction`, `suggestTasks`, `askUserQuestions`, `requestConfirmation`, and `requestCheckboxConfirmation`
1111
+ * - `issue.interactions.read` for `listInteractions`
1112
+ * - `issue.interactions.respond` for `respondInteraction`
1113
+ * - `issue.attachments.read` for `listAttachments` and `getAttachmentContent`
1114
+ * - `issue.documents.read` for `documents.list` and `documents.get`
1115
+ * - `issue.documents.write` for `documents.upsert` and `documents.delete`
1116
+ */
1117
+ export interface PluginIssuesClient {
1118
+ list(input: {
1119
+ companyId: string;
1120
+ projectId?: string;
1121
+ assigneeAgentId?: string;
1122
+ originKind?: PluginIssueOriginKind;
1123
+ originKindPrefix?: string;
1124
+ originId?: string;
1125
+ status?: Issue["status"];
1126
+ includePluginOperations?: boolean;
1127
+ limit?: number;
1128
+ offset?: number;
1129
+ }): Promise<Issue[]>;
1130
+ get(issueId: string, companyId: string): Promise<Issue | null>;
1131
+ create(input: {
1132
+ companyId: string;
1133
+ projectId?: string;
1134
+ goalId?: string;
1135
+ parentId?: string;
1136
+ inheritExecutionWorkspaceFromIssueId?: string;
1137
+ title: string;
1138
+ description?: string;
1139
+ status?: Issue["status"];
1140
+ priority?: Issue["priority"];
1141
+ assigneeAgentId?: string;
1142
+ assigneeUserId?: string | null;
1143
+ requestDepth?: number;
1144
+ billingCode?: string | null;
1145
+ assigneeAdapterOverrides?: IssueAssigneeAdapterOverrides | null;
1146
+ surfaceVisibility?: IssueSurfaceVisibility;
1147
+ originKind?: PluginIssueOriginKind;
1148
+ originId?: string | null;
1149
+ originRunId?: string | null;
1150
+ blockedByIssueIds?: string[];
1151
+ labelIds?: string[];
1152
+ executionWorkspaceId?: string | null;
1153
+ executionWorkspacePreference?: string | null;
1154
+ executionWorkspaceSettings?: Record<string, unknown> | null;
1155
+ actor?: PluginIssueMutationActor;
1156
+ }): Promise<Issue>;
1157
+ update(issueId: string, patch: Partial<Pick<Issue, "title" | "description" | "status" | "priority" | "assigneeAgentId" | "assigneeUserId" | "billingCode" | "originKind" | "originId" | "originRunId" | "requestDepth" | "executionWorkspaceId" | "executionWorkspacePreference">> & {
1158
+ blockedByIssueIds?: string[];
1159
+ labelIds?: string[];
1160
+ executionWorkspaceSettings?: Record<string, unknown> | null;
1161
+ }, companyId: string, actor?: PluginIssueMutationActor): Promise<Issue>;
1162
+ assertCheckoutOwner(input: {
1163
+ issueId: string;
1164
+ companyId: string;
1165
+ actorAgentId: string;
1166
+ actorRunId: string;
1167
+ }): Promise<PluginIssueCheckoutOwnership>;
1168
+ /**
1169
+ * Read a root issue's descendants with optional relation/document/run/assignee
1170
+ * summaries. Requires `issue.subtree.read`.
1171
+ */
1172
+ getSubtree(issueId: string, companyId: string, options?: PluginIssueSubtreeOptions): Promise<PluginIssueSubtree>;
1173
+ requestWakeup(issueId: string, companyId: string, options?: {
1174
+ reason?: string;
1175
+ contextSource?: string;
1176
+ idempotencyKey?: string | null;
1177
+ } & PluginIssueMutationActor): Promise<PluginIssueWakeupResult>;
1178
+ requestWakeups(issueIds: string[], companyId: string, options?: {
1179
+ reason?: string;
1180
+ contextSource?: string;
1181
+ idempotencyKeyPrefix?: string | null;
1182
+ } & PluginIssueMutationActor): Promise<PluginIssueWakeupBatchResult[]>;
1183
+ listComments(issueId: string, companyId: string): Promise<IssueComment[]>;
1184
+ /**
1185
+ * Post a comment on an issue.
1186
+ *
1187
+ * Pass `authorAgentId` to attribute the comment to the plugin's own agent
1188
+ * identity (requires `issue.comments.create`, the default).
1189
+ *
1190
+ * Pass `actorUserId` to attribute the comment to a real human instead —
1191
+ * for example, relaying a paired chat user's reply back into the issue
1192
+ * thread. Requires the additional `issue.comments.create_human_attributed`
1193
+ * capability. The host independently verifies that `actorUserId` is an
1194
+ * active human member of the issue's company before applying the
1195
+ * attribution — a plugin can only ever attribute comments to identities
1196
+ * that could have posted them in the web app. A human-attributed comment
1197
+ * also participates in the normal wake-the-assignee behavior a board
1198
+ * user's comment gets in the web app (subject to the same closed-issue /
1199
+ * no-assignee exclusions) — unlike a plugin's own agent-attributed
1200
+ * comments, which never wake anyone.
1201
+ */
1202
+ createComment(issueId: string, body: string, companyId: string, options?: {
1203
+ authorAgentId?: string;
1204
+ actorUserId?: string;
1205
+ }): Promise<IssueComment>;
1206
+ createInteraction(issueId: string, interaction: CreateIssueThreadInteraction, companyId: string, options?: {
1207
+ authorAgentId?: string;
1208
+ }): Promise<IssueThreadInteraction>;
1209
+ suggestTasks(issueId: string, interaction: Omit<Extract<CreateIssueThreadInteraction, {
1210
+ kind: "suggest_tasks";
1211
+ }>, "kind">, companyId: string, options?: {
1212
+ authorAgentId?: string;
1213
+ }): Promise<SuggestTasksInteraction>;
1214
+ askUserQuestions(issueId: string, interaction: Omit<Extract<CreateIssueThreadInteraction, {
1215
+ kind: "ask_user_questions";
1216
+ }>, "kind">, companyId: string, options?: {
1217
+ authorAgentId?: string;
1218
+ }): Promise<AskUserQuestionsInteraction>;
1219
+ requestConfirmation(issueId: string, interaction: Omit<Extract<CreateIssueThreadInteraction, {
1220
+ kind: "request_confirmation";
1221
+ }>, "kind">, companyId: string, options?: {
1222
+ authorAgentId?: string;
1223
+ }): Promise<RequestConfirmationInteraction>;
1224
+ requestCheckboxConfirmation(issueId: string, interaction: Omit<Extract<CreateIssueThreadInteraction, {
1225
+ kind: "request_checkbox_confirmation";
1226
+ }>, "kind">, companyId: string, options?: {
1227
+ authorAgentId?: string;
1228
+ }): Promise<RequestCheckboxConfirmationInteraction>;
1229
+ /**
1230
+ * List the issue-thread interactions (decision cards) on an issue.
1231
+ * Requires `issue.interactions.read`.
1232
+ */
1233
+ listInteractions(issueId: string, companyId: string): Promise<IssueThreadInteraction[]>;
1234
+ /**
1235
+ * Resolve (accept/reject) a pending issue-thread interaction on behalf of a
1236
+ * paired board user. Requires `issue.interactions.respond`.
1237
+ *
1238
+ * `actorUserId` is the human company member the decision is attributed to.
1239
+ * The host independently re-verifies that this user is an active human
1240
+ * member of the issue's company before applying the decision — a plugin can
1241
+ * only ever resolve interactions as an identity that could have resolved
1242
+ * them in the web app (whose interaction-resolve routes are board-only).
1243
+ *
1244
+ * Returns the (possibly already-resolved) interaction and `applied`, which is
1245
+ * `true` when this call performed the resolution and `false` when the
1246
+ * interaction had already converged to a resolved state (idempotent replays).
1247
+ */
1248
+ respondInteraction(issueId: string, interactionId: string, input: {
1249
+ action: "accept" | "reject";
1250
+ actorUserId?: string;
1251
+ reason?: string | null;
1252
+ }, companyId: string): Promise<{
1253
+ interaction: IssueThreadInteraction;
1254
+ applied: boolean;
1255
+ }>;
1256
+ /** List attachment metadata for an issue. Requires `issue.attachments.read`. */
1257
+ listAttachments(issueId: string, companyId: string): Promise<IssueAttachment[]>;
1258
+ /**
1259
+ * Read an attachment's content bytes (base64) through the capability-scoped
1260
+ * host bridge. Requires `issue.attachments.read`.
1261
+ *
1262
+ * Company-scoped and audit-logged host-side; there is no URL surface. Returns
1263
+ * `null` for an unknown or cross-company attachment id (indistinguishable by
1264
+ * design). Pass `maxBytes` to refuse over-cap assets — the host throws rather
1265
+ * than partially reading when the stored size exceeds the cap.
1266
+ */
1267
+ getAttachmentContent(attachmentId: string, companyId: string, options?: {
1268
+ maxBytes?: number | null;
1269
+ }): Promise<PluginIssueAttachmentContent | null>;
1270
+ /** Read and write issue documents. Requires `issue.documents.read` / `issue.documents.write`. */
1271
+ documents: PluginIssueDocumentsClient;
1272
+ /** Read and write blocker relationships. */
1273
+ relations: PluginIssueRelationsClient;
1274
+ /** Read compact orchestration summaries. */
1275
+ summaries: PluginIssueSummariesClient;
1276
+ }
1277
+ /**
1278
+ * `ctx.approvals` — read and decide company approvals.
1279
+ *
1280
+ * Requires `approvals.read` for `list` / `get`; `approvals.respond` for
1281
+ * `decide`. Approval payloads returned by `list` / `get` are redacted host-side
1282
+ * to match the web app's own approval read surface (no secret leakage through
1283
+ * the bridge).
1284
+ */
1285
+ export interface PluginApprovalsClient {
1286
+ list(input: {
1287
+ companyId: string;
1288
+ status?: string | null;
1289
+ }): Promise<Approval[]>;
1290
+ get(approvalId: string, companyId: string): Promise<Approval | null>;
1291
+ /**
1292
+ * Approve or reject an approval on behalf of a paired board user.
1293
+ *
1294
+ * `actorUserId` is the human company member the decision is attributed to.
1295
+ * The host independently re-verifies that this user is an active human member
1296
+ * of the approval's company before applying the decision — a plugin can only
1297
+ * ever decide approvals as an identity that could have decided them in the
1298
+ * web app (whose approval-decision routes are board-only).
1299
+ *
1300
+ * `applied` is `true` when this call performed the decision and `false` when
1301
+ * the approval had already converged to a decided state (idempotent replays).
1302
+ */
1303
+ decide(approvalId: string, input: {
1304
+ action: "approve" | "reject";
1305
+ actorUserId?: string;
1306
+ decisionNote?: string | null;
1307
+ }, companyId: string): Promise<{
1308
+ approval: Approval;
1309
+ applied: boolean;
1310
+ }>;
1311
+ }
1312
+ /**
1313
+ * `ctx.agents` — read and manage agents.
1314
+ *
1315
+ * Requires `agents.read` for reads; `agents.pause` / `agents.resume` /
1316
+ * `agents.invoke` for write operations.
1317
+ */
1318
+ export interface PluginAgentsClient {
1319
+ list(input: {
1320
+ companyId: string;
1321
+ status?: Agent["status"];
1322
+ limit?: number;
1323
+ offset?: number;
1324
+ }): Promise<Agent[]>;
1325
+ get(agentId: string, companyId: string): Promise<Agent | null>;
1326
+ /** Pause an agent. Throws if agent is terminated or not found. Requires `agents.pause`. */
1327
+ pause(agentId: string, companyId: string): Promise<Agent>;
1328
+ /** Resume a paused agent (sets status to idle). Throws if terminated, pending_approval, or not found. Requires `agents.resume`. */
1329
+ resume(agentId: string, companyId: string): Promise<Agent>;
1330
+ /** Invoke (wake up) an agent with a prompt payload. Throws if paused, terminated, pending_approval, or not found. Requires `agents.invoke`. */
1331
+ invoke(agentId: string, companyId: string, opts: {
1332
+ prompt: string;
1333
+ reason?: string;
1334
+ }): Promise<{
1335
+ runId: string;
1336
+ }>;
1337
+ /** Resolve and reconcile manifest-declared plugin-managed agents by stable key. Requires `agents.managed`. */
1338
+ managed: {
1339
+ get(agentKey: string, companyId: string): Promise<PluginManagedAgentResolution>;
1340
+ reconcile(agentKey: string, companyId: string): Promise<PluginManagedAgentResolution>;
1341
+ reset(agentKey: string, companyId: string): Promise<PluginManagedAgentResolution>;
1342
+ };
1343
+ /** Create, message, and close agent chat sessions. Requires `agent.sessions.*` capabilities. */
1344
+ sessions: PluginAgentSessionsClient;
1345
+ }
1346
+ /**
1347
+ * Represents an active conversational session with an agent.
1348
+ * Maps to an `AgentTaskSession` row on the host.
1349
+ */
1350
+ export interface AgentSession {
1351
+ sessionId: string;
1352
+ agentId: string;
1353
+ companyId: string;
1354
+ status: "active" | "closed";
1355
+ createdAt: string;
1356
+ }
1357
+ /**
1358
+ * A streaming event received during a session's `sendMessage` call.
1359
+ * Delivered via JSON-RPC notifications from host to worker.
1360
+ */
1361
+ export interface AgentSessionEvent {
1362
+ sessionId: string;
1363
+ runId: string;
1364
+ seq: number;
1365
+ /** The kind of event: "chunk" for output data, "status" for run state changes, "done" for end-of-stream, "error" for failures. */
1366
+ eventType: "chunk" | "status" | "done" | "error";
1367
+ stream: "stdout" | "stderr" | "system" | null;
1368
+ /**
1369
+ * Event text. On a successful `done` event this is the canonical final
1370
+ * user-facing assistant reply, or null when the run produced no reply text.
1371
+ */
1372
+ message: string | null;
1373
+ payload: Record<string, unknown> | null;
1374
+ }
1375
+ /**
1376
+ * Result of sending a message to a session.
1377
+ */
1378
+ export interface AgentSessionSendResult {
1379
+ runId: string;
1380
+ }
1381
+ /**
1382
+ * `ctx.agents.sessions` — create, message, and close agent chat sessions.
1383
+ *
1384
+ * Requires `agent.sessions.create` for create, `agent.sessions.list` for list,
1385
+ * `agent.sessions.send` for sendMessage, `agent.sessions.close` for close.
1386
+ */
1387
+ export interface PluginAgentSessionsClient {
1388
+ /** Create a new conversational session with an agent. Requires `agent.sessions.create`. */
1389
+ create(agentId: string, companyId: string, opts?: {
1390
+ taskKey?: string;
1391
+ reason?: string;
1392
+ }): Promise<AgentSession>;
1393
+ /** List active sessions for an agent owned by this plugin. Requires `agent.sessions.list`. */
1394
+ list(agentId: string, companyId: string): Promise<AgentSession[]>;
1395
+ /**
1396
+ * Send a message to a session and receive streaming events via the `onEvent` callback.
1397
+ * Returns immediately with `{ runId }`. Events are delivered asynchronously.
1398
+ * Requires `agent.sessions.send`.
1399
+ */
1400
+ sendMessage(sessionId: string, companyId: string, opts: {
1401
+ prompt: string;
1402
+ reason?: string;
1403
+ onEvent?: (event: AgentSessionEvent) => void;
1404
+ }): Promise<AgentSessionSendResult>;
1405
+ /** Close a session, releasing resources. Requires `agent.sessions.close`. */
1406
+ close(sessionId: string, companyId: string): Promise<void>;
1407
+ }
1408
+ /**
1409
+ * `ctx.goals` — read and mutate goals.
1410
+ *
1411
+ * Requires:
1412
+ * - `goals.read` for read operations
1413
+ * - `goals.create` for create
1414
+ * - `goals.update` for update
1415
+ */
1416
+ export interface PluginGoalsClient {
1417
+ list(input: {
1418
+ companyId: string;
1419
+ level?: Goal["level"];
1420
+ status?: Goal["status"];
1421
+ limit?: number;
1422
+ offset?: number;
1423
+ }): Promise<Goal[]>;
1424
+ get(goalId: string, companyId: string): Promise<Goal | null>;
1425
+ create(input: {
1426
+ companyId: string;
1427
+ title: string;
1428
+ description?: string;
1429
+ level?: Goal["level"];
1430
+ status?: Goal["status"];
1431
+ parentId?: string;
1432
+ ownerAgentId?: string;
1433
+ }): Promise<Goal>;
1434
+ update(goalId: string, patch: Partial<Pick<Goal, "title" | "description" | "level" | "status" | "parentId" | "ownerAgentId">>, companyId: string): Promise<Goal>;
1435
+ }
1436
+ export interface PluginAccessMember {
1437
+ id: string;
1438
+ companyId: string;
1439
+ principalType: PrincipalType;
1440
+ principalId: string;
1441
+ status: MembershipStatus;
1442
+ membershipRole: string | null;
1443
+ grants: PrincipalPermissionGrant[];
1444
+ createdAt: Date | string;
1445
+ updatedAt: Date | string;
1446
+ }
1447
+ export interface PluginAccessInvite {
1448
+ id: string;
1449
+ companyId: string | null;
1450
+ inviteType: string;
1451
+ allowedJoinTypes: InviteJoinType;
1452
+ defaultsPayload: Record<string, unknown> | null;
1453
+ expiresAt: Date | string;
1454
+ invitedByUserId: string | null;
1455
+ revokedAt: Date | string | null;
1456
+ acceptedAt: Date | string | null;
1457
+ createdAt: Date | string;
1458
+ updatedAt: Date | string;
1459
+ state: "active" | "revoked" | "accepted" | "expired";
1460
+ }
1461
+ export interface PluginAccessMembersClient {
1462
+ list(input: {
1463
+ companyId: string;
1464
+ includeArchived?: boolean;
1465
+ }): Promise<PluginAccessMember[]>;
1466
+ get(memberId: string, companyId: string): Promise<PluginAccessMember | null>;
1467
+ update(memberId: string, patch: {
1468
+ membershipRole?: HumanCompanyMembershipRole | null;
1469
+ status?: Extract<MembershipStatus, "pending" | "active" | "suspended">;
1470
+ }, companyId: string): Promise<PluginAccessMember>;
1471
+ }
1472
+ export interface PluginAccessInvitesClient {
1473
+ list(input: {
1474
+ companyId: string;
1475
+ state?: PluginAccessInvite["state"];
1476
+ limit?: number;
1477
+ offset?: number;
1478
+ }): Promise<{
1479
+ invites: PluginAccessInvite[];
1480
+ nextOffset: number | null;
1481
+ }>;
1482
+ create(input: {
1483
+ companyId: string;
1484
+ allowedJoinTypes?: InviteJoinType;
1485
+ humanRole?: HumanCompanyMembershipRole | null;
1486
+ defaultsPayload?: Record<string, unknown> | null;
1487
+ agentMessage?: string | null;
1488
+ }): Promise<PluginAccessInvite & {
1489
+ token: string;
1490
+ }>;
1491
+ revoke(inviteId: string, companyId: string): Promise<PluginAccessInvite>;
1492
+ }
1493
+ export interface PluginAccessClient {
1494
+ /** Read and update company memberships. Requires `access.members.*`. */
1495
+ members: PluginAccessMembersClient;
1496
+ /** Read, create, and revoke company invites. Requires `access.invites.*`. */
1497
+ invites: PluginAccessInvitesClient;
1498
+ }
1499
+ export interface PluginAuthorizationPolicySummary {
1500
+ companyId: string;
1501
+ permissionsMode: "simple";
1502
+ memberCount: number;
1503
+ activeMemberCount: number;
1504
+ grantCount: number;
1505
+ advancedPolicyAvailable: false;
1506
+ }
1507
+ export interface PluginAuthorizationPolicyRecord {
1508
+ resourceType: "company" | "agent" | "project" | "issue";
1509
+ resourceId: string;
1510
+ companyId: string;
1511
+ policy: Record<string, unknown> | null;
1512
+ updatedAt: Date | string | null;
1513
+ }
1514
+ export interface PluginAssignmentPreviewInput {
1515
+ companyId: string;
1516
+ actor: {
1517
+ type: "board";
1518
+ userId?: string | null;
1519
+ companyIds?: string[];
1520
+ isInstanceAdmin?: boolean;
1521
+ } | {
1522
+ type: "agent";
1523
+ agentId: string;
1524
+ companyId: string;
1525
+ };
1526
+ target: {
1527
+ issueId?: string | null;
1528
+ projectId?: string | null;
1529
+ parentIssueId?: string | null;
1530
+ assigneeAgentId?: string | null;
1531
+ assigneeUserId?: string | null;
1532
+ status?: string | null;
1533
+ };
1534
+ }
1535
+ export interface PluginAuthorizationDecisionResult {
1536
+ allowed: boolean;
1537
+ action: string;
1538
+ explanation: string;
1539
+ reason: string;
1540
+ grant?: {
1541
+ principalType: PrincipalType;
1542
+ principalId: string;
1543
+ permissionKey: PermissionKey;
1544
+ scope: Record<string, unknown> | null;
1545
+ };
1546
+ }
1547
+ export interface PluginAuthorizationAuditEntry {
1548
+ id: string;
1549
+ companyId: string;
1550
+ actorType: string;
1551
+ actorId: string;
1552
+ action: string;
1553
+ entityType: string;
1554
+ entityId: string;
1555
+ details: Record<string, unknown> | null;
1556
+ createdAt: Date | string;
1557
+ }
1558
+ export interface PluginAuthorizationClient {
1559
+ grants: {
1560
+ list(input: {
1561
+ companyId: string;
1562
+ principalType?: PrincipalType;
1563
+ principalId?: string;
1564
+ }): Promise<PrincipalPermissionGrant[]>;
1565
+ set(input: {
1566
+ companyId: string;
1567
+ principalType: PrincipalType;
1568
+ principalId: string;
1569
+ grants: Array<{
1570
+ permissionKey: PermissionKey;
1571
+ scope?: Record<string, unknown> | null;
1572
+ }>;
1573
+ grantedByUserId?: string | null;
1574
+ }): Promise<PrincipalPermissionGrant[]>;
1575
+ };
1576
+ policies: {
1577
+ summary(companyId: string): Promise<PluginAuthorizationPolicySummary>;
1578
+ get(input: {
1579
+ companyId: string;
1580
+ resourceType: PluginAuthorizationPolicyRecord["resourceType"];
1581
+ resourceId: string;
1582
+ }): Promise<PluginAuthorizationPolicyRecord | null>;
1583
+ update(input: {
1584
+ companyId: string;
1585
+ resourceType: PluginAuthorizationPolicyRecord["resourceType"];
1586
+ resourceId: string;
1587
+ policy: Record<string, unknown> | null;
1588
+ }): Promise<PluginAuthorizationPolicyRecord>;
1589
+ previewAssignment(input: PluginAssignmentPreviewInput): Promise<PluginAuthorizationDecisionResult>;
1590
+ explainAssignment(input: PluginAssignmentPreviewInput): Promise<PluginAuthorizationDecisionResult>;
1591
+ };
1592
+ audit: {
1593
+ search(input: {
1594
+ companyId: string;
1595
+ action?: string;
1596
+ actorType?: string;
1597
+ actorId?: string;
1598
+ entityType?: string;
1599
+ entityId?: string;
1600
+ decision?: string;
1601
+ limit?: number;
1602
+ offset?: number;
1603
+ }): Promise<PluginAuthorizationAuditEntry[]>;
1604
+ };
1605
+ }
1606
+ /**
1607
+ * `ctx.streams` — push real-time events from the worker to the plugin UI.
1608
+ *
1609
+ * The worker opens a named channel, emits events on it, and closes it when
1610
+ * done. On the UI side, `usePluginStream(channel)` receives these events in
1611
+ * real time via SSE.
1612
+ *
1613
+ * Streams are scoped to `(pluginId, channel, companyId)`. Multiple UI clients
1614
+ * can subscribe to the same channel concurrently.
1615
+ *
1616
+ * @example
1617
+ * ```ts
1618
+ * // Worker: stream chat tokens to the UI
1619
+ * ctx.streams.open("chat", companyId);
1620
+ * for await (const token of tokenStream) {
1621
+ * ctx.streams.emit("chat", { type: "token", text: token });
1622
+ * }
1623
+ * ctx.streams.close("chat");
1624
+ * ```
1625
+ *
1626
+ * @see usePluginStream in `@tickernelz/paperclip-pro-plugin-sdk/ui`
1627
+ */
1628
+ export interface PluginStreamsClient {
1629
+ /**
1630
+ * Open a named stream channel. Optional — `emit()` implicitly opens if needed.
1631
+ * Sends a `stream:open` event to connected UI clients.
1632
+ */
1633
+ open(channel: string, companyId: string): void;
1634
+ /**
1635
+ * Push an event to all UI clients subscribed to this channel.
1636
+ *
1637
+ * @param channel - Stream channel name (e.g. `"chat"`, `"logs"`)
1638
+ * @param event - JSON-serializable event payload
1639
+ */
1640
+ emit(channel: string, event: unknown): void;
1641
+ /**
1642
+ * Close a stream channel. Sends a `stream:close` event to connected UI
1643
+ * clients so they know no more events will arrive.
1644
+ */
1645
+ close(channel: string): void;
1646
+ }
1647
+ /**
1648
+ * `ctx.execution` — deliver incremental command output from an environment
1649
+ * driver's active `execute` call to the host runner log sink.
1650
+ *
1651
+ * A sandbox provider that streams a long-lived command's output calls
1652
+ * `ctx.execution.log(stream, chunk)` for each new chunk while the execute call
1653
+ * runs. The host correlates the chunk to the active execute invocation by the
1654
+ * host-issued invocation id on the message envelope, and delivers it to that
1655
+ * call's log callback before the final result. The default is a no-op that
1656
+ * never throws, so a provider that does not stream keeps its current behavior.
1657
+ *
1658
+ * The `chunk` is a text string, not raw bytes. The host drops a chunk with an
1659
+ * unknown stream name or a chunk that is empty or too large.
1660
+ */
1661
+ export interface PluginExecutionClient {
1662
+ /**
1663
+ * Deliver one incremental output chunk of the active execute call.
1664
+ *
1665
+ * @param stream - Either `"stdout"` or `"stderr"`.
1666
+ * @param chunk - The new output text for that stream.
1667
+ */
1668
+ log(stream: "stdout" | "stderr", chunk: string): void;
1669
+ }
1670
+ /**
1671
+ * `ctx.loginPty` — stream one live login pseudo-terminal's output and exit
1672
+ * from a sandbox provider worker to the host.
1673
+ *
1674
+ * The worker opener registers the output listener on the session and forwards
1675
+ * each raw chunk through `output(hostRouteId, workerSessionId, chunk)`. It
1676
+ * forwards the child exit through `exit(hostRouteId, workerSessionId,
1677
+ * exitCode)`. Each call carries the host route identifier the open request
1678
+ * carried and the worker session identifier the open reply returned, so the
1679
+ * host can hold more than one concurrent login pseudo-terminal per worker and
1680
+ * bind each chunk to its own route. The host drops a chunk or an exit that
1681
+ * carries an unknown, a stale, or a mismatched identifier, and it never logs
1682
+ * the raw bytes. The default is a no-op that never throws.
1683
+ */
1684
+ export interface PluginLoginPtyClient {
1685
+ /**
1686
+ * Deliver one raw output chunk of a live login pseudo-terminal.
1687
+ *
1688
+ * @param hostRouteId - The host route identifier the open request carried. The worker echoes it, so the host routes the chunk to its own route.
1689
+ * @param workerSessionId - The worker session identifier the open reply returned.
1690
+ * @param chunk - The raw terminal output text.
1691
+ */
1692
+ output(hostRouteId: string, workerSessionId: string, chunk: string): void;
1693
+ /**
1694
+ * Deliver the child exit of a live login pseudo-terminal.
1695
+ *
1696
+ * @param hostRouteId - The host route identifier the open request carried. The worker echoes it, so the host resolves the exit against its own route.
1697
+ * @param workerSessionId - The worker session identifier the open reply returned.
1698
+ * @param exitCode - The child exit code, or null when the child ended with no code.
1699
+ */
1700
+ exit(hostRouteId: string, workerSessionId: string, exitCode: number | null): void;
1701
+ }
1702
+ /**
1703
+ * `ctx.duplexChannel` — stream one persistent duplex channel's data and exit from
1704
+ * a sandbox provider worker to the host.
1705
+ *
1706
+ * The worker registers the data listener on the channel and forwards each raw
1707
+ * chunk through `data(workerSessionId, chunk)`. It forwards the child exit through
1708
+ * `exit(workerSessionId, exitCode)`. Each call carries the worker session
1709
+ * identifier the open reply returned, so the host binds the data to the open route
1710
+ * by that identifier while the route is open. The host drops a chunk or an exit
1711
+ * that carries an unknown or a mismatched identifier, and it never logs the raw
1712
+ * bytes. The default is a no-op that never throws. This client models the
1713
+ * `loginPty` client, but it carries no login command allowlist.
1714
+ */
1715
+ export interface PluginDuplexChannelClient {
1716
+ /**
1717
+ * Deliver one raw data chunk of a persistent duplex channel.
1718
+ *
1719
+ * @param hostRouteId - The host route identifier the open request carried. The worker echoes it, so the host routes the exact pair.
1720
+ * @param workerSessionId - The worker session identifier the open reply returned.
1721
+ * @param chunk - The raw channel output bytes.
1722
+ */
1723
+ data(hostRouteId: string, workerSessionId: string, chunk: Uint8Array): void;
1724
+ /**
1725
+ * Deliver the child exit of a persistent duplex channel.
1726
+ *
1727
+ * @param hostRouteId - The host route identifier the open request carried. The worker echoes it, so the host routes the exact pair.
1728
+ * @param workerSessionId - The worker session identifier the open reply returned.
1729
+ * @param exitCode - The child exit code, or null when the child ended with no code.
1730
+ * @param transportClosed - True when the transport closed with no exit data, so the exit is a reason-less transport close, not a process exit. Absent marks a real process exit.
1731
+ */
1732
+ exit(hostRouteId: string, workerSessionId: string, exitCode: number | null, transportClosed?: boolean): void;
1733
+ }
1734
+ /**
1735
+ * The full plugin context object passed to the plugin worker at initialisation.
1736
+ *
1737
+ * This is the central interface plugin authors use to interact with the host.
1738
+ * Every client is capability-gated: calling a client method without the
1739
+ * required capability declared in the manifest results in a runtime error.
1740
+ *
1741
+ * @example
1742
+ * ```ts
1743
+ * import { definePlugin } from "@tickernelz/paperclip-pro-plugin-sdk";
1744
+ *
1745
+ * export default definePlugin({
1746
+ * async setup(ctx) {
1747
+ * ctx.events.on("issue.created", async (event) => {
1748
+ * ctx.logger.info("Issue created", { issueId: event.entityId });
1749
+ * });
1750
+ *
1751
+ * ctx.data.register("sync-health", async ({ companyId }) => {
1752
+ * const state = await ctx.state.get({ scopeKind: "company", scopeId: String(companyId), stateKey: "last-sync" });
1753
+ * return { lastSync: state };
1754
+ * });
1755
+ * },
1756
+ * });
1757
+ * ```
1758
+ *
1759
+ * @see PLUGIN_SPEC.md §14 — SDK Surface
1760
+ */
1761
+ export interface PluginContext {
1762
+ /** The plugin's manifest as validated at install time. */
1763
+ manifest: PaperclipPluginManifestV1;
1764
+ /** Read resolved operator configuration. */
1765
+ config: PluginConfigClient;
1766
+ /** Configure and safely access trusted company-scoped local folders. */
1767
+ localFolders: PluginLocalFoldersClient;
1768
+ /** Subscribe to and emit domain events. Requires `events.subscribe` / `events.emit`. */
1769
+ events: PluginEventsClient;
1770
+ /** Register handlers for scheduled jobs. Requires `jobs.schedule`. */
1771
+ jobs: PluginJobsClient;
1772
+ /** Register launcher metadata that the host can surface in plugin UI entry points. */
1773
+ launchers: PluginLaunchersClient;
1774
+ /** Restricted plugin-owned database namespace. Requires database namespace capabilities. */
1775
+ db: PluginDatabaseClient;
1776
+ /** Make outbound HTTP requests. Requires `http.outbound`. */
1777
+ http: PluginHttpClient;
1778
+ /** Resolve secret references. Requires `secrets.read-ref`. */
1779
+ secrets: PluginSecretsClient;
1780
+ /** Write activity log entries. Requires `activity.log.write`. */
1781
+ activity: PluginActivityClient;
1782
+ /** Read and write scoped plugin state. Requires `plugin.state.read` / `plugin.state.write`. */
1783
+ state: PluginStateClient;
1784
+ /** Create and query plugin-owned entity records. */
1785
+ entities: PluginEntitiesClient;
1786
+ /** Read project and workspace metadata. Requires `projects.read` / `project.workspaces.read`. */
1787
+ projects: PluginProjectsClient;
1788
+ /** Read execution workspace metadata. Requires `execution.workspaces.read`. */
1789
+ executionWorkspaces: PluginExecutionWorkspacesClient;
1790
+ /** Resolve and reconcile plugin-managed routines. Requires `routines.managed`. */
1791
+ routines: PluginRoutinesClient;
1792
+ /** Resolve and reconcile plugin-managed company skills. Requires `skills.managed`. */
1793
+ skills: PluginSkillsClient;
1794
+ /** Read company metadata. Requires `companies.read`. */
1795
+ companies: PluginCompaniesClient;
1796
+ /** Read and write issues, comments, and documents. Requires issue capabilities. */
1797
+ issues: PluginIssuesClient;
1798
+ /** Read and decide company approvals. Requires `approvals.read` / `approvals.respond`. */
1799
+ approvals: PluginApprovalsClient;
1800
+ /** Read and manage agents. Requires `agents.read` for reads; `agents.pause` / `agents.resume` / `agents.invoke` for write ops. */
1801
+ agents: PluginAgentsClient;
1802
+ /** Read and mutate goals. Requires `goals.read` for reads; `goals.create` / `goals.update` for write ops. */
1803
+ goals: PluginGoalsClient;
1804
+ /** Read and manage access memberships and invites. Requires `access.*` capabilities. */
1805
+ access: PluginAccessClient;
1806
+ /** Read and manage authorization grants, policy summaries, previews, and audit entries. Requires `authorization.*` capabilities. */
1807
+ authorization: PluginAuthorizationClient;
1808
+ /** Register getData handlers for the plugin's UI components. */
1809
+ data: PluginDataClient;
1810
+ /** Register performAction handlers for the plugin's UI components. */
1811
+ actions: PluginActionsClient;
1812
+ /** Push real-time events from the worker to the plugin UI via SSE. */
1813
+ streams: PluginStreamsClient;
1814
+ /** Deliver incremental command output from the active execute call to the
1815
+ * host runner log sink. The default is a no-op for a provider that does not
1816
+ * stream. */
1817
+ execution: PluginExecutionClient;
1818
+ /** Stream one live login pseudo-terminal's output and exit to the host.
1819
+ * The default is a no-op for a provider that opens no login
1820
+ * pseudo-terminal. */
1821
+ loginPty: PluginLoginPtyClient;
1822
+ /** Stream one persistent duplex channel's data and exit to the host. The
1823
+ * default is a no-op for a provider that opens no duplex channel. */
1824
+ duplexChannel: PluginDuplexChannelClient;
1825
+ /** Register agent tool handlers. Requires `agent.tools.register`. */
1826
+ tools: PluginToolsClient;
1827
+ /** Write plugin metrics. Requires `metrics.write`. */
1828
+ metrics: PluginMetricsClient;
1829
+ /** Emit plugin-scoped external telemetry. Requires `telemetry.track`. */
1830
+ telemetry: PluginTelemetryClient;
1831
+ /** Structured logger. Output is captured and surfaced in the plugin health dashboard. */
1832
+ logger: PluginLogger;
1833
+ /** Tracer for provider spans. The default is a no-op; the host records a span
1834
+ * only when tracing is on and an active host trace context is present. */
1835
+ tracer: PluginTracer;
1836
+ }
1837
+ //# sourceMappingURL=types.d.ts.map