/** * `LinearIntegration` — the self-contained Linear integration. * * Implements the system-wide `FactoryIntegration` contract * (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env * vars ONCE, constructs an instance with explicit credentials, and passes it * to `MastraFactory`. Everything Linear-flavored the system does — the OAuth * connect/callback flow, workspace/project/issue reads for Intake, and the * agent's issue tools — flows through this instance. No other module reads * `LINEAR_*` env vars. * * The class owns: * - OAuth: the user-facing authorize URL, code exchange, and refresh-token * rotation against Linear's `/oauth/token` endpoint. * - GraphQL reads/writes: viewer workspace, projects, active issues for * Intake, full issue detail (description + discussion), issue comments. * - The HTTP surface (`routes()`) and per-request agent tools * (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with * `this` as the API client. */ import type { RequestContext } from '@mastra/core/request-context'; import type { ApiRoute } from '@mastra/core/server'; import type { MastraWorker } from '@mastra/core/worker'; import type { Intake } from '../../capabilities/intake.js'; import type { RouteAuth } from '../../routes/route.js'; import type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js'; import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js'; import type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js'; import type { LinearEventRules, LinearRuleOverrides } from './default-rules.js'; import type { LinearConnectionRow, LinearStorageHandle, UpsertLinearConnectionInput } from './storage.js'; /** Credentials and optional event rules for the Linear OAuth application. */ export interface LinearIntegrationConfig { /** Per-event replacements; omitted events retain defaults, null disables. */ rules?: LinearRuleOverrides; /** OAuth client id of the Linear application. */ clientId: string; /** OAuth client secret of the Linear application. */ clientSecret: string; } /** * Tokens minted by Linear's `/oauth/token` endpoint. Linear access tokens * expire (24h) and refresh tokens rotate: every refresh invalidates the old * pair, so callers must persist the whole set after each exchange. */ export interface LinearTokenSet { accessToken: string; /** Null when Linear issued no refresh token (legacy non-expiring apps). */ refreshToken: string | null; /** Null when Linear reported no `expires_in`. */ expiresAt: Date | null; /** Scopes granted to the token as reported by Linear; null when omitted. */ scope: string | null; } export interface LinearWorkspace { name: string; urlKey: string; } export interface LinearIssue { id: string; projectId: string; /** Human key like `ENG-123`. */ identifier: string; title: string; url: string; /** Workflow state name, e.g. `In Progress`. */ state: string; /** Workflow state type, e.g. `backlog` / `unstarted` / `started` / `triage`. */ stateType: string; priorityLabel: string; assignee: string | null; /** Display name of the Linear user who created the issue, when Linear returns one. */ creator: string | null; team: string | null; labels: string[]; createdAt: string; updatedAt: string; } export interface LinearIssuePage { issues: LinearIssue[]; /** Opaque cursor for the next page, or `null` on the last page. */ nextCursor: string | null; } export interface LinearProjectTeam { id: string; /** Short team key, e.g. `ENG`. */ key: string; name: string; } export interface LinearProject { id: string; name: string; /** Project state, e.g. `planned` / `started` / `paused` / `completed`. */ state: string; /** Teams the project belongs to (the Settings picker groups by these). */ teams: LinearProjectTeam[]; } export interface LinearIssueComment { author: string | null; body: string; createdAt: string; } /** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */ export interface LinearIssueDetail extends Omit { projectId: string | null; /** Markdown body of the issue, or `null` when empty. */ description: string | null; /** Discussion comments, oldest first. */ comments: LinearIssueComment[]; } /** The comment created by {@link LinearIntegration.createIssueComment}. */ export interface LinearCreatedComment { id: string; url: string; } /** Thrown when the org's Linear authorization can no longer be renewed. */ export declare class LinearReauthRequiredError extends Error { constructor(); } /** Cached result of {@link LinearIntegration.checkConnection}. */ export interface LinearConnectionCheck { connected: boolean; /** Whether the granted OAuth scope allows posting issue comments. */ canComment: boolean; checkedAt: number; } export declare class LinearIntegration implements FactoryIntegration { #private; /** Stable integration identifier (see `../base.ts`). */ readonly id = "linear"; /** Bind Linear's slice of the generic integration storage, the projects domain, and the host auth seam. */ initialize({ storage, projects, auth, }: { storage: IntegrationStorageHandle; projects: FactoryProjectsStorage; auth: RouteAuth; }): void; get storage(): LinearStorageHandle; /** Factory projects domain — maps a session's resourceId to its owning org. */ get projects(): FactoryProjectsStorage; /** * Whether the host runs with web auth enabled. Linear connections are * org-owned, so every Linear surface is inert without a tenant auth seam. */ get authEnabled(): boolean; /** Load the org's Linear connection, or `null` when not connected. */ loadConnection(orgId: string): Promise; /** Insert or replace the org's connection (one per org). */ upsertConnection(input: UpsertLinearConnectionInput): Promise; /** * Whether the connection's token can post issue comments. Legacy rows * without a recorded scope were minted with `read` only, so they count as * read-only until the org reconnects Linear. */ canPostComments(connection: LinearConnectionRow): boolean; /** * Return a usable access token for the connection, proactively refreshing * it when the recorded expiry is past (or imminent). Throws * `LinearReauthRequiredError` when the token is expired and cannot be * refreshed — the org has to go through the OAuth flow again. */ getFreshAccessToken(connection: LinearConnectionRow): Promise; /** * Whether the org has an active connection and whether its granted scope * allows posting comments. Cached per org with a short TTL — tool-set * resolution runs on every request. */ checkConnection(orgId: string): Promise; /** * Drop the cached connection check for an org. Called after a connection is * persisted so the tools show up on the very next run instead of after the * TTL lapses. */ invalidateConnectionCache(orgId: string): void; /** Map a session's resourceId to its owning org, or `null` when it isn't a project. */ resolveOrgId(resourceId: string): Promise; /** Test hook: clear the org/connection caches between specs. */ clearCaches(): void; readonly intake: Intake; /** * The OAuth connect/callback flow round-trips a signed `state` through * Linear, so a multi-replica deploy needs a deployment-stable state secret. */ readonly requiresStableStateSigner = true; get rules(): LinearEventRules; constructor(config: LinearIntegrationConfig); /** * Build the OAuth authorize URL. `prompt=consent` forces the workspace * picker even for an already-authorized user, so "reconnect" can switch * workspaces. */ buildAuthorizeUrl(state: string, redirectUri: string): string; /** Exchange an OAuth `code` for a workspace-scoped token set. */ exchangeOAuthCode(code: string, redirectUri: string): Promise; /** * Exchange a refresh token for a new token set. Linear rotates refresh * tokens, so the returned set replaces the stored one entirely. A 400/401 * here means the refresh token is invalid/revoked and the org must * re-authorize. */ refreshAccessToken(refreshToken: string): Promise; /** Fetch the workspace (organization) the access token is scoped to. */ fetchWorkspace(accessToken: string): Promise; /** List the workspace's projects (for the Settings intake-source picker). */ listProjects(accessToken: string): Promise; /** * List one page of the workspace's active issues (triage/backlog/unstarted/ * started — completed and canceled are excluded), most recently updated * first. When `projectIds` is provided, only issues from those projects are * returned. */ listActiveIssues(accessToken: string, after?: string, projectIds?: string[], labels?: string[]): Promise; /** * Fetch one issue with its description and comments. `idOrIdentifier` * accepts both the Linear UUID and the human key (`ENG-123`). Returns * `null` when the issue doesn't exist (Linear reports it as an "Entity not * found" error). */ fetchIssueDetail(accessToken: string, idOrIdentifier: string): Promise; /** * Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID * and the human key (`ENG-123`) — the identifier is resolved to a UUID * first because `commentCreate` only accepts UUIDs. Returns `null` when the * issue doesn't exist. */ createIssueComment(accessToken: string, idOrIdentifier: string, body: string): Promise; workers(ctx: IntegrationContext): MastraWorker[]; /** * The integration's HTTP surface: `/web/linear/*` + `/auth/linear/*` Mastra * `apiRoutes` (status, OAuth connect/callback, projects + issues for * Intake). Handlers operate on this instance. */ routes(ctx: IntegrationContext): ApiRoute[]; /** * Org-scoped agent tools: issue detail + comment tools for sessions whose * project belongs to an org with an active Linear connection. */ agentTools(args: { requestContext: RequestContext; }): Promise; /** Non-secret config snapshot for system diagnostics/startup logs. */ diagnostics(): Record; } //# sourceMappingURL=integration.d.ts.map