@mastra/factory 0.1.0-alpha.6 → 0.1.0-alpha.7
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.
- package/CHANGELOG.md +7 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +140 -8
- package/dist/factory.js.map +1 -1
- package/dist/index.js +140 -8
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/integration.js +108 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/pat.d.ts +38 -0
- package/dist/integrations/github/pat.d.ts.map +1 -0
- package/dist/integrations/github/pat.js +48 -0
- package/dist/integrations/github/pat.js.map +1 -0
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +94 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +30 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.d.ts +6 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -1
- package/dist/integrations/github/token-refresh.js +10 -0
- package/dist/integrations/github/token-refresh.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +110 -2
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/surface.js +7 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +7 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/workspace.d.ts +5 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +64 -5
- package/dist/workspace.js.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AASnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AASnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAoH1D,wBAAsB,oCAAoC,CACxD,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,mBAAmB,GAAG,eAAe,EAC7C,MAAM,EAAE,iBAAiB,+BAY1B;AAED,wBAAsB,wCAAwC,CAC5D,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,iBAAiB,mBAM1B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjH;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;;EAqCtG;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgB1D;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sBAcA"}
|
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
import { createTool } from "@mastra/core/tools";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
+
// src/integrations/github/pat.ts
|
|
6
|
+
var PAT_SETTINGS_USER_ID = "__github_org_settings__";
|
|
7
|
+
function asToken(value) {
|
|
8
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
9
|
+
}
|
|
10
|
+
async function getGithubPat(getStorage, orgId, kind = "default") {
|
|
11
|
+
try {
|
|
12
|
+
const settings = await getStorage().settings.get(orgId, PAT_SETTINGS_USER_ID);
|
|
13
|
+
if (!settings) return null;
|
|
14
|
+
if (kind === "reviewer") return asToken(settings.reviewerPat) ?? asToken(settings.pat);
|
|
15
|
+
return asToken(settings.pat);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
// src/integrations/github/subscriptions.ts
|
|
6
22
|
function changeRequestTargetKey(input) {
|
|
7
23
|
return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;
|
|
@@ -43,6 +59,11 @@ async function unsubscribeFromPullRequest(input, storage) {
|
|
|
43
59
|
|
|
44
60
|
// src/integrations/github/token-refresh.ts
|
|
45
61
|
var GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = "factoryGithubTokenInjector";
|
|
62
|
+
var GITHUB_PAT_KIND_CONTEXT_KEY = "factoryGithubPatKind";
|
|
63
|
+
function getRegisteredGithubPatKind(requestContext) {
|
|
64
|
+
const kind = requestContext.get(GITHUB_PAT_KIND_CONTEXT_KEY);
|
|
65
|
+
return kind === "reviewer" ? "reviewer" : "default";
|
|
66
|
+
}
|
|
46
67
|
function injectGithubToken(requestContext, token) {
|
|
47
68
|
const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY);
|
|
48
69
|
if (!injector) {
|
|
@@ -140,6 +161,15 @@ async function unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequ
|
|
|
140
161
|
}
|
|
141
162
|
async function refreshGithubToken(requestContext, github) {
|
|
142
163
|
const target = await resolveSessionTarget(requestContext, github);
|
|
164
|
+
const pat = await getGithubPat(
|
|
165
|
+
() => github.integrationStorage,
|
|
166
|
+
target.orgId,
|
|
167
|
+
getRegisteredGithubPatKind(requestContext)
|
|
168
|
+
);
|
|
169
|
+
if (pat) {
|
|
170
|
+
injectGithubToken(requestContext, pat);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
143
173
|
const access = await github.versionControl.getRepositoryAccess({
|
|
144
174
|
orgId: target.orgId,
|
|
145
175
|
repositoryId: target.repository.id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/integrations/github/session-subscriptions.ts","../../../src/integrations/github/subscriptions.ts","../../../src/integrations/github/token-refresh.ts"],"sourcesContent":["import 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';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * Minimal shape of the host-authenticated user placed on the request context\n * under the `user` key. Mirrors the host's auth user without importing it:\n * `workosId` (stable external id) wins over the row `id`, and `organizationId`\n * scopes org tenancy.\n */\ninterface SessionAuthUser {\n workosId?: string;\n id?: string;\n organizationId?: string;\n}\n\nfunction sessionUserId(user: SessionAuthUser | undefined): string | undefined {\n return user?.workosId ?? user?.id;\n}\n\nfunction sessionOrgId(user: SessionAuthUser | undefined): string | undefined {\n return user?.organizationId;\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n return Boolean(\n context?.threadId && context.getState().projectRepositoryId && sessionOrgId(user) && sessionUserId(user),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n const orgId = sessionOrgId(user);\n const userId = sessionUserId(user);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n if (!context?.getState().projectRepositoryId || !sessionOrgId(user) || !sessionUserId(user)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nexport function stripHeredocBodies(command: string): string {\n const lines = command.split('\\n');\n const executableLines: string[] = [];\n let delimiter: string | undefined;\n\n for (const line of lines) {\n if (delimiter) {\n if (line.trim() === delimiter) delimiter = undefined;\n continue;\n }\n executableLines.push(line);\n const heredoc = line.match(/<<-?\\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\\1/);\n delimiter = heredoc?.[2];\n }\n\n return executableLines.join('\\n');\n}\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.toolName !== 'execute_command' || context.error) return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (\n typeof command !== 'string' ||\n !/(?:^|\\n|;|&&|\\|\\|)\\s*gh\\s+pr\\s+create(?:\\s|$)/.test(stripHeredocBodies(command))\n ) {\n return undefined;\n }\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n","import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n return rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n","import type { RequestContext } from '@mastra/core/request-context';\n\nconst GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = 'factoryGithubTokenInjector';\n\ntype GithubTokenInjector = (token: string) => void;\n\nexport function registerGithubTokenInjector(requestContext: RequestContext, injector: GithubTokenInjector): void {\n requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);\n}\n\nexport function injectGithubToken(requestContext: RequestContext, token: string): void {\n const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY) as GithubTokenInjector | undefined;\n if (!injector) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n injector(token);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/integrations/github/session-subscriptions.ts","../../../src/integrations/github/pat.ts","../../../src/integrations/github/subscriptions.ts","../../../src/integrations/github/token-refresh.ts"],"sourcesContent":["import 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';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { getGithubPat } from './pat.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { getRegisteredGithubPatKind, injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * Minimal shape of the host-authenticated user placed on the request context\n * under the `user` key. Mirrors the host's auth user without importing it:\n * `workosId` (stable external id) wins over the row `id`, and `organizationId`\n * scopes org tenancy.\n */\ninterface SessionAuthUser {\n workosId?: string;\n id?: string;\n organizationId?: string;\n}\n\nfunction sessionUserId(user: SessionAuthUser | undefined): string | undefined {\n return user?.workosId ?? user?.id;\n}\n\nfunction sessionOrgId(user: SessionAuthUser | undefined): string | undefined {\n return user?.organizationId;\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n return Boolean(\n context?.threadId && context.getState().projectRepositoryId && sessionOrgId(user) && sessionUserId(user),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n const orgId = sessionOrgId(user);\n const userId = sessionUserId(user);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n // installation token (which 403s on integration-restricted endpoints). The\n // workspace records which PAT kind the sandbox was provisioned with, so a\n // review-board sandbox keeps its reviewer token on refresh.\n const pat = await getGithubPat(\n () => github.integrationStorage,\n target.orgId,\n getRegisteredGithubPatKind(requestContext),\n );\n if (pat) {\n injectGithubToken(requestContext, pat);\n return;\n }\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const user = requestContext.get('user') as SessionAuthUser | undefined;\n if (!context?.getState().projectRepositoryId || !sessionOrgId(user) || !sessionUserId(user)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nexport function stripHeredocBodies(command: string): string {\n const lines = command.split('\\n');\n const executableLines: string[] = [];\n let delimiter: string | undefined;\n\n for (const line of lines) {\n if (delimiter) {\n if (line.trim() === delimiter) delimiter = undefined;\n continue;\n }\n executableLines.push(line);\n const heredoc = line.match(/<<-?\\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\\1/);\n delimiter = heredoc?.[2];\n }\n\n return executableLines.join('\\n');\n}\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.toolName !== 'execute_command' || context.error) return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (\n typeof command !== 'string' ||\n !/(?:^|\\n|;|&&|\\|\\|)\\s*gh\\s+pr\\s+create(?:\\s|$)/.test(stripHeredocBodies(command))\n ) {\n return undefined;\n }\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n","/**\n * Org-scoped GitHub Personal Access Token settings.\n *\n * GitHub App installation tokens are the wrong credential for the `gh` CLI —\n * they hit \"Resource not accessible by integration\" on endpoints the CLI\n * needs regardless of the minted permission set. When an org pastes a PAT in\n * Settings, the sandbox `GH_TOKEN` injection sites and the\n * `github_refresh_token` tool use it instead of a minted installation token.\n * Tokens must be classic PATs whose account has access to the linked repos.\n *\n * Two kinds:\n * - `default` — the worker token every sandbox gets.\n * - `reviewer` — optional second token for review-board sessions, so PR\n * reviews come from a different account than the author. When it isn't\n * configured, review sessions fall back to the worker token.\n *\n * Both live in the generic `integration_settings` collection for the\n * `github` integration under a sentinel user id (the settings are org-wide,\n * but the schema keys settings per `(org, user)`). Tokens are never returned\n * to the browser — the routes only report whether each is configured.\n */\n\nimport type { GithubSubscriptionStorage } from './subscriptions.js';\n\n/** Sentinel `user_id` for the org-wide settings row. */\nconst PAT_SETTINGS_USER_ID = '__github_org_settings__';\n\nexport type GithubPatKind = 'default' | 'reviewer';\n\ntype GithubOrgSettings = { pat?: string; reviewerPat?: string };\n\nconst FIELD_FOR_KIND: Record<GithubPatKind, keyof GithubOrgSettings> = {\n default: 'pat',\n reviewer: 'reviewerPat',\n};\n\nfunction asToken(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\n/** The PAT to install for `kind`, or null. `reviewer` falls back to the\n * worker token so review sessions still authenticate when no dedicated\n * reviewer token is configured. Fail-soft: storage errors (e.g. integration\n * storage not initialized in a partial test harness) read as \"no PAT\n * configured\" so token minting still works. */\nexport async function getGithubPat(\n getStorage: () => GithubSubscriptionStorage,\n orgId: string,\n kind: GithubPatKind = 'default',\n): Promise<string | null> {\n try {\n const settings = (await getStorage().settings.get(orgId, PAT_SETTINGS_USER_ID)) as GithubOrgSettings | null;\n if (!settings) return null;\n if (kind === 'reviewer') return asToken(settings.reviewerPat) ?? asToken(settings.pat);\n return asToken(settings.pat);\n } catch {\n return null;\n }\n}\n\n/** Which tokens are configured, without fallback semantics — feeds the\n * settings UI status badges. */\nexport async function getGithubPatStatus(\n getStorage: () => GithubSubscriptionStorage,\n orgId: string,\n): Promise<{ configured: boolean; reviewerConfigured: boolean }> {\n try {\n const settings = (await getStorage().settings.get(orgId, PAT_SETTINGS_USER_ID)) as GithubOrgSettings | null;\n return {\n configured: asToken(settings?.pat) !== null,\n reviewerConfigured: asToken(settings?.reviewerPat) !== null,\n };\n } catch {\n return { configured: false, reviewerConfigured: false };\n }\n}\n\nexport async function setGithubPat(\n storage: GithubSubscriptionStorage,\n orgId: string,\n pat: string,\n kind: GithubPatKind = 'default',\n): Promise<void> {\n const existing = ((await storage.settings.get(orgId, PAT_SETTINGS_USER_ID)) ?? {}) as GithubOrgSettings;\n await storage.settings.save(orgId, PAT_SETTINGS_USER_ID, { ...existing, [FIELD_FOR_KIND[kind]]: pat });\n}\n\nexport async function clearGithubPat(\n storage: GithubSubscriptionStorage,\n orgId: string,\n kind: GithubPatKind = 'default',\n): Promise<void> {\n const existing = (await storage.settings.get(orgId, PAT_SETTINGS_USER_ID)) as GithubOrgSettings | null;\n const field = FIELD_FOR_KIND[kind];\n if (!existing?.[field]) return;\n const { [field]: _removed, ...rest } = existing;\n await storage.settings.save(orgId, PAT_SETTINGS_USER_ID, rest);\n}\n","import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n return rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n","import type { RequestContext } from '@mastra/core/request-context';\n\nimport type { GithubPatKind } from './pat.js';\n\nconst GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = 'factoryGithubTokenInjector';\nconst GITHUB_PAT_KIND_CONTEXT_KEY = 'factoryGithubPatKind';\n\ntype GithubTokenInjector = (token: string) => void;\n\nexport function registerGithubTokenInjector(requestContext: RequestContext, injector: GithubTokenInjector): void {\n requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);\n}\n\n/** Record which PAT kind the active sandbox was provisioned with, so token\n * refresh re-injects the same credential (review-board sandboxes keep the\n * reviewer token instead of being clobbered with the worker token). */\nexport function registerGithubPatKind(requestContext: RequestContext, kind: GithubPatKind): void {\n requestContext.set(GITHUB_PAT_KIND_CONTEXT_KEY, kind);\n}\n\nexport function getRegisteredGithubPatKind(requestContext: RequestContext): GithubPatKind {\n const kind = requestContext.get(GITHUB_PAT_KIND_CONTEXT_KEY);\n return kind === 'reviewer' ? 'reviewer' : 'default';\n}\n\nexport function injectGithubToken(requestContext: RequestContext, token: string): void {\n const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY) as GithubTokenInjector | undefined;\n if (!injector) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n injector(token);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
2
|
+
import type { GithubPatKind } from './pat.js';
|
|
2
3
|
type GithubTokenInjector = (token: string) => void;
|
|
3
4
|
export declare function registerGithubTokenInjector(requestContext: RequestContext, injector: GithubTokenInjector): void;
|
|
5
|
+
/** Record which PAT kind the active sandbox was provisioned with, so token
|
|
6
|
+
* refresh re-injects the same credential (review-board sandboxes keep the
|
|
7
|
+
* reviewer token instead of being clobbered with the worker token). */
|
|
8
|
+
export declare function registerGithubPatKind(requestContext: RequestContext, kind: GithubPatKind): void;
|
|
9
|
+
export declare function getRegisteredGithubPatKind(requestContext: RequestContext): GithubPatKind;
|
|
4
10
|
export declare function injectGithubToken(requestContext: RequestContext, token: string): void;
|
|
5
11
|
export {};
|
|
6
12
|
//# sourceMappingURL=token-refresh.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-refresh.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/token-refresh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"token-refresh.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/token-refresh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C,KAAK,mBAAmB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAEnD,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAE/G;AAED;;uEAEuE;AACvE,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAE/F;AAED,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,cAAc,GAAG,aAAa,CAGxF;AAED,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMrF"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
// src/integrations/github/token-refresh.ts
|
|
2
2
|
var GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = "factoryGithubTokenInjector";
|
|
3
|
+
var GITHUB_PAT_KIND_CONTEXT_KEY = "factoryGithubPatKind";
|
|
3
4
|
function registerGithubTokenInjector(requestContext, injector) {
|
|
4
5
|
requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);
|
|
5
6
|
}
|
|
7
|
+
function registerGithubPatKind(requestContext, kind) {
|
|
8
|
+
requestContext.set(GITHUB_PAT_KIND_CONTEXT_KEY, kind);
|
|
9
|
+
}
|
|
10
|
+
function getRegisteredGithubPatKind(requestContext) {
|
|
11
|
+
const kind = requestContext.get(GITHUB_PAT_KIND_CONTEXT_KEY);
|
|
12
|
+
return kind === "reviewer" ? "reviewer" : "default";
|
|
13
|
+
}
|
|
6
14
|
function injectGithubToken(requestContext, token) {
|
|
7
15
|
const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY);
|
|
8
16
|
if (!injector) {
|
|
@@ -11,7 +19,9 @@ function injectGithubToken(requestContext, token) {
|
|
|
11
19
|
injector(token);
|
|
12
20
|
}
|
|
13
21
|
export {
|
|
22
|
+
getRegisteredGithubPatKind,
|
|
14
23
|
injectGithubToken,
|
|
24
|
+
registerGithubPatKind,
|
|
15
25
|
registerGithubTokenInjector
|
|
16
26
|
};
|
|
17
27
|
//# sourceMappingURL=token-refresh.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/integrations/github/token-refresh.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\n\nconst GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = 'factoryGithubTokenInjector';\n\ntype GithubTokenInjector = (token: string) => void;\n\nexport function registerGithubTokenInjector(requestContext: RequestContext, injector: GithubTokenInjector): void {\n requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);\n}\n\nexport function injectGithubToken(requestContext: RequestContext, token: string): void {\n const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY) as GithubTokenInjector | undefined;\n if (!injector) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n injector(token);\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../../src/integrations/github/token-refresh.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\n\nimport type { GithubPatKind } from './pat.js';\n\nconst GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = 'factoryGithubTokenInjector';\nconst GITHUB_PAT_KIND_CONTEXT_KEY = 'factoryGithubPatKind';\n\ntype GithubTokenInjector = (token: string) => void;\n\nexport function registerGithubTokenInjector(requestContext: RequestContext, injector: GithubTokenInjector): void {\n requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);\n}\n\n/** Record which PAT kind the active sandbox was provisioned with, so token\n * refresh re-injects the same credential (review-board sandboxes keep the\n * reviewer token instead of being clobbered with the worker token). */\nexport function registerGithubPatKind(requestContext: RequestContext, kind: GithubPatKind): void {\n requestContext.set(GITHUB_PAT_KIND_CONTEXT_KEY, kind);\n}\n\nexport function getRegisteredGithubPatKind(requestContext: RequestContext): GithubPatKind {\n const kind = requestContext.get(GITHUB_PAT_KIND_CONTEXT_KEY);\n return kind === 'reviewer' ? 'reviewer' : 'default';\n}\n\nexport function injectGithubToken(requestContext: RequestContext, token: string): void {\n const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY) as GithubTokenInjector | undefined;\n if (!injector) {\n throw new Error('GitHub token refresh requires an active Factory sandbox workspace.');\n }\n injector(token);\n}\n"],"mappings":";AAIA,IAAM,oCAAoC;AAC1C,IAAM,8BAA8B;AAI7B,SAAS,4BAA4B,gBAAgC,UAAqC;AAC/G,iBAAe,IAAI,mCAAmC,QAAQ;AAChE;AAKO,SAAS,sBAAsB,gBAAgC,MAA2B;AAC/F,iBAAe,IAAI,6BAA6B,IAAI;AACtD;AAEO,SAAS,2BAA2B,gBAA+C;AACxF,QAAM,OAAO,eAAe,IAAI,2BAA2B;AAC3D,SAAO,SAAS,aAAa,aAAa;AAC5C;AAEO,SAAS,kBAAkB,gBAAgC,OAAqB;AACrF,QAAM,WAAW,eAAe,IAAI,iCAAiC;AACrE,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,oEAAoE;AAAA,EACtF;AACA,WAAS,KAAK;AAChB;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAA4B,MAAM,EAAkC,MAAM,iCAAiC,CAAC;AACxH,OAAO,KAAK,EAuBV,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO9G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAQ/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AA4F9D,qBAAa,yBAA0B,YAAW,kBAAkB;;IAClE,QAAQ,CAAC,EAAE,YAAY;IAQvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAoHrB;IAEF,QAAQ,CAAC,cAAc,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAA4B,MAAM,EAAkC,MAAM,iCAAiC,CAAC;AACxH,OAAO,KAAK,EAuBV,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO9G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAQ/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AA4F9D,qBAAa,yBAA0B,YAAW,kBAAkB;;IAClE,QAAQ,CAAC,EAAE,YAAY;IAQvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAoHrB;IAEF,QAAQ,CAAC,cAAc,EAAE,cAAc,CAuErC;;IAUF,IAAI,OAAO,IAAI,0BAA0B,CAGxC;IAED,IAAI,oBAAoB,IAAI,0BAA0B,CAErD;IAED,IAAI,kBAAkB,IAAI,yBAAyB,CAKlD;IAED,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,wBAAwB,CAAA;KAAE,GAAG,IAAI;IASpE,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IA8K3C,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,yBAAyB,EAAE;IAiB7D,YAAY,CAAC,EAAE,cAAc,EAAE,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,gBAAgB;IAIhF,gBAAgB,CAAC,EACrB,WAAW,EACX,cAAc,GACf,EAAE,UAAU,CAAC,WAAW,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAWhC,mCAAmC,CACvC,eAAe,EAAE,MAAM,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC;IAoB5C,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAcrE,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa9D,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,MAAM,EAAE,CAAC;IASpB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;CAqUzG"}
|
|
@@ -51,6 +51,48 @@ function getGithubFeatureDiagnostics(options) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// src/integrations/github/pat.ts
|
|
55
|
+
var PAT_SETTINGS_USER_ID = "__github_org_settings__";
|
|
56
|
+
var FIELD_FOR_KIND = {
|
|
57
|
+
default: "pat",
|
|
58
|
+
reviewer: "reviewerPat"
|
|
59
|
+
};
|
|
60
|
+
function asToken(value) {
|
|
61
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
62
|
+
}
|
|
63
|
+
async function getGithubPat(getStorage, orgId, kind = "default") {
|
|
64
|
+
try {
|
|
65
|
+
const settings = await getStorage().settings.get(orgId, PAT_SETTINGS_USER_ID);
|
|
66
|
+
if (!settings) return null;
|
|
67
|
+
if (kind === "reviewer") return asToken(settings.reviewerPat) ?? asToken(settings.pat);
|
|
68
|
+
return asToken(settings.pat);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function getGithubPatStatus(getStorage, orgId) {
|
|
74
|
+
try {
|
|
75
|
+
const settings = await getStorage().settings.get(orgId, PAT_SETTINGS_USER_ID);
|
|
76
|
+
return {
|
|
77
|
+
configured: asToken(settings?.pat) !== null,
|
|
78
|
+
reviewerConfigured: asToken(settings?.reviewerPat) !== null
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return { configured: false, reviewerConfigured: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function setGithubPat(storage, orgId, pat, kind = "default") {
|
|
85
|
+
const existing = await storage.settings.get(orgId, PAT_SETTINGS_USER_ID) ?? {};
|
|
86
|
+
await storage.settings.save(orgId, PAT_SETTINGS_USER_ID, { ...existing, [FIELD_FOR_KIND[kind]]: pat });
|
|
87
|
+
}
|
|
88
|
+
async function clearGithubPat(storage, orgId, kind = "default") {
|
|
89
|
+
const existing = await storage.settings.get(orgId, PAT_SETTINGS_USER_ID);
|
|
90
|
+
const field = FIELD_FOR_KIND[kind];
|
|
91
|
+
if (!existing?.[field]) return;
|
|
92
|
+
const { [field]: _removed, ...rest } = existing;
|
|
93
|
+
await storage.settings.save(orgId, PAT_SETTINGS_USER_ID, rest);
|
|
94
|
+
}
|
|
95
|
+
|
|
54
96
|
// src/integrations/github/project-lock.ts
|
|
55
97
|
import { createHash } from "crypto";
|
|
56
98
|
var inProcessLocks = /* @__PURE__ */ new Map();
|
|
@@ -1360,6 +1402,56 @@ function buildGithubRoutes(options) {
|
|
|
1360
1402
|
}
|
|
1361
1403
|
})
|
|
1362
1404
|
);
|
|
1405
|
+
const parsePatKind = (value) => {
|
|
1406
|
+
if (value === void 0 || value === null || value === "default") return "default";
|
|
1407
|
+
if (value === "reviewer") return "reviewer";
|
|
1408
|
+
return null;
|
|
1409
|
+
};
|
|
1410
|
+
routes.push(
|
|
1411
|
+
registerApiRoute("/web/github/pat", {
|
|
1412
|
+
method: "GET",
|
|
1413
|
+
requiresAuth: false,
|
|
1414
|
+
handler: async (c) => {
|
|
1415
|
+
const resolved = await resolveOrgTenant(loose(c), auth);
|
|
1416
|
+
if ("response" in resolved) return resolved.response;
|
|
1417
|
+
return c.json(await getGithubPatStatus(() => github.integrationStorage, resolved.tenant.orgId));
|
|
1418
|
+
}
|
|
1419
|
+
}),
|
|
1420
|
+
registerApiRoute("/web/github/pat", {
|
|
1421
|
+
method: "POST",
|
|
1422
|
+
requiresAuth: false,
|
|
1423
|
+
handler: async (c) => {
|
|
1424
|
+
const resolved = await resolveOrgTenant(loose(c), auth);
|
|
1425
|
+
if ("response" in resolved) return resolved.response;
|
|
1426
|
+
let body;
|
|
1427
|
+
try {
|
|
1428
|
+
body = await c.req.json();
|
|
1429
|
+
} catch {
|
|
1430
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
1431
|
+
}
|
|
1432
|
+
const kind = parsePatKind(body.kind);
|
|
1433
|
+
if (!kind) return c.json({ error: "kind must be 'default' or 'reviewer'" }, 400);
|
|
1434
|
+
const token = typeof body.token === "string" ? body.token.trim() : "";
|
|
1435
|
+
if (!token) return c.json({ error: "A token is required" }, 400);
|
|
1436
|
+
if (token.length > 500) return c.json({ error: "Token too long (max 500 characters)" }, 400);
|
|
1437
|
+
if (/\s/.test(token)) return c.json({ error: "Token must not contain whitespace" }, 400);
|
|
1438
|
+
await setGithubPat(github.integrationStorage, resolved.tenant.orgId, token, kind);
|
|
1439
|
+
return c.json(await getGithubPatStatus(() => github.integrationStorage, resolved.tenant.orgId));
|
|
1440
|
+
}
|
|
1441
|
+
}),
|
|
1442
|
+
registerApiRoute("/web/github/pat", {
|
|
1443
|
+
method: "DELETE",
|
|
1444
|
+
requiresAuth: false,
|
|
1445
|
+
handler: async (c) => {
|
|
1446
|
+
const resolved = await resolveOrgTenant(loose(c), auth);
|
|
1447
|
+
if ("response" in resolved) return resolved.response;
|
|
1448
|
+
const kind = parsePatKind(c.req.query("kind"));
|
|
1449
|
+
if (!kind) return c.json({ error: "kind must be 'default' or 'reviewer'" }, 400);
|
|
1450
|
+
await clearGithubPat(github.integrationStorage, resolved.tenant.orgId, kind);
|
|
1451
|
+
return c.json(await getGithubPatStatus(() => github.integrationStorage, resolved.tenant.orgId));
|
|
1452
|
+
}
|
|
1453
|
+
})
|
|
1454
|
+
);
|
|
1363
1455
|
routes.push(...buildProjectGitRoutes({ github, auth, fleet, storage, emitAudit }));
|
|
1364
1456
|
return routes;
|
|
1365
1457
|
}
|
|
@@ -1402,11 +1494,12 @@ async function prepareProject(options) {
|
|
|
1402
1494
|
if (!access.authorization) {
|
|
1403
1495
|
throw new MaterializeError("Repository access did not include a bearer token.", "clone-failed");
|
|
1404
1496
|
}
|
|
1497
|
+
const ghCliToken = await getGithubPat(() => github.integrationStorage, project.installation.orgId) ?? access.authorization.token;
|
|
1405
1498
|
const sandbox = await ensureProjectSandbox({
|
|
1406
1499
|
fleet,
|
|
1407
1500
|
row: sandboxRow,
|
|
1408
1501
|
storage: github.sourceControlStorage.sandboxes,
|
|
1409
|
-
token:
|
|
1502
|
+
token: ghCliToken,
|
|
1410
1503
|
onProgress
|
|
1411
1504
|
});
|
|
1412
1505
|
const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
|
|
@@ -1844,6 +1937,11 @@ import { z } from "zod";
|
|
|
1844
1937
|
|
|
1845
1938
|
// src/integrations/github/token-refresh.ts
|
|
1846
1939
|
var GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = "factoryGithubTokenInjector";
|
|
1940
|
+
var GITHUB_PAT_KIND_CONTEXT_KEY = "factoryGithubPatKind";
|
|
1941
|
+
function getRegisteredGithubPatKind(requestContext) {
|
|
1942
|
+
const kind = requestContext.get(GITHUB_PAT_KIND_CONTEXT_KEY);
|
|
1943
|
+
return kind === "reviewer" ? "reviewer" : "default";
|
|
1944
|
+
}
|
|
1847
1945
|
function injectGithubToken(requestContext, token) {
|
|
1848
1946
|
const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY);
|
|
1849
1947
|
if (!injector) {
|
|
@@ -1941,6 +2039,15 @@ async function unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequ
|
|
|
1941
2039
|
}
|
|
1942
2040
|
async function refreshGithubToken(requestContext, github) {
|
|
1943
2041
|
const target = await resolveSessionTarget(requestContext, github);
|
|
2042
|
+
const pat = await getGithubPat(
|
|
2043
|
+
() => github.integrationStorage,
|
|
2044
|
+
target.orgId,
|
|
2045
|
+
getRegisteredGithubPatKind(requestContext)
|
|
2046
|
+
);
|
|
2047
|
+
if (pat) {
|
|
2048
|
+
injectGithubToken(requestContext, pat);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
1944
2051
|
const access = await github.versionControl.getRepositoryAccess({
|
|
1945
2052
|
orgId: target.orgId,
|
|
1946
2053
|
repositoryId: target.repository.id
|
|
@@ -2613,6 +2720,7 @@ var PlatformGithubIntegration = class {
|
|
|
2613
2720
|
getRepositoryAccess: async ({ orgId, repositoryId }) => {
|
|
2614
2721
|
const repository = await this.storage.repositories.get({ orgId, id: repositoryId });
|
|
2615
2722
|
if (!repository) throw new Error("Version-control repository not found.");
|
|
2723
|
+
const cloneUrl = `https://github.com/${repository.slug}.git`;
|
|
2616
2724
|
const installation = await this.storage.installations.get({ orgId, id: repository.installationId });
|
|
2617
2725
|
if (!installation) throw new Error("Version-control installation not found.");
|
|
2618
2726
|
const installationId = parsePositiveInteger(installation.externalId);
|
|
@@ -2624,7 +2732,7 @@ var PlatformGithubIntegration = class {
|
|
|
2624
2732
|
{ repositories: [repositoryName], permissions: REPOSITORY_TOKEN_PERMISSIONS }
|
|
2625
2733
|
);
|
|
2626
2734
|
return {
|
|
2627
|
-
cloneUrl
|
|
2735
|
+
cloneUrl,
|
|
2628
2736
|
authorization: { scheme: "bearer", token: token.token }
|
|
2629
2737
|
};
|
|
2630
2738
|
},
|