@mastra/factory 0.2.0 → 0.2.1-alpha.1

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 (47) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/capabilities/intake.d.ts +45 -0
  3. package/dist/capabilities/intake.d.ts.map +1 -1
  4. package/dist/factory.js +920 -549
  5. package/dist/factory.js.map +1 -1
  6. package/dist/index.js +920 -549
  7. package/dist/index.js.map +1 -1
  8. package/dist/integrations/github/integration.d.ts.map +1 -1
  9. package/dist/integrations/github/integration.js +126 -11
  10. package/dist/integrations/github/integration.js.map +1 -1
  11. package/dist/integrations/github/project-lock.d.ts +24 -2
  12. package/dist/integrations/github/project-lock.d.ts.map +1 -1
  13. package/dist/integrations/github/project-lock.js +43 -10
  14. package/dist/integrations/github/project-lock.js.map +1 -1
  15. package/dist/integrations/github/routes.js +41 -10
  16. package/dist/integrations/github/routes.js.map +1 -1
  17. package/dist/integrations/linear/agent-tools.js.map +1 -1
  18. package/dist/integrations/linear/integration.d.ts.map +1 -1
  19. package/dist/integrations/linear/integration.js +60 -1
  20. package/dist/integrations/linear/integration.js.map +1 -1
  21. package/dist/integrations/linear/routes.js.map +1 -1
  22. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  23. package/dist/integrations/platform/github/event-worker.js +7 -1
  24. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  25. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  26. package/dist/integrations/platform/github/integration.js +104 -12
  27. package/dist/integrations/platform/github/integration.js.map +1 -1
  28. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  29. package/dist/integrations/platform/linear/integration.js +91 -0
  30. package/dist/integrations/platform/linear/integration.js.map +1 -1
  31. package/dist/routes/fs.d.ts +16 -0
  32. package/dist/routes/fs.d.ts.map +1 -1
  33. package/dist/routes/fs.js +103 -3
  34. package/dist/routes/fs.js.map +1 -1
  35. package/dist/routes/oauth.js +564 -478
  36. package/dist/routes/oauth.js.map +1 -1
  37. package/dist/routes/projects.d.ts.map +1 -1
  38. package/dist/routes/projects.js +1 -0
  39. package/dist/routes/projects.js.map +1 -1
  40. package/dist/routes/surface.d.ts.map +1 -1
  41. package/dist/routes/surface.js +717 -526
  42. package/dist/routes/surface.js.map +1 -1
  43. package/dist/storage/domains/source-control/base.d.ts +0 -1
  44. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  45. package/dist/storage/domains/source-control/base.js +6 -7
  46. package/dist/storage/domains/source-control/base.js.map +1 -1
  47. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/integrations/platform/linear/integration.ts","../../../../src/integrations/linear/agent-tools.ts","../../../../src/integrations/linear/routes.ts","../../../../src/integrations/linear/integration.ts","../../../../src/integrations/platform/api-client.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearConnectionCheck, LinearIntegration } from '../../linear/integration.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport { logPlatformInfo, PlatformApiClient, PlatformApiError, platformApiClientConfigFromEnv } from '../api-client.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n\n readonly intake: Intake = {\n listSources: async () => {\n const sources = await this.#listProjectSources();\n return sources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n },\n listItems: async ({ sourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor);\n return {\n items: result.issues.map(({ issue, source }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: source.workspace.linearWorkspaceId,\n workspaceName: source.workspace.linearWorkspaceName,\n projectId: source.project.id,\n projectName: source.project.name,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels);\n return {\n issues: result.issues.map(({ issue }) => parseIssue(issue)),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n };\n\n constructor() {\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async #listProjectSources(): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const projectGroups = await Promise.all(\n workspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[]) {\n if (sourceIds.length === 0)\n return { issues: [] as Array<{ issue: LinearIssue; source: ProjectSource }>, nextCursor: null };\n const sources = await this.#listProjectSources();\n const sourceMap = new Map(\n sources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const selected = sourceIds\n .map(sourceId => sourceMap.get(sourceId))\n .filter((source): source is ProjectSource => !!source);\n const cursors = decodeCursor(cursor, sourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n selected.map(async source => {\n const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as Array<{ issue: LinearIssue; source: ProjectSource }>;\n }\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: source.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(source.workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .map(issue => ({ issue, source }));\n }),\n );\n return {\n issues: pages.flat(),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [decodeSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\nfunction encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n if (!cursor) return {};\n if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n try {\n const parsed = JSON.parse(cursor) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n return parsed as Record<string, string | null>;\n } catch {\n throw new Error('Linear cursor is invalid.');\n }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n","/**\n * Linear tools exposed to the coding agent.\n *\n * Wired into the agent through the SDK's async `extraTools` provider: on each\n * tool-set resolution we map the session's resourceId (the factory project id)\n * to its owning org and only expose the Linear tools when that org has a\n * Linear connection. Projects whose org never connected Linear (or when the\n * feature is disabled) see no Linear tools at all — the model is never shown\n * tools it can't use.\n *\n * Tenancy mirrors the Linear API routes: everything is scoped by the org that\n * owns the project, and tokens are refreshed through the integration's shared\n * connection lifecycle.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { LinearIntegration } from './integration.js';\nimport { LinearReauthRequiredError } from './integration.js';\n\nfunction createLinearGetIssueTool(linear: LinearIntegration, orgId: string) {\n return createTool({\n id: 'linear_get_issue',\n description:\n \"Fetch a Linear issue's full details — title, description, state, assignee, labels, priority, and discussion comments. Use this whenever you're working on a Linear issue (e.g. ENG-123) to get its complete context.\",\n inputSchema: z.object({\n issue: z.string().min(1).describe('The Linear issue identifier (e.g. \"ENG-123\") or issue UUID.'),\n }),\n execute: async ({ issue }: { issue: string }) => {\n const connection = await linear.loadConnection(orgId);\n if (!connection) {\n return { error: 'Linear is not connected for this repository. Connect Linear in Settings to fetch issues.' };\n }\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const detail = await linear.intake.getIssue({\n connection: { type: 'oauth', accessToken },\n issueId: issue.trim(),\n });\n if (!detail) {\n return { error: `Linear issue \"${issue}\" was not found in this workspace.` };\n }\n return detail;\n } catch (err) {\n if (err instanceof LinearReauthRequiredError) {\n return { error: err.message };\n }\n return { error: `Failed to fetch Linear issue: ${err instanceof Error ? err.message : String(err)}` };\n }\n },\n });\n}\n\nfunction createLinearCommentTool(linear: LinearIntegration, orgId: string) {\n return createTool({\n id: 'linear_create_comment',\n description:\n 'Post a comment on a Linear issue (e.g. to report investigation findings, link a PR, or ask a clarifying question). The comment is posted as the connected Linear integration, so make clear it comes from the agent.',\n inputSchema: z.object({\n issue: z.string().min(1).describe('The Linear issue identifier (e.g. \"ENG-123\") or issue UUID.'),\n body: z.string().min(1).describe('The comment body, as Linear-flavored markdown.'),\n }),\n execute: async ({ issue, body }: { issue: string; body: string }) => {\n const connection = await linear.loadConnection(orgId);\n if (!connection) {\n return { error: 'Linear is not connected for this repository. Connect Linear in Settings to post comments.' };\n }\n if (!linear.canPostComments(connection)) {\n return {\n error: 'The Linear connection does not have comment permissions. Reconnect Linear in Settings to grant them.',\n };\n }\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const comment = await linear.intake.createComment({\n connection: { type: 'oauth', accessToken },\n issueId: issue.trim(),\n body,\n });\n if (!comment) {\n return { error: `Linear issue \"${issue}\" was not found in this workspace.` };\n }\n return { posted: true, url: comment.url };\n } catch (err) {\n if (err instanceof LinearReauthRequiredError) {\n return { error: err.message };\n }\n return { error: `Failed to post Linear comment: ${err instanceof Error ? err.message : String(err)}` };\n }\n },\n });\n}\n\n/**\n * Async `extraTools` provider: expose Linear tools only when the session's\n * project belongs to an org with an active Linear connection.\n */\nexport async function buildLinearAgentTools({\n requestContext,\n linear,\n}: {\n requestContext: RequestContext;\n /** The integration instance providing the Linear API client. */\n linear: LinearIntegration;\n}): Promise<Record<string, ReturnType<typeof createLinearGetIssueTool> | ReturnType<typeof createLinearCommentTool>>> {\n if (!linear.authEnabled) return {};\n\n const ctx = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n const resourceId = ctx?.resourceId;\n if (!resourceId) return {};\n\n const orgId = await linear.resolveOrgId(resourceId);\n if (!orgId) return {};\n const check = await linear.checkConnection(orgId);\n if (!check.connected) return {};\n\n return {\n linear_get_issue: createLinearGetIssueTool(linear, orgId),\n // Only offered when the granted OAuth scope allows posting comments —\n // connections made before `comments:create` was requested are read-only\n // until the org reconnects Linear.\n ...(check.canComment ? { linear_create_comment: createLinearCommentTool(linear, orgId) } : {}),\n };\n}\n","/**\n * Mastra `apiRoutes` for the Linear intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the WorkOS auth gate.\n * Mirrors the GitHub module: every route re-resolves the authenticated user\n * from the request cookie and scopes all rows by the caller's WorkOS org, so an\n * org can only ever see its own Linear connection and issues.\n *\n * When the feature is disabled (`isLinearFeatureEnabled()` false),\n * `buildLinearRoutes` returns only `GET /web/linear/status`, which reports\n * `enabled:false` so the SPA can cleanly hide all Linear UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { StateSigner } from '../../state-signing.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { IntegrationHooks } from '../base.js';\nimport type { LinearIntegration } from './integration.js';\nimport { LinearReauthRequiredError } from './integration.js';\n\ntype RouteContext = Context;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): RouteContext {\n return c as RouteContext;\n}\n\n/**\n * Non-secret diagnostic snapshot of every Linear feature gate, mirroring the\n * GitHub diagnostics shape. Only booleans — never values.\n */\nexport interface LinearFeatureDiagnostics {\n linearAppConfigured: boolean;\n factoryAuthEnabled: boolean;\n appDbConfigured: boolean;\n}\n\nexport interface MountLinearRoutesOptions {\n /**\n * The integration instance providing OAuth + GraphQL access. Required for\n * everything beyond the disabled `status` route.\n */\n linear?: LinearIntegration;\n /** Host auth seam. Linear connections are org-owned, so the feature is inert without it. */\n auth: RouteAuth;\n /**\n * Absolute base URL of the web server (e.g. `http://localhost:4111`), used to\n * build the OAuth redirect URI when one isn't explicitly configured.\n */\n baseUrl?: string;\n /** Explicit OAuth callback URI; defaults to `<baseUrl>/auth/linear/callback`. */\n redirectUri?: string;\n /**\n * Shared OAuth `state` signer (created once per boot by the factory).\n * Required for the connect/callback flow; when absent, only the disabled\n * `status` route is served.\n */\n stateSigner?: StateSigner;\n /**\n * Cross-integration intake selection domain. Required for the issues route's\n * project filter; when absent, only the disabled `status` route is served.\n */\n intake?: IntakeStorage;\n hooks?: IntegrationHooks;\n}\n\n/**\n * Resolve the org-scoped tenant for a Linear request. The connection is\n * org-owned, so it requires both a signed-in user and an organization — same\n * tenancy rules as the GitHub routes.\n */\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n {\n error: 'organization_required',\n message: 'Linear intake requires an organization. Personal accounts cannot connect Linear.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque Linear pagination cursor from the query string. Cursors\n * are server-issued (`pageInfo.endCursor`), so anything outside a conservative\n * charset/length is rejected rather than forwarded to Linear.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 512 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map a Linear read failure to the API response for the SPA. */\nfunction linearFetchError(c: RouteContext, err: unknown) {\n if (err instanceof LinearReauthRequiredError || (err as { status?: number }).status === 401) {\n return c.json({ error: 'linear_reauth_required', message: new LinearReauthRequiredError().message }, 409);\n }\n return c.json({ error: 'linear_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the Linear routes as Mastra `apiRoutes`. When the feature is disabled,\n * returns only the `status` route so the SPA can detect the disabled state.\n */\nexport function buildLinearRoutes(options: MountLinearRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { linear, auth, stateSigner, intake } = options;\n const enabled = Boolean(linear) && auth.enabled();\n const diagnostics = (): LinearFeatureDiagnostics => ({\n linearAppConfigured: Boolean(linear),\n factoryAuthEnabled: auth.enabled(),\n appDbConfigured: true,\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !linear || !stateSigner) {\n return c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: diagnostics(),\n });\n }\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized', reason: 'auth_required' }, 401);\n\n if (!tenant.orgId) {\n return c.json({\n enabled: true,\n organizationRequired: true,\n connected: false,\n workspace: null,\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n\n const connection = await linear.loadConnection(tenant.orgId);\n return c.json({\n enabled: true,\n connected: Boolean(connection),\n workspace: connection ? { name: connection.workspaceName, urlKey: connection.workspaceUrlKey } : null,\n reason: connection ? 'ready' : 'not_connected',\n diagnostics: diagnostics(),\n });\n },\n }),\n );\n\n // Without the integration instance or a state signer the connect/callback\n // flow cannot talk to Linear or bind the OAuth round-trip to a tenant —\n // serve only the disabled `status` route (mirrors the feature gate).\n if (!enabled || !linear || !stateSigner || !intake) {\n return routes;\n }\n\n const redirectUri = options.redirectUri ?? `${(options.baseUrl ?? '').replace(/\\/$/, '')}/auth/linear/callback`;\n\n // ── Connect: send the user to Linear's OAuth consent screen ─────────────\n routes.push(\n registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n const state = stateSigner.sign(resolved.tenant.orgId, resolved.tenant.userId);\n return c.redirect(linear.buildAuthorizeUrl(state, redirectUri));\n },\n }),\n );\n\n // ── Callback: exchange the code, persist the connection for the org ─────\n routes.push(\n registerApiRoute('/auth/linear/callback', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n const { orgId, userId } = resolved.tenant;\n\n // CSRF / cross-tenant linking protection: the signed state must belong\n // to the same logged-in user *and* their current org.\n const stateTenant = stateSigner.verify(c.req.query('state'));\n if (!stateTenant || stateTenant.userId !== userId || stateTenant.orgId !== orgId) {\n console.warn('[Linear] OAuth callback rejected: state/tenant mismatch.');\n return c.redirect('/?linear=error');\n }\n\n const code = c.req.query('code');\n if (!code) {\n // User denied consent (or Linear returned an error).\n return c.redirect('/?linear=error');\n }\n\n try {\n const tokens = await linear.exchangeOAuthCode(code, redirectUri);\n const workspace = await linear.fetchWorkspace(tokens.accessToken);\n await linear.upsertConnection({\n orgId,\n userId,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n workspaceName: workspace.name,\n workspaceUrlKey: workspace.urlKey,\n });\n } catch (error) {\n console.warn(`[Linear] OAuth callback failed to persist connection for org ${orgId}.`, error);\n return c.redirect('/?linear=error');\n }\n\n return c.redirect('/?linear=connected');\n },\n }),\n );\n\n // ── List the workspace's projects (Settings intake-source picker) ───────\n routes.push(\n registerApiRoute('/web/linear/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const connection = await linear.loadConnection(resolved.tenant.orgId);\n if (!connection) {\n return c.json({ error: 'linear_not_connected', message: 'Connect Linear to list Linear projects.' }, 409);\n }\n\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const projects = await linear.listProjects(accessToken);\n return c.json({ projects });\n } catch (err) {\n return linearFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── List the workspace's active issues (cursor-paged) ───────────────────\n // Respects the caller's intake config: disabled Linear intake 404s the\n // source, and an explicit project selection narrows the issue filter.\n routes.push(\n registerApiRoute('/web/linear/issues', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const after = parseAfterCursor(c.req.query('after'));\n if (after === null) return c.json({ error: 'invalid_cursor' }, 400);\n const factoryProjectId = c.req.query('factoryProjectId');\n if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n\n const connection = await linear.loadConnection(resolved.tenant.orgId);\n if (!connection) {\n return c.json({ error: 'linear_not_connected', message: 'Connect Linear to see intake issues.' }, 409);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n integrationIds: ['linear'],\n });\n const selection = config.linear!;\n if (!selection.enabled) {\n return c.json({ error: 'linear_intake_disabled', message: 'Linear intake is turned off in Settings.' }, 404);\n }\n\n // No projects selected means nothing is synced — don't fan out to Linear.\n const projectIds = selection.sourceIds ?? [];\n if (projectIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const { issues, nextCursor } = await linear.intake.listIssues({\n connection: { type: 'oauth', accessToken },\n sourceIds: projectIds,\n cursor: after,\n });\n const issuePayload = issues.map(issue => ({\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee,\n team: issue.source,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n }));\n if (factoryProjectId && options.hooks?.ingestLinearIssues) {\n await options.hooks.ingestLinearIssues({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n factoryProjectId,\n issues: issuePayload,\n });\n }\n return c.json({ issues: issuePayload, nextCursor });\n } catch (err) {\n return linearFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n","/**\n * `LinearIntegration` — the self-contained Linear integration.\n *\n * Implements the system-wide `FactoryIntegration` contract\n * (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env\n * vars ONCE, constructs an instance with explicit credentials, and passes it\n * to `MastraFactory`. Everything Linear-flavored the system does — the OAuth\n * connect/callback flow, workspace/project/issue reads for Intake, and the\n * agent's issue tools — flows through this instance. No other module reads\n * `LINEAR_*` env vars.\n *\n * The class owns:\n * - OAuth: the user-facing authorize URL, code exchange, and refresh-token\n * rotation against Linear's `/oauth/token` endpoint.\n * - GraphQL reads/writes: viewer workspace, projects, active issues for\n * Intake, full issue detail (description + discussion), issue comments.\n * - The HTTP surface (`routes()`) and per-request agent tools\n * (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with\n * `this` as the API client.\n */\n\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\n\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n ListIntakeIssuesInput,\n} from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';\nimport { buildLinearAgentTools } from './agent-tools.js';\nimport { buildLinearRoutes } from './routes.js';\nimport type { LinearConnectionRow, LinearStorageHandle, UpsertLinearConnectionInput } from './storage.js';\n\nconst LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';\nconst LINEAR_TOKEN_URL = 'https://api.linear.app/oauth/token';\nconst LINEAR_AUTHORIZE_URL = 'https://linear.app/oauth/authorize';\n\n/** Credentials for the Linear OAuth application. All fields are required. */\nexport interface LinearIntegrationConfig {\n /** OAuth client id of the Linear application. */\n clientId: string;\n /** OAuth client secret of the Linear application. */\n clientSecret: string;\n}\n\n/**\n * Tokens minted by Linear's `/oauth/token` endpoint. Linear access tokens\n * expire (24h) and refresh tokens rotate: every refresh invalidates the old\n * pair, so callers must persist the whole set after each exchange.\n */\nexport interface LinearTokenSet {\n accessToken: string;\n /** Null when Linear issued no refresh token (legacy non-expiring apps). */\n refreshToken: string | null;\n /** Null when Linear reported no `expires_in`. */\n expiresAt: Date | null;\n /** Scopes granted to the token as reported by Linear; null when omitted. */\n scope: string | null;\n}\n\nexport interface LinearWorkspace {\n name: string;\n urlKey: string;\n}\n\nexport interface LinearIssue {\n id: string;\n projectId: string;\n /** Human key like `ENG-123`. */\n identifier: string;\n title: string;\n url: string;\n /** Workflow state name, e.g. `In Progress`. */\n state: string;\n /** Workflow state type, e.g. `backlog` / `unstarted` / `started` / `triage`. */\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n team: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LinearIssuePage {\n issues: LinearIssue[];\n /** Opaque cursor for the next page, or `null` on the last page. */\n nextCursor: string | null;\n}\n\nexport interface LinearProjectTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n}\n\nexport interface LinearProject {\n id: string;\n name: string;\n /** Project state, e.g. `planned` / `started` / `paused` / `completed`. */\n state: string;\n /** Teams the project belongs to (the Settings picker groups by these). */\n teams: LinearProjectTeam[];\n}\n\nexport interface LinearIssueComment {\n author: string | null;\n body: string;\n createdAt: string;\n}\n\n/** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */\nexport interface LinearIssueDetail extends LinearIssue {\n /** Markdown body of the issue, or `null` when empty. */\n description: string | null;\n /** Discussion comments, oldest first. */\n comments: LinearIssueComment[];\n}\n\n/** The comment created by {@link LinearIntegration.createIssueComment}. */\nexport interface LinearCreatedComment {\n id: string;\n url: string;\n}\n\nconst LINEAR_ISSUES_PAGE_SIZE = 30;\nconst ISSUE_COMMENTS_PAGE_SIZE = 50;\n/** Hard stop for comment pagination so a misbehaving cursor can't loop forever. */\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Refresh this many ms before the recorded expiry to absorb clock skew. */\nconst TOKEN_REFRESH_SKEW_MS = 60_000;\n\n/** Re-check an org's Linear connection (and its scopes) at most this often. */\nconst CONNECTION_TTL_MS = 60_000;\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Thrown when the org's Linear authorization can no longer be renewed. */\nexport class LinearReauthRequiredError extends Error {\n constructor() {\n super('Linear authorization expired. Reconnect Linear to keep syncing intake issues.');\n }\n}\n\n/** Cached result of {@link LinearIntegration.checkConnection}. */\nexport interface LinearConnectionCheck {\n connected: boolean;\n /** Whether the granted OAuth scope allows posting issue comments. */\n canComment: boolean;\n checkedAt: number;\n}\n\ninterface IssuesQueryData {\n issues: {\n nodes: Array<{\n id: string;\n identifier: string;\n title: string;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string };\n assignee: { name: string } | null;\n team: { key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\ninterface IssueCommentNode {\n body: string;\n createdAt: string;\n user: { name: string } | null;\n}\n\ninterface IssueCommentsPage {\n nodes: IssueCommentNode[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n}\n\ninterface IssueDetailQueryData {\n issue: {\n id: string;\n identifier: string;\n title: string;\n description: string | null;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string };\n assignee: { name: string } | null;\n team: { key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n comments: IssueCommentsPage;\n } | null;\n}\n\ninterface IssueCommentsQueryData {\n issue: { comments: IssueCommentsPage } | null;\n}\n\ninterface IssueIdQueryData {\n issue: { id: string } | null;\n}\n\ninterface CommentCreateMutationData {\n commentCreate: { success: boolean; comment: { id: string; url: string } | null };\n}\n\n/** POST a GraphQL query to Linear with the given OAuth access token. */\nasync function linearGraphql<T>(accessToken: string, query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(LINEAR_GRAPHQL_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n // Linear returns GraphQL errors (validation, missing scopes, …) with a\n // 400 status — surface the actual message instead of just the code.\n let detail: string | null = null;\n try {\n const errBody = (await res.json()) as { errors?: Array<{ message?: string }> };\n detail = errBody.errors?.[0]?.message ?? null;\n } catch {\n // Non-JSON error body; fall back to the status code alone.\n }\n const err = new Error(`Linear API request failed (${res.status})${detail ? `: ${detail}` : ''}`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as { data?: T; errors?: Array<{ message?: string }> };\n if (body.errors?.length) {\n throw new Error(`Linear API error: ${body.errors[0]?.message ?? 'unknown error'}`);\n }\n if (!body.data) {\n throw new Error('Linear API returned no data.');\n }\n return body.data;\n}\n\nexport class LinearIntegration implements FactoryIntegration {\n /** Stable integration identifier (see `../base.ts`). */\n readonly id = 'linear';\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #storage: LinearStorageHandle | undefined;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n\n /** Bind Linear's slice of the generic integration storage, the projects domain, and the host auth seam. */\n initialize({\n storage,\n projects,\n auth,\n }: {\n storage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n }): void {\n this.#storage = storage as unknown as LinearStorageHandle;\n this.#projects = projects;\n this.#auth = auth;\n }\n\n get storage(): LinearStorageHandle {\n if (!this.#storage) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#storage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /**\n * Whether the host runs with web auth enabled. Linear connections are\n * org-owned, so every Linear surface is inert without a tenant auth seam.\n */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n // ── Connection + OAuth token lifecycle ───────────────────────────────────\n\n /** Load the org's Linear connection, or `null` when not connected. */\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const connection = await this.storage.connections.get(orgId);\n if (!connection) return null;\n const { data } = connection;\n return {\n id: connection.id,\n orgId: connection.orgId,\n userId: connection.userId,\n accessToken: data.accessToken,\n scope: data.scope ?? null,\n refreshToken: data.refreshToken ?? null,\n expiresAt: data.expiresAtMs === null || data.expiresAtMs === undefined ? null : new Date(data.expiresAtMs),\n workspaceName: data.workspaceName ?? null,\n workspaceUrlKey: data.workspaceUrlKey ?? null,\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n };\n }\n\n /** Insert or replace the org's connection (one per org). */\n async upsertConnection(input: UpsertLinearConnectionInput): Promise<void> {\n await this.storage.connections.upsert(input.orgId, {\n userId: input.userId,\n data: {\n accessToken: input.accessToken,\n refreshToken: input.refreshToken,\n expiresAtMs: input.expiresAt?.getTime() ?? null,\n scope: input.scope,\n workspaceName: input.workspaceName,\n workspaceUrlKey: input.workspaceUrlKey,\n },\n });\n // Let the agent tools see the new connection immediately.\n this.invalidateConnectionCache(input.orgId);\n }\n\n /** Persist a rotated token set on the org's existing connection row. */\n async #updateTokens(orgId: string, tokens: LinearTokenSet): Promise<void> {\n await this.storage.connections.update(orgId, data => ({\n ...data,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAtMs: tokens.expiresAt?.getTime() ?? null,\n // Refresh responses may omit scope; keep the recorded grant.\n scope: tokens.scope ?? data.scope,\n }));\n }\n\n /**\n * Whether the connection's token can post issue comments. Legacy rows\n * without a recorded scope were minted with `read` only, so they count as\n * read-only until the org reconnects Linear.\n */\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n /**\n * In-flight refreshes keyed by org. Linear rotates refresh tokens, so two\n * concurrent refreshes with the same token would invalidate each other —\n * single-flight ensures one exchange per org and shares the result.\n */\n readonly #inflightRefreshes = new Map<string, Promise<string>>();\n\n /**\n * Return a usable access token for the connection, proactively refreshing\n * it when the recorded expiry is past (or imminent). Throws\n * `LinearReauthRequiredError` when the token is expired and cannot be\n * refreshed — the org has to go through the OAuth flow again.\n */\n async getFreshAccessToken(connection: LinearConnectionRow): Promise<string> {\n const expired =\n connection.expiresAt !== null && connection.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!expired) return connection.accessToken;\n\n if (!connection.refreshToken) {\n // Legacy row from before refresh-token support: nothing to renew with.\n throw new LinearReauthRequiredError();\n }\n\n const existing = this.#inflightRefreshes.get(connection.orgId);\n if (existing) return existing;\n\n // The caller may hold a stale row: another request could have refreshed\n // and rotated the refresh token since this row was loaded. Reload before\n // refreshing so we don't burn the rotated token and force a false reauth.\n const latest = await this.loadConnection(connection.orgId);\n if (!latest) throw new LinearReauthRequiredError();\n\n const concurrent = this.#inflightRefreshes.get(connection.orgId);\n if (concurrent) return concurrent;\n\n const latestExpired = latest.expiresAt !== null && latest.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!latestExpired) return latest.accessToken;\n if (!latest.refreshToken) throw new LinearReauthRequiredError();\n\n const refreshToken = latest.refreshToken;\n const refresh = (async () => {\n try {\n const tokens = await this.refreshAccessToken(refreshToken);\n await this.#updateTokens(connection.orgId, tokens);\n return tokens.accessToken;\n } catch (err) {\n const status = (err as { status?: number }).status;\n // invalid_grant surfaces as 400/401: the refresh token was revoked or\n // already rotated away. Terminal for this connection.\n if (status === 400 || status === 401) throw new LinearReauthRequiredError();\n throw err;\n } finally {\n this.#inflightRefreshes.delete(connection.orgId);\n }\n })();\n this.#inflightRefreshes.set(connection.orgId, refresh);\n return refresh;\n }\n\n // ── Connection checks for agent tools ────────────────────────────────────\n\n /** Re-check an org's Linear connection (and its scopes) at most this often. */\n readonly #connectionChecks = new Map<string, LinearConnectionCheck>();\n\n /**\n * Whether the org has an active connection and whether its granted scope\n * allows posting comments. Cached per org with a short TTL — tool-set\n * resolution runs on every request.\n */\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const cached = this.#connectionChecks.get(orgId);\n if (cached && Date.now() - cached.checkedAt < CONNECTION_TTL_MS) return cached;\n const connection = await this.loadConnection(orgId);\n const check: LinearConnectionCheck = {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n this.#connectionChecks.set(orgId, check);\n return check;\n }\n\n /**\n * Drop the cached connection check for an org. Called after a connection is\n * persisted so the tools show up on the very next run instead of after the\n * TTL lapses.\n */\n invalidateConnectionCache(orgId: string): void {\n this.#connectionChecks.delete(orgId);\n }\n\n /**\n * A project's org never changes, so the resourceId → orgId mapping is\n * cached forever. `null` marks resource ids that aren't factory projects\n * (e.g. local default resources) so we don't re-query them on every\n * tool-set resolution.\n */\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n /** Map a session's resourceId to its owning org, or `null` when it isn't a project. */\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n // Non-UUID resource ids (local/dev resources) would make the uuid column\n // comparison throw — they're definitively \"not a project\", so cache that.\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org/connection caches between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#connectionChecks.clear();\n }\n\n readonly intake: Intake = {\n listSources: async ({ orgId }) => {\n const connection = await this.loadConnection(orgId);\n if (!connection) return [];\n const accessToken = await this.getFreshAccessToken(connection);\n const projects = await this.listProjects(accessToken);\n return projects.map(project => ({\n id: project.id,\n name: project.name,\n type: 'project',\n }));\n },\n listItems: async ({ orgId, sourceIds, cursor }) => {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const connection = await this.loadConnection(orgId);\n if (!connection) return { items: [], nextCursor: null };\n const accessToken = await this.getFreshAccessToken(connection);\n const page = await this.listActiveIssues(accessToken, cursor, sourceIds);\n return {\n items: page.issues.map(issue => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: issue.projectId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state,\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n team: issue.team,\n },\n })),\n nextCursor: page.nextCursor,\n };\n },\n listIssues: input => this.#listIntakeIssues(input),\n getIssue: input => this.#getIntakeIssue(input),\n createComment: input => this.#createIntakeComment(input),\n };\n /**\n * The OAuth connect/callback flow round-trips a signed `state` through\n * Linear, so a multi-replica deploy needs a deployment-stable state secret.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #clientId: string;\n readonly #clientSecret: string;\n\n constructor(config: LinearIntegrationConfig) {\n const missing = (['clientId', 'clientSecret'] as const).filter(key => !config[key]);\n if (missing.length > 0) {\n throw new Error(`LinearIntegration is missing required config: ${missing.join(', ')}.`);\n }\n this.#clientId = config.clientId;\n this.#clientSecret = config.clientSecret;\n }\n\n // ── OAuth ────────────────────────────────────────────────────────────────\n\n /**\n * Build the OAuth authorize URL. `prompt=consent` forces the workspace\n * picker even for an already-authorized user, so \"reconnect\" can switch\n * workspaces.\n */\n buildAuthorizeUrl(state: string, redirectUri: string): string {\n const url = new URL(LINEAR_AUTHORIZE_URL);\n url.searchParams.set('client_id', this.#clientId);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('response_type', 'code');\n // `comments:create` lets the agent's linear_create_comment tool post\n // comments; everything else the integration does is read-only.\n url.searchParams.set('scope', 'read,comments:create');\n url.searchParams.set('state', state);\n url.searchParams.set('prompt', 'consent');\n return url.toString();\n }\n\n /** Exchange an OAuth `code` for a workspace-scoped token set. */\n async exchangeOAuthCode(code: string, redirectUri: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'authorization_code', code, redirect_uri: redirectUri }, 'token exchange');\n }\n\n /**\n * Exchange a refresh token for a new token set. Linear rotates refresh\n * tokens, so the returned set replaces the stored one entirely. A 400/401\n * here means the refresh token is invalid/revoked and the org must\n * re-authorize.\n */\n async refreshAccessToken(refreshToken: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'refresh_token', refresh_token: refreshToken }, 'token refresh');\n }\n\n /** POST to Linear's token endpoint and normalize the response. */\n async #requestTokens(params: Record<string, string>, label: string): Promise<LinearTokenSet> {\n const res = await fetch(LINEAR_TOKEN_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(10_000),\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n ...params,\n client_id: this.#clientId,\n client_secret: this.#clientSecret,\n }),\n });\n if (!res.ok) {\n const err = new Error(`Linear ${label} failed (${res.status})`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n if (!body.access_token) {\n throw new Error(`Linear ${label} returned no access token.`);\n }\n return {\n accessToken: body.access_token,\n refreshToken: body.refresh_token ?? null,\n expiresAt: typeof body.expires_in === 'number' ? new Date(Date.now() + body.expires_in * 1000) : null,\n scope: body.scope ?? null,\n };\n }\n\n // ── GraphQL reads/writes ─────────────────────────────────────────────────\n\n /** Fetch the workspace (organization) the access token is scoped to. */\n async fetchWorkspace(accessToken: string): Promise<LinearWorkspace> {\n const data = await linearGraphql<{ organization: { name: string; urlKey: string } }>(\n accessToken,\n `query { organization { name urlKey } }`,\n );\n return { name: data.organization.name, urlKey: data.organization.urlKey };\n }\n\n async #listIntakeIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const accessToken = getLinearAccessToken(input.connection);\n const result = await this.listActiveIssues(accessToken, input.cursor, input.sourceIds, input.labels);\n return {\n issues: result.issues.map(issue => linearIssueToIntakeIssue(issue)),\n nextCursor: result.nextCursor,\n };\n }\n\n async #getIntakeIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n return {\n ...linearIssueToIntakeIssue(issue),\n description: issue.description,\n commentCount: issue.comments.length,\n comments: issue.comments,\n };\n }\n\n async #createIntakeComment(input: CreateIntakeCommentInput): Promise<{ id: string; url: string } | null> {\n const accessToken = getLinearAccessToken(input.connection);\n return this.createIssueComment(accessToken, input.issueId, input.body);\n }\n\n /** List the workspace's projects (for the Settings intake-source picker). */\n async listProjects(accessToken: string): Promise<LinearProject[]> {\n const data = await linearGraphql<{\n projects: {\n nodes: Array<{\n id: string;\n name: string;\n state: string;\n teams: { nodes: Array<{ id: string; key: string; name: string }> };\n }>;\n };\n }>(\n accessToken,\n `query { projects(first: 100) { nodes { id name state teams(first: 10) { nodes { id key name } } } } }`,\n );\n return data.projects.nodes.map(node => ({\n id: node.id,\n name: node.name,\n state: node.state,\n teams: node.teams.nodes.map(team => ({ id: team.id, key: team.key, name: team.name })),\n }));\n }\n\n /**\n * List one page of the workspace's active issues (triage/backlog/unstarted/\n * started — completed and canceled are excluded), most recently updated\n * first. When `projectIds` is provided, only issues from those projects are\n * returned.\n */\n async listActiveIssues(\n accessToken: string,\n after?: string,\n projectIds?: string[],\n labels?: string[],\n ): Promise<LinearIssuePage> {\n const normalizedLabels = [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n const projectFilter = projectIds?.length ? ', project: { id: { in: $projectIds } }' : '';\n const projectVar = projectIds?.length ? ', $projectIds: [ID!]' : '';\n const labelFilter = normalizedLabels.length > 0 ? ', labels: { name: { in: $labels } }' : '';\n const labelVar = normalizedLabels.length > 0 ? ', $labels: [String!]' : '';\n const data = await linearGraphql<IssuesQueryData>(\n accessToken,\n `query Intake($first: Int!, $after: String${projectVar}${labelVar}) {\n issues(\n first: $first\n after: $after\n orderBy: updatedAt\n filter: { state: { type: { in: [\"triage\", \"backlog\", \"unstarted\", \"started\"] } }${projectFilter}${labelFilter} }\n ) {\n nodes {\n id\n identifier\n title\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n team { key }\n labels { nodes { name } }\n }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n {\n first: LINEAR_ISSUES_PAGE_SIZE,\n after: after ?? null,\n ...(projectIds?.length ? { projectIds } : {}),\n ...(normalizedLabels.length > 0 ? { labels: normalizedLabels } : {}),\n },\n );\n const { nodes, pageInfo } = data.issues;\n return {\n issues: nodes.map(node => ({\n id: node.id,\n projectId: node.project.id,\n identifier: node.identifier,\n title: node.title,\n url: node.url,\n state: node.state.name,\n stateType: node.state.type,\n priorityLabel: node.priorityLabel,\n assignee: node.assignee?.name ?? null,\n team: node.team?.key ?? null,\n labels: node.labels.nodes.map(label => label.name),\n createdAt: node.createdAt,\n updatedAt: node.updatedAt,\n })),\n nextCursor: pageInfo.hasNextPage ? pageInfo.endCursor : null,\n };\n }\n\n /** Follow `comments.pageInfo` until exhausted so long discussions aren't truncated. */\n async #fetchRemainingIssueComments(\n accessToken: string,\n issueId: string,\n firstPage: IssueCommentsPage,\n ): Promise<IssueCommentNode[]> {\n const nodes = [...firstPage.nodes];\n let { hasNextPage, endCursor } = firstPage.pageInfo;\n for (let page = 1; hasNextPage && endCursor && page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const data = await linearGraphql<IssueCommentsQueryData>(\n accessToken,\n `query IssueComments($id: String!, $first: Int!, $after: String!) {\n issue(id: $id) {\n comments(first: $first, after: $after) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: issueId, first: ISSUE_COMMENTS_PAGE_SIZE, after: endCursor },\n );\n const comments = data.issue?.comments;\n if (!comments) break;\n nodes.push(...comments.nodes);\n ({ hasNextPage, endCursor } = comments.pageInfo);\n }\n return nodes;\n }\n\n /**\n * Fetch one issue with its description and comments. `idOrIdentifier`\n * accepts both the Linear UUID and the human key (`ENG-123`). Returns\n * `null` when the issue doesn't exist (Linear reports it as an \"Entity not\n * found\" error).\n */\n async fetchIssueDetail(accessToken: string, idOrIdentifier: string): Promise<LinearIssueDetail | null> {\n let data: IssueDetailQueryData;\n try {\n data = await linearGraphql<IssueDetailQueryData>(\n accessToken,\n `query IssueDetail($id: String!, $commentsFirst: Int!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n team { key }\n labels { nodes { name } }\n comments(first: $commentsFirst) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: idOrIdentifier, commentsFirst: ISSUE_COMMENTS_PAGE_SIZE },\n );\n } catch (err) {\n // Linear surfaces unknown ids/identifiers as a GraphQL \"Entity not\n // found\" error rather than a null node — map that to \"issue doesn't\n // exist\".\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const issue = data.issue;\n if (!issue) return null;\n const allComments = await this.#fetchRemainingIssueComments(accessToken, issue.id, issue.comments);\n const comments = allComments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return {\n id: issue.id,\n projectId: issue.project.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description?.trim() ? issue.description : null,\n url: issue.url,\n state: issue.state.name,\n stateType: issue.state.type,\n priorityLabel: issue.priorityLabel,\n assignee: issue.assignee?.name ?? null,\n team: issue.team?.key ?? null,\n labels: issue.labels.nodes.map(label => label.name),\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n comments: comments.map(comment => ({\n author: comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n }\n\n /**\n * Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID\n * and the human key (`ENG-123`) — the identifier is resolved to a UUID\n * first because `commentCreate` only accepts UUIDs. Returns `null` when the\n * issue doesn't exist.\n */\n async createIssueComment(\n accessToken: string,\n idOrIdentifier: string,\n body: string,\n ): Promise<LinearCreatedComment | null> {\n let issueId: string;\n try {\n const data = await linearGraphql<IssueIdQueryData>(\n accessToken,\n `query IssueId($id: String!) { issue(id: $id) { id } }`,\n { id: idOrIdentifier },\n );\n if (!data.issue) return null;\n issueId = data.issue.id;\n } catch (err) {\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const data = await linearGraphql<CommentCreateMutationData>(\n accessToken,\n `mutation CommentCreate($input: CommentCreateInput!) {\n commentCreate(input: $input) { success comment { id url } }\n }`,\n { input: { issueId, body } },\n );\n if (!data.commentCreate.success || !data.commentCreate.comment) {\n throw new Error('Linear did not accept the comment.');\n }\n return data.commentCreate.comment;\n }\n\n // ── FactoryIntegration surface ───────────────────────────────────────────\n\n /**\n * The integration's HTTP surface: `/web/linear/*` + `/auth/linear/*` Mastra\n * `apiRoutes` (status, OAuth connect/callback, projects + issues for\n * Intake). Handlers operate on this instance.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildLinearRoutes({\n linear: this,\n auth: ctx.auth,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n hooks: ctx.hooks,\n });\n }\n\n /**\n * Org-scoped agent tools: issue detail + comment tools for sessions whose\n * project belongs to an org with an active Linear connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext: args.requestContext, linear: this });\n }\n\n /** Non-secret config snapshot for system diagnostics/startup logs. */\n diagnostics(): Record<string, unknown> {\n return {\n oauthAppConfigured: true,\n };\n }\n}\n\nfunction getLinearAccessToken(connection: IntegrationConnection): string {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n return connection.accessToken;\n}\n\nfunction linearIssueToIntakeIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: null,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n assignee: issue.assignee,\n source: issue.team,\n labels: issue.labels,\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n","export interface PlatformApiClientConfig {\n baseUrl: string;\n accessToken: string;\n fetchImpl?: typeof fetch;\n}\n\nexport function platformApiClientConfigFromEnv(): PlatformApiClientConfig {\n const sharedApiUrl = process.env.MASTRA_SHARED_API_URL?.trim() || 'https://platform.mastra.ai/v1';\n const accessToken = process.env.MASTRA_PLATFORM_SECRET_KEY?.trim();\n if (!accessToken) {\n throw new Error('Platform integration: missing required environment variable MASTRA_PLATFORM_SECRET_KEY.');\n }\n return { baseUrl: normalizeSharedApiUrl(sharedApiUrl), accessToken };\n}\n\nfunction normalizeSharedApiUrl(sharedApiUrl: string): string {\n return sharedApiUrl.replace(/\\/+$/, '').replace(/\\/v1$/, '');\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly retryAfterSeconds: number | null;\n\n constructor(message: string, status: number, retryAfterSeconds: number | null = null) {\n super(message);\n this.name = 'PlatformApiError';\n this.status = status;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nexport class PlatformApiClient {\n readonly #baseUrl: string;\n readonly #accessToken: string;\n readonly #fetch: typeof fetch;\n\n constructor(config: PlatformApiClientConfig) {\n const missing = ['baseUrl', 'accessToken'].filter(field => !config[field as keyof PlatformApiClientConfig]);\n if (missing.length > 0) {\n throw new Error(`Platform integration: missing required config field(s): ${missing.join(', ')}.`);\n }\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.#accessToken = config.accessToken;\n this.#fetch = config.fetchImpl ?? globalThis.fetch;\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): Promise<T> {\n const response = await this.#send(method, path, body, options);\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n async requestRedirect(method: string, path: string, options?: { signal?: AbortSignal }): Promise<string> {\n const response = await this.#send(method, path, undefined, options, 'manual');\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (location) return location;\n }\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API redirect request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n logPlatformError('Platform API request did not return a redirect', { method, path, status: response.status });\n throw new PlatformApiError('Platform API request did not return a redirect.', response.status);\n }\n\n async #send(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\n }\n const timeoutSignal = AbortSignal.timeout(15_000);\n const init: RequestInit = {\n method,\n headers,\n redirect,\n signal: options?.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal,\n };\n if (body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n try {\n return await this.#fetch(`${this.#baseUrl}${path}`, init);\n } catch (error) {\n if (error instanceof Error && error.message.includes(this.#accessToken)) {\n const redacted = new Error(redact(error.message, this.#accessToken));\n redacted.name = error.name;\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: redacted.name,\n message: redacted.message,\n });\n throw redacted;\n }\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: error instanceof Error ? error.name : undefined,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n\nasync function extractError(response: Response): Promise<string> {\n try {\n const data = (await response.clone().json()) as Record<string, unknown>;\n for (const field of ['detail', 'error', 'title']) {\n if (typeof data[field] === 'string' && data[field]) return data[field];\n }\n } catch {\n // Fall through to the status-based message.\n }\n return `Platform API request failed (${response.status})`;\n}\n\nfunction redact(message: string, accessToken: string): string {\n return message.split(accessToken).join('[REDACTED]');\n}\n\nexport function logPlatformInfo(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('info', message, fields);\n}\n\nexport function logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', message, fields);\n}\n\nexport function logPlatformError(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('error', message, fields);\n}\n\nfunction writePlatformLog(level: 'info' | 'warn' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[Mastra Factory] ${level.toUpperCase()} ${message}${metadata}\\n`);\n}\n\nfunction stripUndefined(fields: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const seconds = Number.parseInt(value, 10);\n return Number.isSafeInteger(seconds) && seconds >= 0 ? seconds : null;\n}\n"],"mappings":"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","names":["registerApiRoute","loose","registerApiRoute"]}
1
+ {"version":3,"sources":["../../../../src/integrations/platform/linear/integration.ts","../../../../src/integrations/linear/agent-tools.ts","../../../../src/integrations/linear/routes.ts","../../../../src/integrations/linear/integration.ts","../../../../src/integrations/platform/api-client.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail, UpdateIntakeIssueInput } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearConnectionCheck, LinearIntegration } from '../../linear/integration.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport {\n logPlatformInfo,\n logPlatformWarn,\n PlatformApiClient,\n PlatformApiError,\n platformApiClientConfigFromEnv,\n} from '../api-client.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\ntype LinearWorkflowState = {\n id: string;\n name: string;\n type: string;\n position: number;\n teamId: string;\n};\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n /**\n * Per-instance workflow-state cache keyed by `${workspaceId}:${teamId}`. Scoped to the process\n * lifetime; not shared across requests intentionally to keep failure modes simple (stale states\n * clear on process restart). Populated lazily on first `updateIssue` per team.\n */\n readonly #workflowStatesByTeam = new Map<string, LinearWorkflowState[]>();\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async () => {\n const sources = await this.#listProjectSources();\n return sources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n },\n listItems: async ({ sourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor);\n return {\n items: result.issues.map(({ issue, source }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: source.workspace.linearWorkspaceId,\n workspaceName: source.workspace.linearWorkspaceName,\n projectId: source.project.id,\n projectName: source.project.name,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels);\n return {\n issues: result.issues.map(({ issue }) => parseIssue(issue)),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n updateIssue: async (input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> => {\n requireLinearConnection(input.connection);\n const located = await this.#findIssue(input.sourceId, input.issueId);\n if (!located) return null;\n const { workspaceId, issue } = located;\n\n const target = input.state;\n // Idempotency: skip the write entirely if the issue is already at the target state.\n if (target.kind === 'byType' && issue.state.type === target.stateType) {\n return parseIssue(issue);\n }\n if (target.kind === 'byName' && issue.state.name.toLowerCase() === target.name.toLowerCase()) {\n return parseIssue(issue);\n }\n\n let states: LinearWorkflowState[];\n try {\n states = await this.#listWorkflowStates(workspaceId, issue.team.id);\n } catch (error) {\n if (isNotFound(error)) {\n // Platform companion endpoint not deployed yet — degrade to policy skip so PR 1\n // is standalone-mergeable ahead of the platform-side workflow-states route landing.\n logPlatformWarn('Platform Linear: workflow-states endpoint not available; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n });\n return null;\n }\n throw error;\n }\n let matched: LinearWorkflowState | undefined;\n if (target.kind === 'byType') {\n matched = states\n .slice()\n .sort((a, b) => a.position - b.position)\n .find(s => s.type === target.stateType);\n } else {\n const wanted = target.name.toLowerCase();\n matched = states.find(s => s.name.toLowerCase() === wanted);\n }\n if (!matched) {\n logPlatformWarn('Platform Linear: no workflow state matched target; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n target,\n });\n return null;\n }\n\n try {\n await this.#client.request<LinearIssue>(\n 'PATCH',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(input.issueId)}`,\n { stateId: matched.id },\n );\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n\n const refreshed = await this.#findIssue(input.sourceId, input.issueId);\n return refreshed ? parseIssue(refreshed.issue) : null;\n },\n };\n\n constructor() {\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n /**\n * Background-dispatch context: platform-managed connection token when the\n * org has a connected workspace. Linear work items store the issue UUID\n * directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n return {\n connection: { type: 'oauth', accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN },\n issueId: externalSource.externalId,\n };\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async #listProjectSources(): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const projectGroups = await Promise.all(\n workspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[]) {\n if (sourceIds.length === 0)\n return { issues: [] as Array<{ issue: LinearIssue; source: ProjectSource }>, nextCursor: null };\n const sources = await this.#listProjectSources();\n const sourceMap = new Map(\n sources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const selected = sourceIds\n .map(sourceId => sourceMap.get(sourceId))\n .filter((source): source is ProjectSource => !!source);\n const cursors = decodeCursor(cursor, sourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n selected.map(async source => {\n const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as Array<{ issue: LinearIssue; source: ProjectSource }>;\n }\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: source.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(source.workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .map(issue => ({ issue, source }));\n }),\n );\n return {\n issues: pages.flat(),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #listWorkflowStates(workspaceId: string, teamId: string): Promise<LinearWorkflowState[]> {\n const cacheKey = `${workspaceId}:${teamId}`;\n const cached = this.#workflowStatesByTeam.get(cacheKey);\n if (cached) return cached;\n const result = await this.#client.request<{ workflowStates: LinearWorkflowState[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/workflow-states?teamId=${encodeURIComponent(teamId)}&first=100`,\n );\n this.#workflowStatesByTeam.set(cacheKey, result.workflowStates);\n return result.workflowStates;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [decodeSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\nfunction encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n if (!cursor) return {};\n if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n try {\n const parsed = JSON.parse(cursor) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n return parsed as Record<string, string | null>;\n } catch {\n throw new Error('Linear cursor is invalid.');\n }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n","/**\n * Linear tools exposed to the coding agent.\n *\n * Wired into the agent through the SDK's async `extraTools` provider: on each\n * tool-set resolution we map the session's resourceId (the factory project id)\n * to its owning org and only expose the Linear tools when that org has a\n * Linear connection. Projects whose org never connected Linear (or when the\n * feature is disabled) see no Linear tools at all — the model is never shown\n * tools it can't use.\n *\n * Tenancy mirrors the Linear API routes: everything is scoped by the org that\n * owns the project, and tokens are refreshed through the integration's shared\n * connection lifecycle.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { LinearIntegration } from './integration.js';\nimport { LinearReauthRequiredError } from './integration.js';\n\nfunction createLinearGetIssueTool(linear: LinearIntegration, orgId: string) {\n return createTool({\n id: 'linear_get_issue',\n description:\n \"Fetch a Linear issue's full details — title, description, state, assignee, labels, priority, and discussion comments. Use this whenever you're working on a Linear issue (e.g. ENG-123) to get its complete context.\",\n inputSchema: z.object({\n issue: z.string().min(1).describe('The Linear issue identifier (e.g. \"ENG-123\") or issue UUID.'),\n }),\n execute: async ({ issue }: { issue: string }) => {\n const connection = await linear.loadConnection(orgId);\n if (!connection) {\n return { error: 'Linear is not connected for this repository. Connect Linear in Settings to fetch issues.' };\n }\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const detail = await linear.intake.getIssue({\n connection: { type: 'oauth', accessToken },\n issueId: issue.trim(),\n });\n if (!detail) {\n return { error: `Linear issue \"${issue}\" was not found in this workspace.` };\n }\n return detail;\n } catch (err) {\n if (err instanceof LinearReauthRequiredError) {\n return { error: err.message };\n }\n return { error: `Failed to fetch Linear issue: ${err instanceof Error ? err.message : String(err)}` };\n }\n },\n });\n}\n\nfunction createLinearCommentTool(linear: LinearIntegration, orgId: string) {\n return createTool({\n id: 'linear_create_comment',\n description:\n 'Post a comment on a Linear issue (e.g. to report investigation findings, link a PR, or ask a clarifying question). The comment is posted as the connected Linear integration, so make clear it comes from the agent.',\n inputSchema: z.object({\n issue: z.string().min(1).describe('The Linear issue identifier (e.g. \"ENG-123\") or issue UUID.'),\n body: z.string().min(1).describe('The comment body, as Linear-flavored markdown.'),\n }),\n execute: async ({ issue, body }: { issue: string; body: string }) => {\n const connection = await linear.loadConnection(orgId);\n if (!connection) {\n return { error: 'Linear is not connected for this repository. Connect Linear in Settings to post comments.' };\n }\n if (!linear.canPostComments(connection)) {\n return {\n error: 'The Linear connection does not have comment permissions. Reconnect Linear in Settings to grant them.',\n };\n }\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const comment = await linear.intake.createComment({\n connection: { type: 'oauth', accessToken },\n issueId: issue.trim(),\n body,\n });\n if (!comment) {\n return { error: `Linear issue \"${issue}\" was not found in this workspace.` };\n }\n return { posted: true, url: comment.url };\n } catch (err) {\n if (err instanceof LinearReauthRequiredError) {\n return { error: err.message };\n }\n return { error: `Failed to post Linear comment: ${err instanceof Error ? err.message : String(err)}` };\n }\n },\n });\n}\n\n/**\n * Async `extraTools` provider: expose Linear tools only when the session's\n * project belongs to an org with an active Linear connection.\n */\nexport async function buildLinearAgentTools({\n requestContext,\n linear,\n}: {\n requestContext: RequestContext;\n /** The integration instance providing the Linear API client. */\n linear: LinearIntegration;\n}): Promise<Record<string, ReturnType<typeof createLinearGetIssueTool> | ReturnType<typeof createLinearCommentTool>>> {\n if (!linear.authEnabled) return {};\n\n const ctx = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n const resourceId = ctx?.resourceId;\n if (!resourceId) return {};\n\n const orgId = await linear.resolveOrgId(resourceId);\n if (!orgId) return {};\n const check = await linear.checkConnection(orgId);\n if (!check.connected) return {};\n\n return {\n linear_get_issue: createLinearGetIssueTool(linear, orgId),\n // Only offered when the granted OAuth scope allows posting comments —\n // connections made before `comments:create` was requested are read-only\n // until the org reconnects Linear.\n ...(check.canComment ? { linear_create_comment: createLinearCommentTool(linear, orgId) } : {}),\n };\n}\n","/**\n * Mastra `apiRoutes` for the Linear intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the WorkOS auth gate.\n * Mirrors the GitHub module: every route re-resolves the authenticated user\n * from the request cookie and scopes all rows by the caller's WorkOS org, so an\n * org can only ever see its own Linear connection and issues.\n *\n * When the feature is disabled (`isLinearFeatureEnabled()` false),\n * `buildLinearRoutes` returns only `GET /web/linear/status`, which reports\n * `enabled:false` so the SPA can cleanly hide all Linear UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { StateSigner } from '../../state-signing.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport type { IntegrationHooks } from '../base.js';\nimport type { LinearIntegration } from './integration.js';\nimport { LinearReauthRequiredError } from './integration.js';\n\ntype RouteContext = Context;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): RouteContext {\n return c as RouteContext;\n}\n\n/**\n * Non-secret diagnostic snapshot of every Linear feature gate, mirroring the\n * GitHub diagnostics shape. Only booleans — never values.\n */\nexport interface LinearFeatureDiagnostics {\n linearAppConfigured: boolean;\n factoryAuthEnabled: boolean;\n appDbConfigured: boolean;\n}\n\nexport interface MountLinearRoutesOptions {\n /**\n * The integration instance providing OAuth + GraphQL access. Required for\n * everything beyond the disabled `status` route.\n */\n linear?: LinearIntegration;\n /** Host auth seam. Linear connections are org-owned, so the feature is inert without it. */\n auth: RouteAuth;\n /**\n * Absolute base URL of the web server (e.g. `http://localhost:4111`), used to\n * build the OAuth redirect URI when one isn't explicitly configured.\n */\n baseUrl?: string;\n /** Explicit OAuth callback URI; defaults to `<baseUrl>/auth/linear/callback`. */\n redirectUri?: string;\n /**\n * Shared OAuth `state` signer (created once per boot by the factory).\n * Required for the connect/callback flow; when absent, only the disabled\n * `status` route is served.\n */\n stateSigner?: StateSigner;\n /**\n * Cross-integration intake selection domain. Required for the issues route's\n * project filter; when absent, only the disabled `status` route is served.\n */\n intake?: IntakeStorage;\n hooks?: IntegrationHooks;\n}\n\n/**\n * Resolve the org-scoped tenant for a Linear request. The connection is\n * org-owned, so it requires both a signed-in user and an organization — same\n * tenancy rules as the GitHub routes.\n */\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n {\n error: 'organization_required',\n message: 'Linear intake requires an organization. Personal accounts cannot connect Linear.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque Linear pagination cursor from the query string. Cursors\n * are server-issued (`pageInfo.endCursor`), so anything outside a conservative\n * charset/length is rejected rather than forwarded to Linear.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 512 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map a Linear read failure to the API response for the SPA. */\nfunction linearFetchError(c: RouteContext, err: unknown) {\n if (err instanceof LinearReauthRequiredError || (err as { status?: number }).status === 401) {\n return c.json({ error: 'linear_reauth_required', message: new LinearReauthRequiredError().message }, 409);\n }\n return c.json({ error: 'linear_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the Linear routes as Mastra `apiRoutes`. When the feature is disabled,\n * returns only the `status` route so the SPA can detect the disabled state.\n */\nexport function buildLinearRoutes(options: MountLinearRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { linear, auth, stateSigner, intake } = options;\n const enabled = Boolean(linear) && auth.enabled();\n const diagnostics = (): LinearFeatureDiagnostics => ({\n linearAppConfigured: Boolean(linear),\n factoryAuthEnabled: auth.enabled(),\n appDbConfigured: true,\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !linear || !stateSigner) {\n return c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: diagnostics(),\n });\n }\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized', reason: 'auth_required' }, 401);\n\n if (!tenant.orgId) {\n return c.json({\n enabled: true,\n organizationRequired: true,\n connected: false,\n workspace: null,\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n\n const connection = await linear.loadConnection(tenant.orgId);\n return c.json({\n enabled: true,\n connected: Boolean(connection),\n workspace: connection ? { name: connection.workspaceName, urlKey: connection.workspaceUrlKey } : null,\n reason: connection ? 'ready' : 'not_connected',\n diagnostics: diagnostics(),\n });\n },\n }),\n );\n\n // Without the integration instance or a state signer the connect/callback\n // flow cannot talk to Linear or bind the OAuth round-trip to a tenant —\n // serve only the disabled `status` route (mirrors the feature gate).\n if (!enabled || !linear || !stateSigner || !intake) {\n return routes;\n }\n\n const redirectUri = options.redirectUri ?? `${(options.baseUrl ?? '').replace(/\\/$/, '')}/auth/linear/callback`;\n\n // ── Connect: send the user to Linear's OAuth consent screen ─────────────\n routes.push(\n registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n const state = stateSigner.sign(resolved.tenant.orgId, resolved.tenant.userId);\n return c.redirect(linear.buildAuthorizeUrl(state, redirectUri));\n },\n }),\n );\n\n // ── Callback: exchange the code, persist the connection for the org ─────\n routes.push(\n registerApiRoute('/auth/linear/callback', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n const { orgId, userId } = resolved.tenant;\n\n // CSRF / cross-tenant linking protection: the signed state must belong\n // to the same logged-in user *and* their current org.\n const stateTenant = stateSigner.verify(c.req.query('state'));\n if (!stateTenant || stateTenant.userId !== userId || stateTenant.orgId !== orgId) {\n console.warn('[Linear] OAuth callback rejected: state/tenant mismatch.');\n return c.redirect('/?linear=error');\n }\n\n const code = c.req.query('code');\n if (!code) {\n // User denied consent (or Linear returned an error).\n return c.redirect('/?linear=error');\n }\n\n try {\n const tokens = await linear.exchangeOAuthCode(code, redirectUri);\n const workspace = await linear.fetchWorkspace(tokens.accessToken);\n await linear.upsertConnection({\n orgId,\n userId,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n workspaceName: workspace.name,\n workspaceUrlKey: workspace.urlKey,\n });\n } catch (error) {\n console.warn(`[Linear] OAuth callback failed to persist connection for org ${orgId}.`, error);\n return c.redirect('/?linear=error');\n }\n\n return c.redirect('/?linear=connected');\n },\n }),\n );\n\n // ── List the workspace's projects (Settings intake-source picker) ───────\n routes.push(\n registerApiRoute('/web/linear/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const connection = await linear.loadConnection(resolved.tenant.orgId);\n if (!connection) {\n return c.json({ error: 'linear_not_connected', message: 'Connect Linear to list Linear projects.' }, 409);\n }\n\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const projects = await linear.listProjects(accessToken);\n return c.json({ projects });\n } catch (err) {\n return linearFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── List the workspace's active issues (cursor-paged) ───────────────────\n // Respects the caller's intake config: disabled Linear intake 404s the\n // source, and an explicit project selection narrows the issue filter.\n routes.push(\n registerApiRoute('/web/linear/issues', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const after = parseAfterCursor(c.req.query('after'));\n if (after === null) return c.json({ error: 'invalid_cursor' }, 400);\n const factoryProjectId = c.req.query('factoryProjectId');\n if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n\n const connection = await linear.loadConnection(resolved.tenant.orgId);\n if (!connection) {\n return c.json({ error: 'linear_not_connected', message: 'Connect Linear to see intake issues.' }, 409);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n integrationIds: ['linear'],\n });\n const selection = config.linear!;\n if (!selection.enabled) {\n return c.json({ error: 'linear_intake_disabled', message: 'Linear intake is turned off in Settings.' }, 404);\n }\n\n // No projects selected means nothing is synced — don't fan out to Linear.\n const projectIds = selection.sourceIds ?? [];\n if (projectIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const accessToken = await linear.getFreshAccessToken(connection);\n const { issues, nextCursor } = await linear.intake.listIssues({\n connection: { type: 'oauth', accessToken },\n sourceIds: projectIds,\n cursor: after,\n });\n const issuePayload = issues.map(issue => ({\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee,\n team: issue.source,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n }));\n if (factoryProjectId && options.hooks?.ingestLinearIssues) {\n await options.hooks.ingestLinearIssues({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n factoryProjectId,\n issues: issuePayload,\n });\n }\n return c.json({ issues: issuePayload, nextCursor });\n } catch (err) {\n return linearFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n","/**\n * `LinearIntegration` — the self-contained Linear integration.\n *\n * Implements the system-wide `FactoryIntegration` contract\n * (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env\n * vars ONCE, constructs an instance with explicit credentials, and passes it\n * to `MastraFactory`. Everything Linear-flavored the system does — the OAuth\n * connect/callback flow, workspace/project/issue reads for Intake, and the\n * agent's issue tools — flows through this instance. No other module reads\n * `LINEAR_*` env vars.\n *\n * The class owns:\n * - OAuth: the user-facing authorize URL, code exchange, and refresh-token\n * rotation against Linear's `/oauth/token` endpoint.\n * - GraphQL reads/writes: viewer workspace, projects, active issues for\n * Intake, full issue detail (description + discussion), issue comments.\n * - The HTTP surface (`routes()`) and per-request agent tools\n * (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with\n * `this` as the API client.\n */\n\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\n\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n ListIntakeIssuesInput,\n UpdateIntakeIssueInput,\n} from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';\nimport { buildLinearAgentTools } from './agent-tools.js';\nimport { buildLinearRoutes } from './routes.js';\nimport type { LinearConnectionRow, LinearStorageHandle, UpsertLinearConnectionInput } from './storage.js';\n\nconst LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';\nconst LINEAR_TOKEN_URL = 'https://api.linear.app/oauth/token';\nconst LINEAR_AUTHORIZE_URL = 'https://linear.app/oauth/authorize';\n\n/** Credentials for the Linear OAuth application. All fields are required. */\nexport interface LinearIntegrationConfig {\n /** OAuth client id of the Linear application. */\n clientId: string;\n /** OAuth client secret of the Linear application. */\n clientSecret: string;\n}\n\n/**\n * Tokens minted by Linear's `/oauth/token` endpoint. Linear access tokens\n * expire (24h) and refresh tokens rotate: every refresh invalidates the old\n * pair, so callers must persist the whole set after each exchange.\n */\nexport interface LinearTokenSet {\n accessToken: string;\n /** Null when Linear issued no refresh token (legacy non-expiring apps). */\n refreshToken: string | null;\n /** Null when Linear reported no `expires_in`. */\n expiresAt: Date | null;\n /** Scopes granted to the token as reported by Linear; null when omitted. */\n scope: string | null;\n}\n\nexport interface LinearWorkspace {\n name: string;\n urlKey: string;\n}\n\nexport interface LinearIssue {\n id: string;\n projectId: string;\n /** Human key like `ENG-123`. */\n identifier: string;\n title: string;\n url: string;\n /** Workflow state name, e.g. `In Progress`. */\n state: string;\n /** Workflow state type, e.g. `backlog` / `unstarted` / `started` / `triage`. */\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n team: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LinearIssuePage {\n issues: LinearIssue[];\n /** Opaque cursor for the next page, or `null` on the last page. */\n nextCursor: string | null;\n}\n\nexport interface LinearProjectTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n}\n\nexport interface LinearProject {\n id: string;\n name: string;\n /** Project state, e.g. `planned` / `started` / `paused` / `completed`. */\n state: string;\n /** Teams the project belongs to (the Settings picker groups by these). */\n teams: LinearProjectTeam[];\n}\n\nexport interface LinearIssueComment {\n author: string | null;\n body: string;\n createdAt: string;\n}\n\n/** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */\nexport interface LinearIssueDetail extends LinearIssue {\n /** Markdown body of the issue, or `null` when empty. */\n description: string | null;\n /** Discussion comments, oldest first. */\n comments: LinearIssueComment[];\n}\n\n/** The comment created by {@link LinearIntegration.createIssueComment}. */\nexport interface LinearCreatedComment {\n id: string;\n url: string;\n}\n\nconst LINEAR_ISSUES_PAGE_SIZE = 30;\nconst ISSUE_COMMENTS_PAGE_SIZE = 50;\n/** Hard stop for comment pagination so a misbehaving cursor can't loop forever. */\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Refresh this many ms before the recorded expiry to absorb clock skew. */\nconst TOKEN_REFRESH_SKEW_MS = 60_000;\n\n/** Re-check an org's Linear connection (and its scopes) at most this often. */\nconst CONNECTION_TTL_MS = 60_000;\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Thrown when the org's Linear authorization can no longer be renewed. */\nexport class LinearReauthRequiredError extends Error {\n constructor() {\n super('Linear authorization expired. Reconnect Linear to keep syncing intake issues.');\n }\n}\n\n/** Cached result of {@link LinearIntegration.checkConnection}. */\nexport interface LinearConnectionCheck {\n connected: boolean;\n /** Whether the granted OAuth scope allows posting issue comments. */\n canComment: boolean;\n checkedAt: number;\n}\n\ninterface IssuesQueryData {\n issues: {\n nodes: Array<{\n id: string;\n identifier: string;\n title: string;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string };\n assignee: { name: string } | null;\n team: { key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\ninterface IssueCommentNode {\n body: string;\n createdAt: string;\n user: { name: string } | null;\n}\n\ninterface IssueCommentsPage {\n nodes: IssueCommentNode[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n}\n\ninterface IssueDetailQueryData {\n issue: {\n id: string;\n identifier: string;\n title: string;\n description: string | null;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string };\n assignee: { name: string } | null;\n team: { key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n comments: IssueCommentsPage;\n } | null;\n}\n\ninterface IssueCommentsQueryData {\n issue: { comments: IssueCommentsPage } | null;\n}\n\ninterface IssueIdQueryData {\n issue: { id: string } | null;\n}\n\ninterface CommentCreateMutationData {\n commentCreate: { success: boolean; comment: { id: string; url: string } | null };\n}\n\n/** POST a GraphQL query to Linear with the given OAuth access token. */\nasync function linearGraphql<T>(accessToken: string, query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(LINEAR_GRAPHQL_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n // Linear returns GraphQL errors (validation, missing scopes, …) with a\n // 400 status — surface the actual message instead of just the code.\n let detail: string | null = null;\n try {\n const errBody = (await res.json()) as { errors?: Array<{ message?: string }> };\n detail = errBody.errors?.[0]?.message ?? null;\n } catch {\n // Non-JSON error body; fall back to the status code alone.\n }\n const err = new Error(`Linear API request failed (${res.status})${detail ? `: ${detail}` : ''}`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as { data?: T; errors?: Array<{ message?: string }> };\n if (body.errors?.length) {\n throw new Error(`Linear API error: ${body.errors[0]?.message ?? 'unknown error'}`);\n }\n if (!body.data) {\n throw new Error('Linear API returned no data.');\n }\n return body.data;\n}\n\nexport class LinearIntegration implements FactoryIntegration {\n /** Stable integration identifier (see `../base.ts`). */\n readonly id = 'linear';\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #storage: LinearStorageHandle | undefined;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n\n /** Bind Linear's slice of the generic integration storage, the projects domain, and the host auth seam. */\n initialize({\n storage,\n projects,\n auth,\n }: {\n storage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n }): void {\n this.#storage = storage as unknown as LinearStorageHandle;\n this.#projects = projects;\n this.#auth = auth;\n }\n\n get storage(): LinearStorageHandle {\n if (!this.#storage) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#storage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /**\n * Whether the host runs with web auth enabled. Linear connections are\n * org-owned, so every Linear surface is inert without a tenant auth seam.\n */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n // ── Connection + OAuth token lifecycle ───────────────────────────────────\n\n /** Load the org's Linear connection, or `null` when not connected. */\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const connection = await this.storage.connections.get(orgId);\n if (!connection) return null;\n const { data } = connection;\n return {\n id: connection.id,\n orgId: connection.orgId,\n userId: connection.userId,\n accessToken: data.accessToken,\n scope: data.scope ?? null,\n refreshToken: data.refreshToken ?? null,\n expiresAt: data.expiresAtMs === null || data.expiresAtMs === undefined ? null : new Date(data.expiresAtMs),\n workspaceName: data.workspaceName ?? null,\n workspaceUrlKey: data.workspaceUrlKey ?? null,\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n };\n }\n\n /** Insert or replace the org's connection (one per org). */\n async upsertConnection(input: UpsertLinearConnectionInput): Promise<void> {\n await this.storage.connections.upsert(input.orgId, {\n userId: input.userId,\n data: {\n accessToken: input.accessToken,\n refreshToken: input.refreshToken,\n expiresAtMs: input.expiresAt?.getTime() ?? null,\n scope: input.scope,\n workspaceName: input.workspaceName,\n workspaceUrlKey: input.workspaceUrlKey,\n },\n });\n // Let the agent tools see the new connection immediately.\n this.invalidateConnectionCache(input.orgId);\n }\n\n /** Persist a rotated token set on the org's existing connection row. */\n async #updateTokens(orgId: string, tokens: LinearTokenSet): Promise<void> {\n await this.storage.connections.update(orgId, data => ({\n ...data,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAtMs: tokens.expiresAt?.getTime() ?? null,\n // Refresh responses may omit scope; keep the recorded grant.\n scope: tokens.scope ?? data.scope,\n }));\n }\n\n /**\n * Whether the connection's token can post issue comments. Legacy rows\n * without a recorded scope were minted with `read` only, so they count as\n * read-only until the org reconnects Linear.\n */\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n /**\n * In-flight refreshes keyed by org. Linear rotates refresh tokens, so two\n * concurrent refreshes with the same token would invalidate each other —\n * single-flight ensures one exchange per org and shares the result.\n */\n readonly #inflightRefreshes = new Map<string, Promise<string>>();\n\n /**\n * Return a usable access token for the connection, proactively refreshing\n * it when the recorded expiry is past (or imminent). Throws\n * `LinearReauthRequiredError` when the token is expired and cannot be\n * refreshed — the org has to go through the OAuth flow again.\n */\n async getFreshAccessToken(connection: LinearConnectionRow): Promise<string> {\n const expired =\n connection.expiresAt !== null && connection.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!expired) return connection.accessToken;\n\n if (!connection.refreshToken) {\n // Legacy row from before refresh-token support: nothing to renew with.\n throw new LinearReauthRequiredError();\n }\n\n const existing = this.#inflightRefreshes.get(connection.orgId);\n if (existing) return existing;\n\n // The caller may hold a stale row: another request could have refreshed\n // and rotated the refresh token since this row was loaded. Reload before\n // refreshing so we don't burn the rotated token and force a false reauth.\n const latest = await this.loadConnection(connection.orgId);\n if (!latest) throw new LinearReauthRequiredError();\n\n const concurrent = this.#inflightRefreshes.get(connection.orgId);\n if (concurrent) return concurrent;\n\n const latestExpired = latest.expiresAt !== null && latest.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!latestExpired) return latest.accessToken;\n if (!latest.refreshToken) throw new LinearReauthRequiredError();\n\n const refreshToken = latest.refreshToken;\n const refresh = (async () => {\n try {\n const tokens = await this.refreshAccessToken(refreshToken);\n await this.#updateTokens(connection.orgId, tokens);\n return tokens.accessToken;\n } catch (err) {\n const status = (err as { status?: number }).status;\n // invalid_grant surfaces as 400/401: the refresh token was revoked or\n // already rotated away. Terminal for this connection.\n if (status === 400 || status === 401) throw new LinearReauthRequiredError();\n throw err;\n } finally {\n this.#inflightRefreshes.delete(connection.orgId);\n }\n })();\n this.#inflightRefreshes.set(connection.orgId, refresh);\n return refresh;\n }\n\n // ── Connection checks for agent tools ────────────────────────────────────\n\n /** Re-check an org's Linear connection (and its scopes) at most this often. */\n readonly #connectionChecks = new Map<string, LinearConnectionCheck>();\n\n /**\n * Whether the org has an active connection and whether its granted scope\n * allows posting comments. Cached per org with a short TTL — tool-set\n * resolution runs on every request.\n */\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const cached = this.#connectionChecks.get(orgId);\n if (cached && Date.now() - cached.checkedAt < CONNECTION_TTL_MS) return cached;\n const connection = await this.loadConnection(orgId);\n const check: LinearConnectionCheck = {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n this.#connectionChecks.set(orgId, check);\n return check;\n }\n\n /**\n * Drop the cached connection check for an org. Called after a connection is\n * persisted so the tools show up on the very next run instead of after the\n * TTL lapses.\n */\n invalidateConnectionCache(orgId: string): void {\n this.#connectionChecks.delete(orgId);\n }\n\n /**\n * A project's org never changes, so the resourceId → orgId mapping is\n * cached forever. `null` marks resource ids that aren't factory projects\n * (e.g. local default resources) so we don't re-query them on every\n * tool-set resolution.\n */\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n /** Map a session's resourceId to its owning org, or `null` when it isn't a project. */\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n // Non-UUID resource ids (local/dev resources) would make the uuid column\n // comparison throw — they're definitively \"not a project\", so cache that.\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org/connection caches between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#connectionChecks.clear();\n }\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async ({ orgId }) => {\n const connection = await this.loadConnection(orgId);\n if (!connection) return [];\n const accessToken = await this.getFreshAccessToken(connection);\n const projects = await this.listProjects(accessToken);\n return projects.map(project => ({\n id: project.id,\n name: project.name,\n type: 'project',\n }));\n },\n listItems: async ({ orgId, sourceIds, cursor }) => {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const connection = await this.loadConnection(orgId);\n if (!connection) return { items: [], nextCursor: null };\n const accessToken = await this.getFreshAccessToken(connection);\n const page = await this.listActiveIssues(accessToken, cursor, sourceIds);\n return {\n items: page.issues.map(issue => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: issue.projectId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state,\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n team: issue.team,\n },\n })),\n nextCursor: page.nextCursor,\n };\n },\n listIssues: input => this.#listIntakeIssues(input),\n getIssue: input => this.#getIntakeIssue(input),\n createComment: input => this.#createIntakeComment(input),\n updateIssue: input => this.#updateIntakeIssue(input),\n };\n\n /**\n * Background-dispatch context: the org's OAuth connection with a fresh\n * token. Linear work items store the issue UUID directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n const accessToken = await this.getFreshAccessToken(connection);\n return { connection: { type: 'oauth', accessToken }, issueId: externalSource.externalId };\n }\n /**\n * The OAuth connect/callback flow round-trips a signed `state` through\n * Linear, so a multi-replica deploy needs a deployment-stable state secret.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #clientId: string;\n readonly #clientSecret: string;\n\n constructor(config: LinearIntegrationConfig) {\n const missing = (['clientId', 'clientSecret'] as const).filter(key => !config[key]);\n if (missing.length > 0) {\n throw new Error(`LinearIntegration is missing required config: ${missing.join(', ')}.`);\n }\n this.#clientId = config.clientId;\n this.#clientSecret = config.clientSecret;\n }\n\n // ── OAuth ────────────────────────────────────────────────────────────────\n\n /**\n * Build the OAuth authorize URL. `prompt=consent` forces the workspace\n * picker even for an already-authorized user, so \"reconnect\" can switch\n * workspaces.\n */\n buildAuthorizeUrl(state: string, redirectUri: string): string {\n const url = new URL(LINEAR_AUTHORIZE_URL);\n url.searchParams.set('client_id', this.#clientId);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('response_type', 'code');\n // `comments:create` lets the agent's linear_create_comment tool post\n // comments; everything else the integration does is read-only.\n url.searchParams.set('scope', 'read,comments:create');\n url.searchParams.set('state', state);\n url.searchParams.set('prompt', 'consent');\n return url.toString();\n }\n\n /** Exchange an OAuth `code` for a workspace-scoped token set. */\n async exchangeOAuthCode(code: string, redirectUri: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'authorization_code', code, redirect_uri: redirectUri }, 'token exchange');\n }\n\n /**\n * Exchange a refresh token for a new token set. Linear rotates refresh\n * tokens, so the returned set replaces the stored one entirely. A 400/401\n * here means the refresh token is invalid/revoked and the org must\n * re-authorize.\n */\n async refreshAccessToken(refreshToken: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'refresh_token', refresh_token: refreshToken }, 'token refresh');\n }\n\n /** POST to Linear's token endpoint and normalize the response. */\n async #requestTokens(params: Record<string, string>, label: string): Promise<LinearTokenSet> {\n const res = await fetch(LINEAR_TOKEN_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(10_000),\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n ...params,\n client_id: this.#clientId,\n client_secret: this.#clientSecret,\n }),\n });\n if (!res.ok) {\n const err = new Error(`Linear ${label} failed (${res.status})`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n if (!body.access_token) {\n throw new Error(`Linear ${label} returned no access token.`);\n }\n return {\n accessToken: body.access_token,\n refreshToken: body.refresh_token ?? null,\n expiresAt: typeof body.expires_in === 'number' ? new Date(Date.now() + body.expires_in * 1000) : null,\n scope: body.scope ?? null,\n };\n }\n\n // ── GraphQL reads/writes ─────────────────────────────────────────────────\n\n /** Fetch the workspace (organization) the access token is scoped to. */\n async fetchWorkspace(accessToken: string): Promise<LinearWorkspace> {\n const data = await linearGraphql<{ organization: { name: string; urlKey: string } }>(\n accessToken,\n `query { organization { name urlKey } }`,\n );\n return { name: data.organization.name, urlKey: data.organization.urlKey };\n }\n\n async #listIntakeIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const accessToken = getLinearAccessToken(input.connection);\n const result = await this.listActiveIssues(accessToken, input.cursor, input.sourceIds, input.labels);\n return {\n issues: result.issues.map(issue => linearIssueToIntakeIssue(issue)),\n nextCursor: result.nextCursor,\n };\n }\n\n async #getIntakeIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n return {\n ...linearIssueToIntakeIssue(issue),\n description: issue.description,\n commentCount: issue.comments.length,\n comments: issue.comments,\n };\n }\n\n async #createIntakeComment(input: CreateIntakeCommentInput): Promise<{ id: string; url: string } | null> {\n const accessToken = getLinearAccessToken(input.connection);\n return this.createIssueComment(accessToken, input.issueId, input.body);\n }\n\n async #updateIntakeIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n const teamKey = issue.team;\n if (!teamKey) return null;\n const states = await this.#listTeamWorkflowStates(accessToken, teamKey);\n let targetState: { id: string; name: string; type: string } | null = null;\n if (input.state.kind === 'byType') {\n const wantedType = input.state.stateType;\n targetState = states.find(state => state.type === wantedType) ?? null;\n } else {\n const wanted = input.state.name.toLowerCase();\n targetState = states.find(state => state.name.toLowerCase() === wanted) ?? null;\n }\n if (!targetState) return null;\n if (targetState.name === issue.state) {\n return linearIssueToIntakeIssue(issue);\n }\n const data = await linearGraphql<{ issueUpdate: { success: boolean } }>(\n accessToken,\n `mutation UpdateIssueState($id: String!, $stateId: String!) {\n issueUpdate(id: $id, input: { stateId: $stateId }) { success }\n }`,\n { id: issue.id, stateId: targetState.id },\n );\n if (!data.issueUpdate.success) {\n throw new Error('Linear did not accept the issue update.');\n }\n const fresh = await this.fetchIssueDetail(accessToken, issue.id);\n if (!fresh) return null;\n return linearIssueToIntakeIssue(fresh);\n }\n\n async #listTeamWorkflowStates(\n accessToken: string,\n teamKey: string,\n ): Promise<Array<{ id: string; name: string; type: string }>> {\n const data = await linearGraphql<{\n team: { states: { nodes: Array<{ id: string; name: string; type: string }> } } | null;\n }>(\n accessToken,\n `query TeamStates($key: String!) {\n team(id: $key) { states(first: 100) { nodes { id name type } } }\n }`,\n { key: teamKey },\n );\n return data.team?.states.nodes ?? [];\n }\n\n /** List the workspace's projects (for the Settings intake-source picker). */\n async listProjects(accessToken: string): Promise<LinearProject[]> {\n const data = await linearGraphql<{\n projects: {\n nodes: Array<{\n id: string;\n name: string;\n state: string;\n teams: { nodes: Array<{ id: string; key: string; name: string }> };\n }>;\n };\n }>(\n accessToken,\n `query { projects(first: 100) { nodes { id name state teams(first: 10) { nodes { id key name } } } } }`,\n );\n return data.projects.nodes.map(node => ({\n id: node.id,\n name: node.name,\n state: node.state,\n teams: node.teams.nodes.map(team => ({ id: team.id, key: team.key, name: team.name })),\n }));\n }\n\n /**\n * List one page of the workspace's active issues (triage/backlog/unstarted/\n * started — completed and canceled are excluded), most recently updated\n * first. When `projectIds` is provided, only issues from those projects are\n * returned.\n */\n async listActiveIssues(\n accessToken: string,\n after?: string,\n projectIds?: string[],\n labels?: string[],\n ): Promise<LinearIssuePage> {\n const normalizedLabels = [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n const projectFilter = projectIds?.length ? ', project: { id: { in: $projectIds } }' : '';\n const projectVar = projectIds?.length ? ', $projectIds: [ID!]' : '';\n const labelFilter = normalizedLabels.length > 0 ? ', labels: { name: { in: $labels } }' : '';\n const labelVar = normalizedLabels.length > 0 ? ', $labels: [String!]' : '';\n const data = await linearGraphql<IssuesQueryData>(\n accessToken,\n `query Intake($first: Int!, $after: String${projectVar}${labelVar}) {\n issues(\n first: $first\n after: $after\n orderBy: updatedAt\n filter: { state: { type: { in: [\"triage\", \"backlog\", \"unstarted\", \"started\"] } }${projectFilter}${labelFilter} }\n ) {\n nodes {\n id\n identifier\n title\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n team { key }\n labels { nodes { name } }\n }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n {\n first: LINEAR_ISSUES_PAGE_SIZE,\n after: after ?? null,\n ...(projectIds?.length ? { projectIds } : {}),\n ...(normalizedLabels.length > 0 ? { labels: normalizedLabels } : {}),\n },\n );\n const { nodes, pageInfo } = data.issues;\n return {\n issues: nodes.map(node => ({\n id: node.id,\n projectId: node.project.id,\n identifier: node.identifier,\n title: node.title,\n url: node.url,\n state: node.state.name,\n stateType: node.state.type,\n priorityLabel: node.priorityLabel,\n assignee: node.assignee?.name ?? null,\n team: node.team?.key ?? null,\n labels: node.labels.nodes.map(label => label.name),\n createdAt: node.createdAt,\n updatedAt: node.updatedAt,\n })),\n nextCursor: pageInfo.hasNextPage ? pageInfo.endCursor : null,\n };\n }\n\n /** Follow `comments.pageInfo` until exhausted so long discussions aren't truncated. */\n async #fetchRemainingIssueComments(\n accessToken: string,\n issueId: string,\n firstPage: IssueCommentsPage,\n ): Promise<IssueCommentNode[]> {\n const nodes = [...firstPage.nodes];\n let { hasNextPage, endCursor } = firstPage.pageInfo;\n for (let page = 1; hasNextPage && endCursor && page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const data = await linearGraphql<IssueCommentsQueryData>(\n accessToken,\n `query IssueComments($id: String!, $first: Int!, $after: String!) {\n issue(id: $id) {\n comments(first: $first, after: $after) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: issueId, first: ISSUE_COMMENTS_PAGE_SIZE, after: endCursor },\n );\n const comments = data.issue?.comments;\n if (!comments) break;\n nodes.push(...comments.nodes);\n ({ hasNextPage, endCursor } = comments.pageInfo);\n }\n return nodes;\n }\n\n /**\n * Fetch one issue with its description and comments. `idOrIdentifier`\n * accepts both the Linear UUID and the human key (`ENG-123`). Returns\n * `null` when the issue doesn't exist (Linear reports it as an \"Entity not\n * found\" error).\n */\n async fetchIssueDetail(accessToken: string, idOrIdentifier: string): Promise<LinearIssueDetail | null> {\n let data: IssueDetailQueryData;\n try {\n data = await linearGraphql<IssueDetailQueryData>(\n accessToken,\n `query IssueDetail($id: String!, $commentsFirst: Int!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n team { key }\n labels { nodes { name } }\n comments(first: $commentsFirst) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: idOrIdentifier, commentsFirst: ISSUE_COMMENTS_PAGE_SIZE },\n );\n } catch (err) {\n // Linear surfaces unknown ids/identifiers as a GraphQL \"Entity not\n // found\" error rather than a null node — map that to \"issue doesn't\n // exist\".\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const issue = data.issue;\n if (!issue) return null;\n const allComments = await this.#fetchRemainingIssueComments(accessToken, issue.id, issue.comments);\n const comments = allComments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return {\n id: issue.id,\n projectId: issue.project.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description?.trim() ? issue.description : null,\n url: issue.url,\n state: issue.state.name,\n stateType: issue.state.type,\n priorityLabel: issue.priorityLabel,\n assignee: issue.assignee?.name ?? null,\n team: issue.team?.key ?? null,\n labels: issue.labels.nodes.map(label => label.name),\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n comments: comments.map(comment => ({\n author: comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n }\n\n /**\n * Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID\n * and the human key (`ENG-123`) — the identifier is resolved to a UUID\n * first because `commentCreate` only accepts UUIDs. Returns `null` when the\n * issue doesn't exist.\n */\n async createIssueComment(\n accessToken: string,\n idOrIdentifier: string,\n body: string,\n ): Promise<LinearCreatedComment | null> {\n let issueId: string;\n try {\n const data = await linearGraphql<IssueIdQueryData>(\n accessToken,\n `query IssueId($id: String!) { issue(id: $id) { id } }`,\n { id: idOrIdentifier },\n );\n if (!data.issue) return null;\n issueId = data.issue.id;\n } catch (err) {\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const data = await linearGraphql<CommentCreateMutationData>(\n accessToken,\n `mutation CommentCreate($input: CommentCreateInput!) {\n commentCreate(input: $input) { success comment { id url } }\n }`,\n { input: { issueId, body } },\n );\n if (!data.commentCreate.success || !data.commentCreate.comment) {\n throw new Error('Linear did not accept the comment.');\n }\n return data.commentCreate.comment;\n }\n\n // ── FactoryIntegration surface ───────────────────────────────────────────\n\n /**\n * The integration's HTTP surface: `/web/linear/*` + `/auth/linear/*` Mastra\n * `apiRoutes` (status, OAuth connect/callback, projects + issues for\n * Intake). Handlers operate on this instance.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildLinearRoutes({\n linear: this,\n auth: ctx.auth,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n hooks: ctx.hooks,\n });\n }\n\n /**\n * Org-scoped agent tools: issue detail + comment tools for sessions whose\n * project belongs to an org with an active Linear connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext: args.requestContext, linear: this });\n }\n\n /** Non-secret config snapshot for system diagnostics/startup logs. */\n diagnostics(): Record<string, unknown> {\n return {\n oauthAppConfigured: true,\n };\n }\n}\n\nfunction getLinearAccessToken(connection: IntegrationConnection): string {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n return connection.accessToken;\n}\n\nfunction linearIssueToIntakeIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: null,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n assignee: issue.assignee,\n source: issue.team,\n labels: issue.labels,\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n","export interface PlatformApiClientConfig {\n baseUrl: string;\n accessToken: string;\n fetchImpl?: typeof fetch;\n}\n\nexport function platformApiClientConfigFromEnv(): PlatformApiClientConfig {\n const sharedApiUrl = process.env.MASTRA_SHARED_API_URL?.trim() || 'https://platform.mastra.ai/v1';\n const accessToken = process.env.MASTRA_PLATFORM_SECRET_KEY?.trim();\n if (!accessToken) {\n throw new Error('Platform integration: missing required environment variable MASTRA_PLATFORM_SECRET_KEY.');\n }\n return { baseUrl: normalizeSharedApiUrl(sharedApiUrl), accessToken };\n}\n\nfunction normalizeSharedApiUrl(sharedApiUrl: string): string {\n return sharedApiUrl.replace(/\\/+$/, '').replace(/\\/v1$/, '');\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly retryAfterSeconds: number | null;\n\n constructor(message: string, status: number, retryAfterSeconds: number | null = null) {\n super(message);\n this.name = 'PlatformApiError';\n this.status = status;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nexport class PlatformApiClient {\n readonly #baseUrl: string;\n readonly #accessToken: string;\n readonly #fetch: typeof fetch;\n\n constructor(config: PlatformApiClientConfig) {\n const missing = ['baseUrl', 'accessToken'].filter(field => !config[field as keyof PlatformApiClientConfig]);\n if (missing.length > 0) {\n throw new Error(`Platform integration: missing required config field(s): ${missing.join(', ')}.`);\n }\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.#accessToken = config.accessToken;\n this.#fetch = config.fetchImpl ?? globalThis.fetch;\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n ): Promise<T> {\n const response = await this.#send(method, path, body, options);\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n async requestRedirect(method: string, path: string, options?: { signal?: AbortSignal }): Promise<string> {\n const response = await this.#send(method, path, undefined, options, 'manual');\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (location) return location;\n }\n if (!response.ok) {\n const message = redact(await extractError(response), this.#accessToken);\n const retryAfterSeconds = parseRetryAfter(response.headers.get('retry-after'));\n logPlatformError('Platform API redirect request failed', {\n method,\n path,\n status: response.status,\n retryAfterSeconds,\n message,\n });\n throw new PlatformApiError(message, response.status, retryAfterSeconds);\n }\n logPlatformError('Platform API request did not return a redirect', { method, path, status: response.status });\n throw new PlatformApiError('Platform API request did not return a redirect.', response.status);\n }\n\n async #send(\n method: string,\n path: string,\n body?: unknown,\n options?: { signal?: AbortSignal; actingUserId?: string },\n redirect?: RequestInit['redirect'],\n ): Promise<Response> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n authorization: `Bearer ${this.#accessToken}`,\n };\n // Acting end-user for platform GitHub writes: the platform resolves this\n // user's GitHub OAuth connection (org-scoped) so issues/PRs are authored\n // by the human instead of the App bot. Ignored by older platforms.\n if (options?.actingUserId) {\n headers['x-acting-user-id'] = options.actingUserId;\n }\n const timeoutSignal = AbortSignal.timeout(15_000);\n const init: RequestInit = {\n method,\n headers,\n redirect,\n signal: options?.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal,\n };\n if (body !== undefined) {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n try {\n return await this.#fetch(`${this.#baseUrl}${path}`, init);\n } catch (error) {\n if (error instanceof Error && error.message.includes(this.#accessToken)) {\n const redacted = new Error(redact(error.message, this.#accessToken));\n redacted.name = error.name;\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: redacted.name,\n message: redacted.message,\n });\n throw redacted;\n }\n logPlatformError('Platform API transport error', {\n method,\n path,\n name: error instanceof Error ? error.name : undefined,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n\nasync function extractError(response: Response): Promise<string> {\n try {\n const data = (await response.clone().json()) as Record<string, unknown>;\n for (const field of ['detail', 'error', 'title']) {\n if (typeof data[field] === 'string' && data[field]) return data[field];\n }\n } catch {\n // Fall through to the status-based message.\n }\n return `Platform API request failed (${response.status})`;\n}\n\nfunction redact(message: string, accessToken: string): string {\n return message.split(accessToken).join('[REDACTED]');\n}\n\nexport function logPlatformInfo(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('info', message, fields);\n}\n\nexport function logPlatformWarn(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('warn', message, fields);\n}\n\nexport function logPlatformError(message: string, fields?: Record<string, unknown>): void {\n writePlatformLog('error', message, fields);\n}\n\nfunction writePlatformLog(level: 'info' | 'warn' | 'error', message: string, fields?: Record<string, unknown>): void {\n const metadata = fields ? ` ${JSON.stringify(stripUndefined(fields))}` : '';\n process.stderr.write(`[Mastra Factory] ${level.toUpperCase()} ${message}${metadata}\\n`);\n}\n\nfunction stripUndefined(fields: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));\n}\n\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const seconds = Number.parseInt(value, 10);\n return Number.isSafeInteger(seconds) && seconds >= 0 ? seconds : null;\n}\n"],"mappings":"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","names":["registerApiRoute","loose","registerApiRoute"]}
@@ -1,4 +1,7 @@
1
1
  import type { ApiRoute } from '@mastra/core/server';
2
+ import type { SandboxFleet } from '../sandbox/fleet.js';
3
+ import type { SourceControlSession } from '../storage/domains/source-control/base.js';
4
+ import type { RouteAuth } from './route.js';
2
5
  /**
3
6
  * Server-side directory browser for the web project picker.
4
7
  *
@@ -74,6 +77,18 @@ export declare function listDirectory(root: string, requestedPath?: string): Pro
74
77
  export declare function listWorkspaceRenderedPath(root: string, workspacePath: string, renderedRoot: string): Promise<WorkspaceRenderedListing>;
75
78
  export declare function readWorkspaceFile(root: string, workspacePath: string, path: string): Promise<WorkspaceFile>;
76
79
  export declare function listArtifacts(root: string, workspacePath: string): Promise<ArtifactListing>;
80
+ /** Dependencies for resolving a `workspacePath` that is a Factory session id. */
81
+ export interface SessionFsDeps {
82
+ auth: RouteAuth;
83
+ fleet: SandboxFleet;
84
+ sessions: {
85
+ getBySessionId(sessionId: string): Promise<SourceControlSession | null>;
86
+ };
87
+ }
88
+ /** List an approved rendered root inside a Factory session's sandbox workdir. */
89
+ export declare function listSessionRenderedPath(fleet: SandboxFleet, session: SourceControlSession, renderedRoot: string): Promise<WorkspaceRenderedListing>;
90
+ /** Read a file under an approved rendered root inside a session's sandbox. */
91
+ export declare function readSessionWorkspaceFile(fleet: SandboxFleet, session: SourceControlSession, path: string): Promise<WorkspaceFile>;
77
92
  export interface ResolvedCodebase {
78
93
  /**
79
94
  * The resourceId the TUI would use for this path — derived identically so a
@@ -101,5 +116,6 @@ export declare function resolveCodebase(projectPath: string): ResolvedCodebase;
101
116
  */
102
117
  export declare function buildFsRoutes(options?: {
103
118
  root?: string;
119
+ sessionFs?: SessionFsDeps;
104
120
  }): ApiRoute[];
105
121
  //# sourceMappingURL=fs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"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"}