@mastra/factory 0.16.0-alpha.9 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +19 -8
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +31 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +4 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/capabilities/version-control.d.ts +15 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +7 -7
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +100 -40
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +3 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +21 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.js +2 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +41 -42
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +416 -227
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts +2 -5
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +8 -48
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/agent-tools.d.ts +14 -0
- package/dist/integrations/gitlab/agent-tools.d.ts.map +1 -0
- package/dist/integrations/gitlab/agent-tools.js +42 -0
- package/dist/integrations/gitlab/agent-tools.js.map +1 -0
- package/dist/integrations/gitlab/api.d.ts +222 -0
- package/dist/integrations/gitlab/api.d.ts.map +1 -0
- package/dist/integrations/gitlab/api.js +274 -0
- package/dist/integrations/gitlab/api.js.map +1 -0
- package/dist/integrations/gitlab/default-rules.d.ts +125 -0
- package/dist/integrations/gitlab/default-rules.d.ts.map +1 -0
- package/dist/integrations/gitlab/default-rules.js +172 -0
- package/dist/integrations/gitlab/default-rules.js.map +1 -0
- package/dist/integrations/gitlab/integration.d.ts +138 -0
- package/dist/integrations/gitlab/integration.d.ts.map +1 -0
- package/dist/integrations/gitlab/integration.js +705 -0
- package/dist/integrations/gitlab/integration.js.map +1 -0
- package/dist/integrations/gitlab/issue-reconciler.d.ts +6 -0
- package/dist/integrations/gitlab/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/issue-reconciler.js +123 -0
- package/dist/integrations/gitlab/issue-reconciler.js.map +1 -0
- package/dist/integrations/gitlab/merge-request-reconciler.d.ts +6 -0
- package/dist/integrations/gitlab/merge-request-reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/merge-request-reconciler.js +267 -0
- package/dist/integrations/gitlab/merge-request-reconciler.js.map +1 -0
- package/dist/integrations/gitlab/reconciler.d.ts +5 -0
- package/dist/integrations/gitlab/reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/reconciler.js +31 -0
- package/dist/integrations/gitlab/reconciler.js.map +1 -0
- package/dist/integrations/gitlab/reconciliation-config.d.ts +3 -0
- package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/gitlab/reconciliation-config.js +17 -0
- package/dist/integrations/gitlab/reconciliation-config.js.map +1 -0
- package/dist/integrations/gitlab/routes.d.ts +20 -0
- package/dist/integrations/gitlab/routes.d.ts.map +1 -0
- package/dist/integrations/gitlab/routes.js +473 -0
- package/dist/integrations/gitlab/routes.js.map +1 -0
- package/dist/integrations/gitlab/rules.d.ts +35 -0
- package/dist/integrations/gitlab/rules.d.ts.map +1 -0
- package/dist/integrations/gitlab/rules.js +391 -0
- package/dist/integrations/gitlab/rules.js.map +1 -0
- package/dist/integrations/gitlab/session-subscriptions.d.ts +32 -0
- package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/gitlab/session-subscriptions.js +192 -0
- package/dist/integrations/gitlab/session-subscriptions.js.map +1 -0
- package/dist/integrations/gitlab/subscriptions.d.ts +70 -0
- package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -0
- package/dist/integrations/gitlab/subscriptions.js +76 -0
- package/dist/integrations/gitlab/subscriptions.js.map +1 -0
- package/dist/integrations/gitlab/version-control.d.ts +16 -0
- package/dist/integrations/gitlab/version-control.d.ts.map +1 -0
- package/dist/integrations/gitlab/version-control.js +622 -0
- package/dist/integrations/gitlab/version-control.js.map +1 -0
- package/dist/integrations/gitlab/webhook-dispatch.d.ts +75 -0
- package/dist/integrations/gitlab/webhook-dispatch.d.ts.map +1 -0
- package/dist/integrations/gitlab/webhook-dispatch.js +235 -0
- package/dist/integrations/gitlab/webhook-dispatch.js.map +1 -0
- package/dist/integrations/gitlab/webhook.d.ts +72 -0
- package/dist/integrations/gitlab/webhook.d.ts.map +1 -0
- package/dist/integrations/gitlab/webhook.js +201 -0
- package/dist/integrations/gitlab/webhook.js.map +1 -0
- package/dist/integrations/incidentio/integration.js +1 -1
- package/dist/integrations/issue-reconciler.d.ts +3 -3
- package/dist/integrations/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/issue-reconciler.js +22 -2
- package/dist/integrations/issue-reconciler.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +21 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.d.ts +64 -0
- package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/gitlab/event-worker.js +306 -0
- package/dist/integrations/platform/gitlab/event-worker.js.map +1 -0
- package/dist/integrations/platform/gitlab/integration.d.ts +57 -0
- package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -0
- package/dist/integrations/platform/gitlab/integration.js +125 -0
- package/dist/integrations/platform/gitlab/integration.js.map +1 -0
- package/dist/integrations/platform/incidentio/integration.js +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +3 -3
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +2 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +21 -7
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +37 -0
- package/dist/integrations/subscription-session.d.ts.map +1 -0
- package/dist/integrations/subscription-session.js +83 -0
- package/dist/integrations/subscription-session.js.map +1 -0
- package/dist/packages/_internals/workspace/dist/index.js +1 -11
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +4 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/projects.d.ts +1 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +1 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +20 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -0
- package/dist/routes/source-control-sessions.js +319 -0
- package/dist/routes/source-control-sessions.js.map +1 -0
- package/dist/routes/source-control-settings.d.ts +10 -0
- package/dist/routes/source-control-settings.d.ts.map +1 -0
- package/dist/routes/source-control-settings.js +87 -0
- package/dist/routes/source-control-settings.js.map +1 -0
- package/dist/routes/surface.d.ts +3 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +55 -13
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +8 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +20 -3
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +7 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +3 -2
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +6 -3
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +67 -3
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +23 -2
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +2 -0
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/git-ref.d.ts +7 -0
- package/dist/sandbox/git-ref.d.ts.map +1 -0
- package/dist/sandbox/git-ref.js +15 -0
- package/dist/sandbox/git-ref.js.map +1 -0
- package/dist/sandbox/session-retirement.js +1 -1
- package/dist/sandbox/workdir.d.ts.map +1 -1
- package/dist/sandbox/workdir.js +6 -6
- package/dist/sandbox/workdir.js.map +1 -1
- package/dist/session/factory-session.d.ts +31 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +60 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/source-control-tools.d.ts +16 -0
- package/dist/session/source-control-tools.d.ts.map +1 -0
- package/dist/session/source-control-tools.js +464 -0
- package/dist/session/source-control-tools.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +18 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +77 -0
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/work-item-branch.d.ts +3 -1
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +22 -4
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.d.ts +15 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +57 -23
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +19 -0
- package/factory-skills/factory-gitlab-review/SKILL.md +30 -0
- package/factory-skills/factory-rereview/SKILL.md +13 -6
- package/factory-skills/factory-review/SKILL.md +13 -6
- package/factory-skills/factory-review/references/archaeology.md +225 -0
- package/factory-skills/factory-review/references/categories/README.md +34 -0
- package/factory-skills/factory-review/references/categories/behavior-change.md +36 -0
- package/factory-skills/factory-review/references/categories/bug-fix.md +37 -0
- package/factory-skills/factory-review/references/categories/docs.md +32 -0
- package/factory-skills/factory-review/references/categories/experimental-flagged.md +34 -0
- package/factory-skills/factory-review/references/categories/infra-tooling.md +37 -0
- package/factory-skills/factory-review/references/categories/internal-capability.md +35 -0
- package/factory-skills/factory-review/references/categories/large-cross-cutting.md +35 -0
- package/factory-skills/factory-review/references/categories/mechanical.md +36 -0
- package/factory-skills/factory-review/references/categories/performance.md +33 -0
- package/factory-skills/factory-review/references/categories/public-api.md +38 -0
- package/factory-skills/factory-review/references/categories/refactor.md +35 -0
- package/factory-skills/factory-review/references/categories/revert.md +31 -0
- package/factory-skills/factory-review/references/categories/schema-storage.md +39 -0
- package/factory-skills/factory-review/references/categories/security.md +35 -0
- package/factory-skills/factory-review/references/categories/tests-only.md +38 -0
- package/package.json +9 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-control.js","names":[],"sources":["../../../src/integrations/gitlab/version-control.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n PullRequest,\n PullRequestComment,\n RequestedReviewers,\n Review,\n ReviewComment,\n VersionControl,\n} from '../../capabilities/version-control.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport {\n GITLAB_DISCUSSIONS_PAGE_SIZE,\n GITLAB_MERGE_REQUESTS_PAGE_SIZE,\n GITLAB_NOTES_PAGE_SIZE,\n GitLabApiError,\n} from './api.js';\nimport type {\n GitLabApiClient,\n GitLabDiscussionNote,\n GitLabDiscussionPosition,\n GitLabMember,\n GitLabMergeRequest,\n GitLabNote,\n GitLabUser,\n} from './api.js';\n\nexport interface GitLabVersionControlContext {\n api: GitLabApiClient;\n connection: IntegrationConnection;\n host: string;\n webBaseUrl?: string;\n repositoryAccessToken?: () => Promise<string>;\n}\n\nexport interface GitLabVersionControlDependencies {\n contextForConnection(connection: IntegrationConnection): Promise<GitLabVersionControlContext>;\n contextForStoredInstallation?(connection: IntegrationConnection, host: string | undefined): Promise<GitLabVersionControlContext>;\n}\n\nexport function buildGitLabVersionControl(deps: GitLabVersionControlDependencies): VersionControl {\n let storage: SourceControlStorageHandle | undefined;\n\n const sourceControlStorage = (): SourceControlStorageHandle => {\n if (!storage) throw new Error('GitLab VersionControl is not initialized.');\n return storage;\n };\n\n const listPullRequests: VersionControl['listPullRequests'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const page = parsePositiveCursor(input.cursor);\n const state = input.state === 'open' ? 'opened' : input.state === 'closed' ? 'all' : (input.state ?? 'opened');\n const mergeRequests = await context.api.listMergeRequests(input.sourceId, { page, state });\n return {\n pullRequests: mergeRequests\n .filter(mergeRequest => input.state !== 'closed' || mergeRequest.state === 'closed' || mergeRequest.state === 'merged')\n .filter(mergeRequest => input.includeDrafts !== false || !isDraft(mergeRequest))\n .map(toPullRequest),\n nextCursor: mergeRequests.length === GITLAB_MERGE_REQUESTS_PAGE_SIZE ? String(page + 1) : null,\n };\n };\n\n const getPullRequest: VersionControl['getPullRequest'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n try {\n return toPullRequest(\n await context.api.getMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, 'merge request')),\n );\n } catch (error) {\n if (error instanceof GitLabApiError && error.status === 404) return null;\n throw error;\n }\n };\n\n const createPullRequest: VersionControl['createPullRequest'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const title = input.draft && !/^(?:draft:|\\[draft\\])/i.test(input.title) ? `Draft: ${input.title}` : input.title;\n return toPullRequest(\n await context.api.createMergeRequest(input.sourceId, {\n sourceBranch: input.headBranch,\n targetBranch: input.baseBranch,\n title,\n description: input.body,\n }),\n );\n };\n\n const updatePullRequest: VersionControl['updatePullRequest'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n return toPullRequest(\n await context.api.updateMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, 'merge request'), {\n title: input.title,\n description: input.body === null ? '' : input.body,\n targetBranch: input.baseBranch,\n stateEvent: input.state === 'closed' ? 'close' : input.state === 'open' ? 'reopen' : undefined,\n }),\n );\n };\n\n const closePullRequest: VersionControl['closePullRequest'] = input =>\n updatePullRequest({ ...input, state: 'closed' });\n\n const mergePullRequest: VersionControl['mergePullRequest'] = async input => {\n if (input.method === 'rebase') {\n throw notSupported('GitLab rebases merge requests asynchronously; rebase-and-merge is not supported.');\n }\n const context = await deps.contextForConnection(input.connection);\n const commitMessage = combinedCommitMessage(input.commitTitle, input.commitMessage);\n const result = await context.api.mergeMergeRequest(\n input.sourceId,\n requirePositiveId(input.pullRequestId, 'merge request'),\n {\n squash: input.method === 'squash',\n mergeCommitMessage: input.method === 'squash' ? undefined : commitMessage,\n squashCommitMessage: input.method === 'squash' ? commitMessage : undefined,\n },\n );\n const merged = result.state === 'merged' || Boolean(result.merged_at);\n return {\n merged,\n message: result.message ?? (merged ? 'Merge request merged.' : 'Merge request was not merged.'),\n sha: result.squash_commit_sha ?? result.merge_commit_sha ?? result.sha ?? null,\n };\n };\n\n const listComments: VersionControl['listComments'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const page = parsePositiveCursor(input.cursor);\n const notes = await context.api.listMergeRequestNotes(input.sourceId, mergeRequestIid, { page });\n return {\n comments: notes\n .filter(note => !note.system && note.type !== 'DiffNote')\n .map(note => toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note)),\n nextCursor: notes.length === GITLAB_NOTES_PAGE_SIZE ? String(page + 1) : null,\n };\n };\n\n const createComment: VersionControl['createComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const note = await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, input.body);\n return toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note);\n };\n\n const updateComment: VersionControl['updateComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const { mergeRequestIid, noteId } = parseNoteId(input.commentId);\n const note = await context.api.updateMergeRequestNote(input.sourceId, mergeRequestIid, noteId, input.body);\n return toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note);\n };\n\n const deleteComment: VersionControl['deleteComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const { mergeRequestIid, noteId } = parseNoteId(input.commentId);\n await context.api.deleteMergeRequestNote(input.sourceId, mergeRequestIid, noteId);\n };\n\n const listReviews: VersionControl['listReviews'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const approvals = await context.api.getMergeRequestApprovals(input.sourceId, mergeRequestIid);\n return {\n reviews: (approvals.approved_by ?? []).map(({ user }) =>\n toApprovalReview(webBaseUrl(context), input.sourceId, mergeRequestIid, user),\n ),\n nextCursor: null,\n };\n };\n\n const getReview: VersionControl['getReview'] = async input => {\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const approvalPrefix = `${mergeRequestIid}:approval`;\n const commentPrefix = `${mergeRequestIid}:comment:`;\n if (input.reviewId !== approvalPrefix && !input.reviewId.startsWith(`${approvalPrefix}:`) && !input.reviewId.startsWith(commentPrefix)) {\n return null;\n }\n const context = await deps.contextForConnection(input.connection);\n if (input.reviewId === approvalPrefix || input.reviewId.startsWith(`${approvalPrefix}:`)) {\n const reviewerKey = input.reviewId === approvalPrefix ? null : input.reviewId.slice(approvalPrefix.length + 1);\n if (reviewerKey === '') return null;\n const currentUser = reviewerKey === null ? await context.api.getCurrentUser() : null;\n const approvals = await context.api.getMergeRequestApprovals(input.sourceId, mergeRequestIid);\n const user = (approvals.approved_by ?? []).map(entry => entry.user).find(candidate =>\n reviewerKey === null\n ? (currentUser?.id !== undefined && candidate.id === currentUser.id) ||\n candidate.username === currentUser?.username\n : String(candidate.id ?? candidate.username) === reviewerKey,\n );\n return user ? toApprovalReview(webBaseUrl(context), input.sourceId, mergeRequestIid, user) : null;\n }\n const noteId = parsePositiveInteger(input.reviewId.slice(commentPrefix.length));\n if (noteId === null) return null;\n try {\n const note = await context.api.getMergeRequestNote(input.sourceId, mergeRequestIid, noteId);\n if (note.system || note.type === 'DiffNote') return null;\n return toCommentReview(webBaseUrl(context), input.sourceId, mergeRequestIid, note);\n } catch (error) {\n if (error instanceof GitLabApiError && error.status === 404) return null;\n throw error;\n }\n };\n\n const createReview: VersionControl['createReview'] = async input =>\n submitReviewAction(deps, {\n connection: input.connection,\n sourceId: input.sourceId,\n pullRequestId: input.pullRequestId,\n event: input.event,\n body: input.body,\n commitId: input.commitId,\n });\n\n const updateReview: VersionControl['updateReview'] = async () => {\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab does not expose mutable pending review objects.');\n };\n\n const submitReview: VersionControl['submitReview'] = async input =>\n submitReviewAction(deps, {\n connection: input.connection,\n sourceId: input.sourceId,\n pullRequestId: input.pullRequestId,\n event: input.event,\n body: input.body,\n });\n\n const dismissReview: VersionControl['dismissReview'] = async () => {\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab approval dismissal has no GitHub review equivalent.');\n };\n\n const deletePendingReview: VersionControl['deletePendingReview'] = async () => {\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab does not expose pending review objects.');\n };\n\n const listReviewComments: VersionControl['listReviewComments'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const page = parsePositiveCursor(input.cursor);\n const discussions = await context.api.listMergeRequestDiscussions(input.sourceId, mergeRequestIid, { page });\n return {\n comments: discussions.flatMap(discussion => {\n const position = discussionPosition(discussion.notes);\n if (!position) return [];\n const root = discussion.notes.find(note => note.position) ?? discussion.notes[0];\n const replyToId = root ? packDiscussionNoteId(mergeRequestIid, discussion.id, root.id) : null;\n return discussion.notes\n .filter(note => !note.system)\n .map(note =>\n toReviewComment(\n webBaseUrl(context),\n input.sourceId,\n mergeRequestIid,\n discussion.id,\n note,\n note === root ? null : replyToId,\n position,\n ),\n );\n }),\n nextCursor: discussions.length === GITLAB_DISCUSSIONS_PAGE_SIZE ? String(page + 1) : null,\n };\n };\n\n const createReviewComment: VersionControl['createReviewComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n if (input.replyToId) {\n const thread = parseReviewReplyId(input.replyToId);\n if (thread.mergeRequestIid !== mergeRequestIid) {\n throw new GitLabApiError('GitLab discussion does not belong to the requested merge request.', 400);\n }\n const discussion = await context.api.getMergeRequestDiscussion(\n input.sourceId,\n mergeRequestIid,\n thread.discussionId,\n );\n const position = discussionPosition(discussion.notes);\n if (!position) throw new GitLabApiError('GitLab discussion is not anchored to a diff.', 400);\n const root = discussion.notes.find(note => note.position) ?? discussion.notes[0];\n if (!root) throw new GitLabApiError('GitLab discussion response did not include a note.', 502);\n const note = await context.api.addMergeRequestDiscussionNote(\n input.sourceId,\n mergeRequestIid,\n thread.discussionId,\n input.body,\n );\n return toReviewComment(\n webBaseUrl(context),\n input.sourceId,\n mergeRequestIid,\n thread.discussionId,\n note,\n packDiscussionNoteId(mergeRequestIid, thread.discussionId, root.id),\n position,\n );\n }\n\n const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);\n if (!mergeRequest.diff_refs) {\n throw new GitLabApiError('GitLab merge request diff refs are not ready.', 409);\n }\n if (mergeRequest.diff_refs.head_sha !== input.commitId) {\n throw new GitLabApiError('GitLab merge request changed since this review comment was prepared.', 409);\n }\n const { path, line, side } = input;\n if (typeof path !== 'string' || typeof line !== 'number' || (side !== 'left' && side !== 'right')) {\n throw new GitLabApiError('GitLab diff review comment position is invalid.', 400);\n }\n if ((input.startLine === undefined) !== (input.startSide === undefined)) {\n throw new GitLabApiError('A multi-line GitLab review comment requires both startLine and startSide.', 400);\n }\n const position: GitLabDiscussionPosition = {\n position_type: 'text',\n ...mergeRequest.diff_refs,\n old_path: path,\n new_path: path,\n old_line: side === 'left' ? line : undefined,\n new_line: side === 'right' ? line : undefined,\n ...(input.startLine !== undefined && input.startSide\n ? {\n line_range: {\n start: discussionLine(path, input.startLine, input.startSide),\n end: discussionLine(path, line, side),\n },\n }\n : {}),\n };\n const discussion = await context.api.createMergeRequestDiscussion(input.sourceId, mergeRequestIid, {\n body: input.body,\n commitId: input.commitId,\n position,\n });\n const note = discussion.notes.find(candidate => candidate.position) ?? discussion.notes[0];\n if (!note) throw new GitLabApiError('GitLab discussion response did not include a note.', 502);\n return toReviewComment(webBaseUrl(context), input.sourceId, mergeRequestIid, discussion.id, note, null, position);\n };\n\n const updateReviewComment: VersionControl['updateReviewComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const reference = parseDiscussionNoteId(input.commentId);\n const discussion = await context.api.getMergeRequestDiscussion(\n input.sourceId,\n reference.mergeRequestIid,\n reference.discussionId,\n );\n const position = discussionPosition(discussion.notes);\n if (!position) throw new GitLabApiError('GitLab discussion is not anchored to a diff.', 400);\n const root = discussion.notes.find(candidate => candidate.position) ?? discussion.notes[0];\n if (!root) throw new GitLabApiError('GitLab discussion response did not include a note.', 502);\n const note = await context.api.updateMergeRequestDiscussionNote(\n input.sourceId,\n reference.mergeRequestIid,\n reference.discussionId,\n reference.noteId,\n input.body,\n );\n return toReviewComment(\n webBaseUrl(context),\n input.sourceId,\n reference.mergeRequestIid,\n reference.discussionId,\n note,\n note.id === root.id ? null : packDiscussionNoteId(reference.mergeRequestIid, reference.discussionId, root.id),\n position,\n );\n };\n\n const deleteReviewComment: VersionControl['deleteReviewComment'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const reference = parseDiscussionNoteId(input.commentId);\n await context.api.deleteMergeRequestDiscussionNote(\n input.sourceId,\n reference.mergeRequestIid,\n reference.discussionId,\n reference.noteId,\n );\n };\n\n const resolveReviewThread: NonNullable<VersionControl['resolveReviewThread']> = async input => {\n const context = await deps.contextForConnection(input.connection);\n const reference = parseDiscussionNoteId(input.commentId);\n await context.api.resolveMergeRequestDiscussion(\n input.sourceId,\n reference.mergeRequestIid,\n reference.discussionId,\n input.resolved,\n );\n };\n\n const listRequestedReviewers: VersionControl['listRequestedReviewers'] = async input => {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequest = await context.api.getMergeRequest(\n input.sourceId,\n requirePositiveId(input.pullRequestId, 'merge request'),\n );\n return toRequestedReviewers(mergeRequest);\n };\n\n const requestReviewers: VersionControl['requestReviewers'] = async input => {\n rejectTeamReviewers(input.teams);\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);\n const requestedMembers = await resolveMembers(context.api, input.sourceId, input.users ?? []);\n const currentIds = await reviewerIds(context.api, input.sourceId, mergeRequest.reviewers ?? []);\n const reviewerIdsToSet = [...new Set([...currentIds, ...requestedMembers.map(member => member.id)])];\n return toRequestedReviewers(\n await context.api.setMergeRequestReviewers(input.sourceId, mergeRequestIid, reviewerIdsToSet),\n );\n };\n\n const removeRequestedReviewers: VersionControl['removeRequestedReviewers'] = async input => {\n rejectTeamReviewers(input.teams);\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);\n const removals = new Set((input.users ?? []).map(username => username.toLowerCase()));\n const retained = (mergeRequest.reviewers ?? []).filter(user => !removals.has(user.username.toLowerCase()));\n const retainedIds = await reviewerIds(context.api, input.sourceId, retained);\n return toRequestedReviewers(\n await context.api.setMergeRequestReviewers(input.sourceId, mergeRequestIid, retainedIds),\n );\n };\n\n return {\n initialize: input => {\n storage = input.storage;\n },\n registerInstallation: async ({ orgId, userId, installation }) => {\n const requestedConnection = parseConnection(installation.metadata?.connection);\n if (!requestedConnection) {\n throw new GitLabApiError('GitLab installation metadata must include a valid connection.', 400);\n }\n const context = await deps.contextForConnection(requestedConnection);\n return sourceControlStorage().installations.upsert({\n orgId,\n connectedByUserId: userId,\n externalId: installation.externalId,\n accountName: installation.accountName,\n accountType: installation.accountType,\n providerMetadata: {\n ...installation.metadata,\n connection: context.connection,\n host: normalizeHost(context.host),\n },\n });\n },\n registerRepositories: async ({ orgId, installationId, repositories }) => {\n const normalizedRepositories = repositories.map(repository => ({\n ...repository,\n slug: normalizeSlug(repository.slug),\n }));\n return await Promise.all(\n normalizedRepositories.map(repository =>\n sourceControlStorage().repositories.upsert({\n orgId,\n input: {\n installationId,\n externalId: repository.externalId,\n slug: repository.slug,\n defaultBranch: repository.defaultBranch,\n providerMetadata: repository.metadata,\n },\n }),\n ),\n );\n },\n getRepositoryTarget: async ({ orgId, repositoryId }) => {\n const repository = await sourceControlStorage().repositories.get({ orgId, id: repositoryId });\n if (!repository) throw new Error('Version-control repository not found.');\n const installation = await sourceControlStorage().installations.get({\n orgId,\n id: repository.installationId,\n });\n if (!installation) throw new Error('Version-control installation not found.');\n const connection = parseConnection(installation.providerMetadata.connection);\n if (!connection) throw new GitLabApiError('GitLab installation connection metadata is invalid.', 500);\n const context = await (deps.contextForStoredInstallation ?? deps.contextForConnection)(\n connection,\n typeof installation.providerMetadata.host === 'string' ? installation.providerMetadata.host : undefined,\n );\n // The API uses GitLab's immutable numeric project ID because Platform's\n // proxy normalizes encoded path slashes; retain the slug for browser URLs.\n return { connection: context.connection, sourceId: `${repository.externalId}:${repository.slug}` };\n },\n getRepositoryAccess: async ({ orgId, repositoryId }) => {\n const repository = await sourceControlStorage().repositories.get({ orgId, id: repositoryId });\n if (!repository) throw new Error('Version-control repository not found.');\n const installation = await sourceControlStorage().installations.get({\n orgId,\n id: repository.installationId,\n });\n if (!installation) throw new Error('Version-control installation not found.');\n const connection = parseConnection(installation.providerMetadata.connection);\n if (!connection) throw new GitLabApiError('GitLab installation connection metadata is invalid.', 500);\n const context = await (deps.contextForStoredInstallation ?? deps.contextForConnection)(\n connection,\n typeof installation.providerMetadata.host === 'string' ? installation.providerMetadata.host : undefined,\n );\n if (!context.repositoryAccessToken) {\n throw notSupported('This GitLab connection does not expose credentials for repository cloning.');\n }\n const token = await context.repositoryAccessToken();\n const baseUrl = webBaseUrl(context);\n const slug = normalizeSlug(repository.slug);\n return {\n cloneUrl: `${baseUrl}/${slug}.git`,\n authorization: { scheme: 'bearer', token, username: 'oauth2' },\n };\n },\n listPullRequests,\n getPullRequest,\n createPullRequest,\n updatePullRequest,\n closePullRequest,\n mergePullRequest,\n listComments,\n resolveReviewThread,\n createComment,\n updateComment,\n deleteComment,\n listReviews,\n getReview,\n createReview,\n updateReview,\n submitReview,\n dismissReview,\n deletePendingReview,\n listReviewComments,\n createReviewComment,\n updateReviewComment,\n deleteReviewComment,\n listRequestedReviewers,\n requestReviewers,\n removeRequestedReviewers,\n };\n}\n\nasync function submitReviewAction(\n deps: GitLabVersionControlDependencies,\n input: {\n connection: IntegrationConnection;\n sourceId: string;\n pullRequestId: string;\n event: 'approve' | 'request-changes' | 'comment' | undefined;\n body?: string;\n commitId?: string;\n },\n): Promise<Review> {\n const context = await deps.contextForConnection(input.connection);\n const mergeRequestIid = requirePositiveId(input.pullRequestId, 'merge request');\n if (input.event === 'request-changes') {\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab has no first-class request-changes review event.');\n }\n if (input.event === undefined) {\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab has no first-class pending review object.');\n }\n if (input.event === 'approve') {\n const body = input.body?.trim();\n await context.api.approveMergeRequest(input.sourceId, mergeRequestIid, input.commitId);\n const comment = body\n ? toPullRequestComment(\n webBaseUrl(context),\n input.sourceId,\n mergeRequestIid,\n await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, body),\n )\n : null;\n return {\n id: String(mergeRequestIid) + ':approval',\n url: comment?.url ?? mergeRequestUrl(webBaseUrl(context), input.sourceId, mergeRequestIid),\n author: comment?.author ?? null,\n body: comment?.body ?? null,\n state: 'approved',\n commitId: null,\n submittedAt: comment?.createdAt ?? null,\n };\n }\n const body = input.body?.trim();\n if (!body) throw new GitLabApiError('GitLab comment reviews require a body.', 400);\n const note = await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, body);\n return toCommentReview(webBaseUrl(context), input.sourceId, mergeRequestIid, note);\n}\n\nfunction toCommentReview(host: string, sourceId: string, mergeRequestIid: number, note: GitLabNote): Review {\n const comment = toPullRequestComment(host, sourceId, mergeRequestIid, note);\n return {\n id: `${mergeRequestIid}:comment:${note.id}`,\n url: comment.url,\n author: comment.author,\n body: comment.body,\n state: 'commented',\n commitId: null,\n submittedAt: note.created_at,\n };\n}\n\nfunction toApprovalReview(host: string, sourceId: string, mergeRequestIid: number, user: GitLabUser): Review {\n return {\n id: `${mergeRequestIid}:approval:${user.id ?? user.username}`,\n url: mergeRequestUrl(host, sourceId, mergeRequestIid),\n author: displayName(user),\n body: null,\n state: 'approved',\n commitId: null,\n submittedAt: null,\n };\n}\n\nfunction toReviewComment(\n host: string,\n sourceId: string,\n mergeRequestIid: number,\n discussionId: string,\n note: GitLabDiscussionNote,\n replyToId: string | null,\n fallbackPosition?: GitLabDiscussionPosition,\n): ReviewComment {\n const position = note.position ?? fallbackPosition;\n if (!position) throw new GitLabApiError('GitLab review comment is missing its diff position.', 502);\n const base = toPullRequestComment(host, sourceId, mergeRequestIid, note);\n const side = position.new_line !== null && position.new_line !== undefined ? 'right' : 'left';\n return {\n ...base,\n id: packDiscussionNoteId(mergeRequestIid, discussionId, note.id),\n path: side === 'right' ? position.new_path : position.old_path,\n line: side === 'right' ? (position.new_line ?? null) : (position.old_line ?? null),\n side,\n commitId: position.head_sha,\n replyToId,\n };\n}\n\nfunction mergeRequestUrl(host: string, sourceId: string, mergeRequestIid: number): string {\n return `${host}/${sourcePath(sourceId)}/-/merge_requests/${mergeRequestIid}`;\n}\n\nfunction webBaseUrl(context: GitLabVersionControlContext): string {\n const host = normalizeHost(context.host);\n if (!context.webBaseUrl) return `https://${host}`;\n let url: URL;\n try {\n url = new URL(context.webBaseUrl);\n } catch {\n throw new GitLabApiError('GitLab web base URL is invalid.', 400);\n }\n const loopback = url.hostname === 'localhost' || url.hostname === '[::1]' || /^127(?:\\.\\d{1,3}){3}$/.test(url.hostname);\n if (\n url.host !== host ||\n (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new GitLabApiError('GitLab web base URL is invalid.', 400);\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`;\n}\n\nfunction sourcePath(sourceId: string): string {\n const match = /^\\d+:(.+)$/.exec(sourceId);\n return normalizeSlug(match?.[1] ?? sourceId);\n}\n\nfunction packDiscussionId(mergeRequestIid: number, discussionId: string): string {\n if (!discussionId || discussionId.includes(':')) {\n throw new GitLabApiError('GitLab discussion id is invalid.', 400);\n }\n return `${mergeRequestIid}:${discussionId}`;\n}\n\nfunction packDiscussionNoteId(mergeRequestIid: number, discussionId: string, noteId: number): string {\n return `${packDiscussionId(mergeRequestIid, discussionId)}:${noteId}`;\n}\n\nfunction parseReviewReplyId(value: string): { mergeRequestIid: number; discussionId: string } {\n const parts = value.split(':');\n if (parts.length === 2) return parseDiscussionId(value);\n if (parts.length === 3 && parts[1]) {\n requirePositiveId(parts[2]!, 'discussion note');\n return {\n mergeRequestIid: requirePositiveId(parts[0]!, 'merge request'),\n discussionId: parts[1],\n };\n }\n throw new GitLabApiError('GitLab review reply id is invalid.', 400);\n}\n\nfunction parseDiscussionId(value: string): { mergeRequestIid: number; discussionId: string } {\n const parts = value.split(':');\n if (parts.length !== 2 || !parts[1]) throw new GitLabApiError('GitLab discussion id is invalid.', 400);\n return {\n mergeRequestIid: requirePositiveId(parts[0]!, 'merge request'),\n discussionId: parts[1],\n };\n}\n\nfunction parseDiscussionNoteId(value: string): { mergeRequestIid: number; discussionId: string; noteId: number } {\n const parts = value.split(':');\n if (parts.length !== 3 || !parts[1]) throw new GitLabApiError('GitLab discussion note id is invalid.', 400);\n return {\n mergeRequestIid: requirePositiveId(parts[0]!, 'merge request'),\n discussionId: parts[1],\n noteId: requirePositiveId(parts[2]!, 'discussion note'),\n };\n}\n\nfunction discussionPosition(notes: GitLabDiscussionNote[]): GitLabDiscussionPosition | undefined {\n return notes.find(note => note.position)?.position ?? undefined;\n}\n\nfunction discussionLine(path: string, line: number, side: 'left' | 'right') {\n const pathHash = createHash('sha1').update(path).digest('hex');\n const oldLine = side === 'left' ? line : undefined;\n const newLine = side === 'right' ? line : undefined;\n return {\n line_code: `${pathHash}_${oldLine ?? 0}_${newLine ?? 0}`,\n type: side === 'left' ? ('old' as const) : ('new' as const),\n old_line: oldLine,\n new_line: newLine,\n };\n}\n\nfunction toRequestedReviewers(mergeRequest: GitLabMergeRequest): RequestedReviewers {\n return {\n users: (mergeRequest.reviewers ?? []).map(user => user.username),\n teams: [],\n };\n}\n\nasync function resolveMembers(api: GitLabApiClient, sourceId: string, usernames: string[]): Promise<GitLabMember[]> {\n return Promise.all(usernames.map(username => resolveMember(api, sourceId, username)));\n}\n\nasync function resolveMember(api: GitLabApiClient, sourceId: string, username: string): Promise<GitLabMember> {\n const normalized = username.trim();\n if (!normalized) throw new GitLabApiError('GitLab reviewer username is invalid.', 400);\n const members = await api.listProjectMembers(sourceId, { query: normalized });\n const member = members.find(candidate => candidate.username.toLowerCase() === normalized.toLowerCase());\n if (!member) throw new GitLabApiError(`GitLab reviewer ${normalized} is not a project member.`, 404);\n return member;\n}\n\nasync function reviewerIds(api: GitLabApiClient, sourceId: string, users: GitLabUser[]): Promise<number[]> {\n return Promise.all(\n users.map(async user => user.id ?? (await resolveMember(api, sourceId, user.username)).id),\n );\n}\n\nfunction rejectTeamReviewers(teams: string[] | undefined): void {\n if (!teams?.length) return;\n // FLAGGED FOR MANUAL REVIEW (spec §6.2)\n throw notSupported('GitLab merge requests do not have a direct team-reviewer equivalent.');\n}\n\nfunction notSupported(message: string): GitLabApiError {\n return new GitLabApiError(message, 501);\n}\n\nfunction toPullRequest(mergeRequest: GitLabMergeRequest): PullRequest {\n return {\n id: String(mergeRequest.iid),\n title: mergeRequest.title,\n url: mergeRequest.web_url,\n author: displayName(mergeRequest.author),\n assignees: mergeRequest.assignees?.map(user => user.username),\n requestedReviewers: mergeRequest.reviewers?.map(user => user.username),\n labels: mergeRequest.labels,\n body: mergeRequest.description?.trim() ? mergeRequest.description : null,\n state: mergeRequest.state === 'closed' || mergeRequest.state === 'merged' ? 'closed' : 'open',\n draft: isDraft(mergeRequest),\n merged: mergeRequest.state === 'merged' || Boolean(mergeRequest.merged_at),\n mergeable: mergeableState(mergeRequest.merge_status),\n baseBranch: mergeRequest.target_branch,\n headBranch: mergeRequest.source_branch,\n headSha: mergeRequest.sha,\n createdAt: mergeRequest.created_at,\n updatedAt: mergeRequest.updated_at,\n };\n}\n\nfunction toPullRequestComment(\n host: string,\n sourceId: string,\n mergeRequestIid: number,\n note: GitLabNote,\n): PullRequestComment {\n return {\n id: `${mergeRequestIid}:${note.id}`,\n url: `${mergeRequestUrl(host, sourceId, mergeRequestIid)}#note_${note.id}`,\n author: displayName(note.author),\n body: note.body,\n createdAt: note.created_at,\n updatedAt: note.updated_at ?? note.created_at,\n };\n}\n\nfunction displayName(user: { name?: string | null; username: string } | null | undefined): string | null {\n return user?.username || user?.name?.trim() || null;\n}\n\nfunction isDraft(mergeRequest: GitLabMergeRequest): boolean {\n return mergeRequest.draft ?? mergeRequest.work_in_progress ?? /^(?:draft:|\\[draft\\])/i.test(mergeRequest.title);\n}\n\nfunction mergeableState(status: string | undefined): boolean | null {\n if (status === 'can_be_merged') return true;\n if (status === 'cannot_be_merged') return false;\n return null;\n}\n\nfunction combinedCommitMessage(title: string | undefined, body: string | undefined): string | undefined {\n const parts = [title?.trim(), body?.trim()].filter((part): part is string => Boolean(part));\n return parts.length > 0 ? parts.join('\\n\\n') : undefined;\n}\n\nfunction parseNoteId(value: string): { mergeRequestIid: number; noteId: number } {\n const parts = value.split(':');\n if (parts.length !== 2) throw new GitLabApiError('GitLab merge request note id is invalid.', 400);\n return {\n mergeRequestIid: requirePositiveId(parts[0]!, 'merge request'),\n noteId: requirePositiveId(parts[1]!, 'note'),\n };\n}\n\nfunction parsePositiveCursor(cursor: string | undefined): number {\n if (cursor === undefined) return 1;\n const page = parsePositiveInteger(cursor);\n if (page === null) throw new GitLabApiError('GitLab cursor must be a positive page number.', 400);\n return page;\n}\n\nfunction requirePositiveId(value: string, resource: string): number {\n const parsed = parsePositiveInteger(value);\n if (parsed === null) throw new GitLabApiError(`GitLab ${resource} id must be a positive integer.`, 400);\n return parsed;\n}\n\nfunction parsePositiveInteger(value: string): number | null {\n if (!/^\\d+$/.test(value)) return null;\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;\n}\n\nfunction parseConnection(value: unknown): IntegrationConnection | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const connection = value as Record<string, unknown>;\n if (connection.type === 'oauth' && typeof connection.accessToken === 'string' && connection.accessToken.length > 0) {\n return { type: 'oauth', accessToken: connection.accessToken };\n }\n if (\n connection.type === 'app-installation' &&\n Number.isSafeInteger(connection.installationId) &&\n Number(connection.installationId) > 0\n ) {\n return { type: 'app-installation', installationId: Number(connection.installationId) };\n }\n return null;\n}\n\nfunction normalizeHost(value: string): string {\n const host = value.trim();\n if (!host || host.includes('/') || host.includes('@')) throw new GitLabApiError('GitLab host is invalid.', 400);\n let parsed: URL;\n try {\n parsed = new URL(`https://${host}`);\n } catch {\n throw new GitLabApiError('GitLab host is invalid.', 400);\n }\n if (parsed.host !== host) throw new GitLabApiError('GitLab host is invalid.', 400);\n return host;\n}\n\nfunction normalizeSlug(value: string): string {\n const slug = value.replace(/^\\/+|\\/+$/g, '');\n const segments = slug.split('/');\n if (!slug || segments.some(segment => !/^[A-Za-z0-9_.-]+$/.test(segment) || segment === '.' || segment === '..')) {\n throw new GitLabApiError('GitLab repository slug is invalid.', 400);\n }\n return slug;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { MountedMastraCode } from '@mastra/code-sdk';
|
|
2
|
+
import type { NotificationPriority } from '@mastra/core/notifications';
|
|
3
|
+
import type { SubscriptionSessionLookup } from '../subscription-session.js';
|
|
4
|
+
import type { GitLabSignalSubscriptionRow, GitLabSubscriptionStorage, GitLabWebhookMergeRequestTarget } from './subscriptions.js';
|
|
5
|
+
import type { ParsedGitLabWebhook } from './webhook.js';
|
|
6
|
+
export interface GitLabWebhookNotificationMetadata {
|
|
7
|
+
event: string;
|
|
8
|
+
action: string;
|
|
9
|
+
host: string;
|
|
10
|
+
projectId: number;
|
|
11
|
+
projectPath: string;
|
|
12
|
+
mergeRequestIid: number;
|
|
13
|
+
sender?: string;
|
|
14
|
+
senderId?: number;
|
|
15
|
+
deliveryId: string;
|
|
16
|
+
}
|
|
17
|
+
export interface GitLabWebhookNotification {
|
|
18
|
+
action: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
priority: NotificationPriority;
|
|
21
|
+
summary: string;
|
|
22
|
+
terminal: boolean;
|
|
23
|
+
metadata: GitLabWebhookNotificationMetadata;
|
|
24
|
+
payload: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The integration surface this dispatch uses. Narrow on purpose so the direct
|
|
28
|
+
* and Platform-backed integrations, and tests, can satisfy it.
|
|
29
|
+
*/
|
|
30
|
+
export interface GitLabWebhookDispatchIntegration {
|
|
31
|
+
readonly integrationStorage: GitLabSubscriptionStorage;
|
|
32
|
+
readonly sourceControlStorage?: SubscriptionSessionLookup;
|
|
33
|
+
getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;
|
|
34
|
+
resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
export interface GitLabWebhookDispatchDependencies {
|
|
37
|
+
controller: MountedMastraCode['controller'];
|
|
38
|
+
gitlab?: GitLabWebhookDispatchIntegration;
|
|
39
|
+
listSubscriptions?: (target: GitLabWebhookMergeRequestTarget, options?: {
|
|
40
|
+
includeTerminal?: boolean;
|
|
41
|
+
}) => Promise<GitLabSignalSubscriptionRow[]>;
|
|
42
|
+
retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Author gate override. The default requires the sender to be a trusted
|
|
45
|
+
* project member through the subscription's connection.
|
|
46
|
+
*/
|
|
47
|
+
isAuthorizedSender?: (notification: GitLabWebhookNotification, subscription: GitLabSignalSubscriptionRow) => Promise<boolean>;
|
|
48
|
+
/** Called when the sender gate drops a notification, so the drop is observable. */
|
|
49
|
+
onSenderRejected?: (notification: GitLabWebhookNotification) => void;
|
|
50
|
+
onTargetError?: (subscription: GitLabSignalSubscriptionRow, error: unknown) => void;
|
|
51
|
+
/** Called when a subscription names a thread this deployment does not hold. */
|
|
52
|
+
onTargetSkipped?: (subscription: GitLabSignalSubscriptionRow) => void;
|
|
53
|
+
/**
|
|
54
|
+
* The connection the event arrived through. When set, only subscriptions
|
|
55
|
+
* created under that connection are delivered; a direct project webhook
|
|
56
|
+
* leaves it unset because GitLab does not say which connection it belongs to.
|
|
57
|
+
*/
|
|
58
|
+
sourceConnectionId?: string;
|
|
59
|
+
/** Called when a subscription is skipped because it belongs to another connection. */
|
|
60
|
+
onConnectionMismatch?: (subscription: GitLabSignalSubscriptionRow) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The GitLab instance a delivery came from: the project's `web_url` host, which
|
|
64
|
+
* must agree with the `X-Gitlab-Instance` header when one was sent. A mismatch
|
|
65
|
+
* yields no host, so the delivery matches no subscription.
|
|
66
|
+
*/
|
|
67
|
+
export declare function gitlabWebhookHost(parsed: ParsedGitLabWebhook): string | undefined;
|
|
68
|
+
export declare function classifyGitLabWebhook(parsed: ParsedGitLabWebhook): GitLabWebhookNotification | undefined;
|
|
69
|
+
export declare function dispatchGitLabWebhook(parsed: ParsedGitLabWebhook, dependencies: GitLabWebhookDispatchDependencies): Promise<{
|
|
70
|
+
delivered: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
skipped: number;
|
|
73
|
+
ignored: boolean;
|
|
74
|
+
}>;
|
|
75
|
+
//# sourceMappingURL=webhook-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-dispatch.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/webhook-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAQ5E,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,+BAA+B,EAChC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,iCAAiC;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,iCAAiC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IACvD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,yBAAyB,CAAC;IAC1D,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACpH,8BAA8B,CAAC,CAAC,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5F;AAED,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,CAAC,EAAE,gCAAgC,CAAC;IAC1C,iBAAiB,CAAC,EAAE,CAClB,MAAM,EAAE,+BAA+B,EACvC,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KACpC,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CACnB,YAAY,EAAE,yBAAyB,EACvC,YAAY,EAAE,2BAA2B,KACtC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACrE,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,2BAA2B,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpF,+EAA+E;IAC/E,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,2BAA2B,KAAK,IAAI,CAAC;IACtE;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,CAAC,YAAY,EAAE,2BAA2B,KAAK,IAAI,CAAC;CAC5E;AAcD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAWjF;AAUD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,yBAAyB,GAAG,SAAS,CA2FxG;AAyCD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,mBAAmB,EAC3B,YAAY,EAAE,iCAAiC,GAC9C,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA0GnF"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { resolveSubscriptionSession, subscriptionRunContext } from "../subscription-session.js";
|
|
2
|
+
import { listMergeRequestSubscriptionsForWebhook, mergeRequestUrl, normalizeSubscriptionHost, retireMergeRequestSubscription } from "./subscriptions.js";
|
|
3
|
+
import "./integration.js";
|
|
4
|
+
//#region src/integrations/gitlab/webhook-dispatch.ts
|
|
5
|
+
function getObject(value) {
|
|
6
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7
|
+
}
|
|
8
|
+
function getString(value) {
|
|
9
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10
|
+
}
|
|
11
|
+
function getNumber(value) {
|
|
12
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The GitLab instance a delivery came from: the project's `web_url` host, which
|
|
16
|
+
* must agree with the `X-Gitlab-Instance` header when one was sent. A mismatch
|
|
17
|
+
* yields no host, so the delivery matches no subscription.
|
|
18
|
+
*/
|
|
19
|
+
function gitlabWebhookHost(parsed) {
|
|
20
|
+
const declaredHost = parsed.instanceHost ? normalizeSubscriptionHost(parsed.instanceHost) : void 0;
|
|
21
|
+
const projectUrl = getString(getObject(parsed.payload.project)?.web_url);
|
|
22
|
+
if (!projectUrl) return declaredHost;
|
|
23
|
+
try {
|
|
24
|
+
const payloadHost = normalizeSubscriptionHost(new URL(projectUrl).host);
|
|
25
|
+
if (declaredHost && declaredHost !== payloadHost) return void 0;
|
|
26
|
+
return payloadHost;
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
|
|
32
|
+
"issue-comment-created",
|
|
33
|
+
"review-comment-created",
|
|
34
|
+
"review-approved",
|
|
35
|
+
"review-dismissed"
|
|
36
|
+
]);
|
|
37
|
+
const PERMISSION_CHECK_TIMEOUT_MS = 5e3;
|
|
38
|
+
function classifyGitLabWebhook(parsed) {
|
|
39
|
+
const { event, payload } = parsed;
|
|
40
|
+
const project = getObject(payload.project);
|
|
41
|
+
const attributes = getObject(payload.object_attributes);
|
|
42
|
+
const user = getObject(payload.user);
|
|
43
|
+
const host = gitlabWebhookHost(parsed);
|
|
44
|
+
const projectId = getNumber(project?.id);
|
|
45
|
+
const projectPath = getString(project?.path_with_namespace);
|
|
46
|
+
const sender = getString(payload.user_username) ?? getString(user?.username);
|
|
47
|
+
const senderId = getNumber(user?.id) ?? getNumber(payload.user_id);
|
|
48
|
+
if (!host || !projectId || !projectPath || !attributes) return void 0;
|
|
49
|
+
let action;
|
|
50
|
+
let mergeRequestIid;
|
|
51
|
+
let priority;
|
|
52
|
+
let kind;
|
|
53
|
+
let label;
|
|
54
|
+
let terminal = false;
|
|
55
|
+
if (event === "Merge Request Hook") {
|
|
56
|
+
action = getString(attributes.action)?.toLowerCase() ?? "";
|
|
57
|
+
mergeRequestIid = getNumber(attributes.iid);
|
|
58
|
+
if (action === "approved") {
|
|
59
|
+
priority = "urgent";
|
|
60
|
+
kind = "review-approved";
|
|
61
|
+
label = "approved the merge request";
|
|
62
|
+
} else if (action === "unapproved") {
|
|
63
|
+
priority = "high";
|
|
64
|
+
kind = "review-dismissed";
|
|
65
|
+
label = "revoked an approval";
|
|
66
|
+
} else if (action === "merge") {
|
|
67
|
+
priority = "urgent";
|
|
68
|
+
kind = "pull-request-merged";
|
|
69
|
+
label = "merged the merge request";
|
|
70
|
+
terminal = true;
|
|
71
|
+
} else if (action === "close") {
|
|
72
|
+
priority = "urgent";
|
|
73
|
+
kind = "pull-request-closed";
|
|
74
|
+
label = "closed the merge request";
|
|
75
|
+
terminal = true;
|
|
76
|
+
} else if (action === "reopen") {
|
|
77
|
+
priority = "high";
|
|
78
|
+
kind = "pull-request-reopened";
|
|
79
|
+
label = "reopened the merge request";
|
|
80
|
+
} else if (action === "update" && getString(attributes.oldrev)) {
|
|
81
|
+
priority = "medium";
|
|
82
|
+
kind = "pull-request-synchronize";
|
|
83
|
+
label = "pushed new commits";
|
|
84
|
+
} else if (action === "update") {
|
|
85
|
+
priority = "low";
|
|
86
|
+
kind = "pull-request-edited";
|
|
87
|
+
label = "edited the merge request";
|
|
88
|
+
} else return;
|
|
89
|
+
} else if (event === "Note Hook" && getString(attributes.noteable_type) === "MergeRequest") {
|
|
90
|
+
action = "created";
|
|
91
|
+
mergeRequestIid = getNumber(getObject(payload.merge_request)?.iid);
|
|
92
|
+
priority = "high";
|
|
93
|
+
if (getString(attributes.type) === "DiffNote") {
|
|
94
|
+
kind = "review-comment-created";
|
|
95
|
+
label = "left a review comment";
|
|
96
|
+
} else {
|
|
97
|
+
kind = "issue-comment-created";
|
|
98
|
+
label = "commented";
|
|
99
|
+
}
|
|
100
|
+
} else return;
|
|
101
|
+
if (!mergeRequestIid) return void 0;
|
|
102
|
+
const actor = sender ? `${sender} ` : "";
|
|
103
|
+
return {
|
|
104
|
+
action,
|
|
105
|
+
kind,
|
|
106
|
+
priority,
|
|
107
|
+
summary: `${actor}${label} on ${projectPath}!${mergeRequestIid}`,
|
|
108
|
+
terminal,
|
|
109
|
+
metadata: {
|
|
110
|
+
event,
|
|
111
|
+
action,
|
|
112
|
+
host,
|
|
113
|
+
projectId,
|
|
114
|
+
projectPath,
|
|
115
|
+
mergeRequestIid,
|
|
116
|
+
...sender ? { sender } : {},
|
|
117
|
+
...senderId !== void 0 ? { senderId } : {},
|
|
118
|
+
deliveryId: parsed.deliveryId
|
|
119
|
+
},
|
|
120
|
+
payload
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function notificationTargetUrl(notification) {
|
|
124
|
+
const attributes = getObject(notification.payload.object_attributes);
|
|
125
|
+
const { host, projectPath, mergeRequestIid } = notification.metadata;
|
|
126
|
+
return getString(attributes?.url) ?? mergeRequestUrl(host, projectPath, mergeRequestIid);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Comments and approval changes wake a session only when a trusted project
|
|
130
|
+
* member sent them. The check runs through the subscription's own connection so
|
|
131
|
+
* a deployment answers with the credential that created the link; an
|
|
132
|
+
* unreachable or slow provider fails closed.
|
|
133
|
+
*/
|
|
134
|
+
async function isTrustedGitLabSender(notification, subscription, gitlab) {
|
|
135
|
+
if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
|
|
136
|
+
const sender = notification.metadata.sender;
|
|
137
|
+
if (!sender || !gitlab) return false;
|
|
138
|
+
let timeout;
|
|
139
|
+
try {
|
|
140
|
+
const connectionId = gitlab.resolveActiveConnectionForHost ? await gitlab.resolveActiveConnectionForHost(subscription.data.installationExternalId, subscription.data.host) : subscription.data.installationExternalId;
|
|
141
|
+
return (await Promise.race([gitlab.getProjectMemberAccessLevel(connectionId, subscription.data.projectId, sender), new Promise((resolve) => {
|
|
142
|
+
timeout = setTimeout(() => resolve(void 0), PERMISSION_CHECK_TIMEOUT_MS);
|
|
143
|
+
})]) ?? 0) >= 30;
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
} finally {
|
|
147
|
+
if (timeout) clearTimeout(timeout);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function dispatchGitLabWebhook(parsed, dependencies) {
|
|
151
|
+
const notification = classifyGitLabWebhook(parsed);
|
|
152
|
+
if (!notification) return {
|
|
153
|
+
delivered: 0,
|
|
154
|
+
failed: 0,
|
|
155
|
+
skipped: 0,
|
|
156
|
+
ignored: true
|
|
157
|
+
};
|
|
158
|
+
const target = {
|
|
159
|
+
host: notification.metadata.host,
|
|
160
|
+
projectId: String(notification.metadata.projectId),
|
|
161
|
+
changeRequestId: String(notification.metadata.mergeRequestIid)
|
|
162
|
+
};
|
|
163
|
+
const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
|
|
164
|
+
if (!dependencies.gitlab) throw new Error("GitLab integration is required to load webhook subscriptions.");
|
|
165
|
+
return listMergeRequestSubscriptionsForWebhook(subscriptionTarget, options, dependencies.gitlab.integrationStorage);
|
|
166
|
+
});
|
|
167
|
+
const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
|
|
168
|
+
if (!dependencies.gitlab) throw new Error("GitLab integration is required to retire webhook subscriptions.");
|
|
169
|
+
return retireMergeRequestSubscription(id, status, dependencies.gitlab.integrationStorage);
|
|
170
|
+
});
|
|
171
|
+
const isAuthorizedSender = dependencies.isAuthorizedSender ?? ((candidate, subscription) => isTrustedGitLabSender(candidate, subscription, dependencies.gitlab));
|
|
172
|
+
const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopen" });
|
|
173
|
+
let delivered = 0;
|
|
174
|
+
let failed = 0;
|
|
175
|
+
let skipped = 0;
|
|
176
|
+
let rejected = false;
|
|
177
|
+
for (const subscription of subscriptions) try {
|
|
178
|
+
if (dependencies.sourceConnectionId !== void 0 && subscription.data.installationExternalId !== dependencies.sourceConnectionId) {
|
|
179
|
+
skipped += 1;
|
|
180
|
+
dependencies.onConnectionMismatch?.(subscription);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!await isAuthorizedSender(notification, subscription)) {
|
|
184
|
+
rejected = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const session = await resolveSubscriptionSession(dependencies.controller, subscription, {
|
|
188
|
+
label: "GitLab",
|
|
189
|
+
sourceControl: dependencies.gitlab?.sourceControlStorage
|
|
190
|
+
});
|
|
191
|
+
if (!session) {
|
|
192
|
+
skipped += 1;
|
|
193
|
+
dependencies.onTargetSkipped?.(subscription);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const runContext = await subscriptionRunContext(subscription, dependencies.gitlab?.sourceControlStorage);
|
|
197
|
+
if (!runContext) throw new Error(`GitLab subscription ${subscription.id} has no resolvable tenant identity; not delivered.`);
|
|
198
|
+
const result = await session.sendNotificationSignal({
|
|
199
|
+
source: "gitlab",
|
|
200
|
+
kind: notification.kind,
|
|
201
|
+
summary: notification.summary,
|
|
202
|
+
priority: notification.priority,
|
|
203
|
+
payload: notification.payload,
|
|
204
|
+
sourceId: parsed.deliveryId,
|
|
205
|
+
dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
|
|
206
|
+
coalesceKey: `gitlab:${notification.metadata.host}:${notification.metadata.projectId}:merge-request:${notification.metadata.mergeRequestIid}`,
|
|
207
|
+
metadata: {
|
|
208
|
+
event: notification.metadata.event,
|
|
209
|
+
action: notification.action,
|
|
210
|
+
repository: notification.metadata.projectPath,
|
|
211
|
+
mergeRequestIid: notification.metadata.mergeRequestIid,
|
|
212
|
+
targetUrl: notificationTargetUrl(notification),
|
|
213
|
+
deliveryId: parsed.deliveryId
|
|
214
|
+
}
|
|
215
|
+
}, { requestContext: runContext });
|
|
216
|
+
await Promise.all([result.persisted, result.accepted].filter(Boolean));
|
|
217
|
+
if (notification.terminal) await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
|
|
218
|
+
else if (notification.action === "reopen") await retireSubscription(subscription.id, "open");
|
|
219
|
+
delivered += 1;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
failed += 1;
|
|
222
|
+
dependencies.onTargetError?.(subscription, error);
|
|
223
|
+
}
|
|
224
|
+
if (rejected) dependencies.onSenderRejected?.(notification);
|
|
225
|
+
return {
|
|
226
|
+
delivered,
|
|
227
|
+
failed,
|
|
228
|
+
skipped,
|
|
229
|
+
ignored: false
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
export { classifyGitLabWebhook, dispatchGitLabWebhook, gitlabWebhookHost };
|
|
234
|
+
|
|
235
|
+
//# sourceMappingURL=webhook-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-dispatch.js","names":[],"sources":["../../../src/integrations/gitlab/webhook-dispatch.ts"],"sourcesContent":["import type { MountedMastraCode } from '@mastra/code-sdk';\nimport type { NotificationPriority } from '@mastra/core/notifications';\n\nimport { resolveSubscriptionSession, subscriptionRunContext } from '../subscription-session.js';\nimport type { SubscriptionSessionLookup } from '../subscription-session.js';\nimport { GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport {\n listMergeRequestSubscriptionsForWebhook,\n mergeRequestUrl,\n normalizeSubscriptionHost,\n retireMergeRequestSubscription,\n} from './subscriptions.js';\nimport type {\n GitLabSignalSubscriptionRow,\n GitLabSubscriptionStorage,\n GitLabWebhookMergeRequestTarget,\n} from './subscriptions.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nexport interface GitLabWebhookNotificationMetadata {\n event: string;\n action: string;\n host: string;\n projectId: number;\n projectPath: string;\n mergeRequestIid: number;\n sender?: string;\n senderId?: number;\n deliveryId: string;\n}\n\nexport interface GitLabWebhookNotification {\n action: string;\n kind: string;\n priority: NotificationPriority;\n summary: string;\n terminal: boolean;\n metadata: GitLabWebhookNotificationMetadata;\n payload: Record<string, unknown>;\n}\n\n/**\n * The integration surface this dispatch uses. Narrow on purpose so the direct\n * and Platform-backed integrations, and tests, can satisfy it.\n */\nexport interface GitLabWebhookDispatchIntegration {\n readonly integrationStorage: GitLabSubscriptionStorage;\n readonly sourceControlStorage?: SubscriptionSessionLookup;\n getProjectMemberAccessLevel(connectionId: string, projectId: string, username: string): Promise<number | undefined>;\n resolveActiveConnectionForHost?(storedConnectionId: string, host: string): Promise<string>;\n}\n\nexport interface GitLabWebhookDispatchDependencies {\n controller: MountedMastraCode['controller'];\n gitlab?: GitLabWebhookDispatchIntegration;\n listSubscriptions?: (\n target: GitLabWebhookMergeRequestTarget,\n options?: { includeTerminal?: boolean },\n ) => Promise<GitLabSignalSubscriptionRow[]>;\n retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;\n /**\n * Author gate override. The default requires the sender to be a trusted\n * project member through the subscription's connection.\n */\n isAuthorizedSender?: (\n notification: GitLabWebhookNotification,\n subscription: GitLabSignalSubscriptionRow,\n ) => Promise<boolean>;\n /** Called when the sender gate drops a notification, so the drop is observable. */\n onSenderRejected?: (notification: GitLabWebhookNotification) => void;\n onTargetError?: (subscription: GitLabSignalSubscriptionRow, error: unknown) => void;\n /** Called when a subscription names a thread this deployment does not hold. */\n onTargetSkipped?: (subscription: GitLabSignalSubscriptionRow) => void;\n /**\n * The connection the event arrived through. When set, only subscriptions\n * created under that connection are delivered; a direct project webhook\n * leaves it unset because GitLab does not say which connection it belongs to.\n */\n sourceConnectionId?: string;\n /** Called when a subscription is skipped because it belongs to another connection. */\n onConnectionMismatch?: (subscription: GitLabSignalSubscriptionRow) => void;\n}\n\nfunction getObject(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * The GitLab instance a delivery came from: the project's `web_url` host, which\n * must agree with the `X-Gitlab-Instance` header when one was sent. A mismatch\n * yields no host, so the delivery matches no subscription.\n */\nexport function gitlabWebhookHost(parsed: ParsedGitLabWebhook): string | undefined {\n const declaredHost = parsed.instanceHost ? normalizeSubscriptionHost(parsed.instanceHost) : undefined;\n const projectUrl = getString(getObject(parsed.payload.project)?.web_url);\n if (!projectUrl) return declaredHost;\n try {\n const payloadHost = normalizeSubscriptionHost(new URL(projectUrl).host);\n if (declaredHost && declaredHost !== payloadHost) return undefined;\n return payloadHost;\n } catch {\n return undefined;\n }\n}\n\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-approved',\n 'review-dismissed',\n]);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\n\nexport function classifyGitLabWebhook(parsed: ParsedGitLabWebhook): GitLabWebhookNotification | undefined {\n const { event, payload } = parsed;\n const project = getObject(payload.project);\n const attributes = getObject(payload.object_attributes);\n const user = getObject(payload.user);\n const host = gitlabWebhookHost(parsed);\n const projectId = getNumber(project?.id);\n const projectPath = getString(project?.path_with_namespace);\n const sender = getString(payload.user_username) ?? getString(user?.username);\n const senderId = getNumber(user?.id) ?? getNumber(payload.user_id);\n if (!host || !projectId || !projectPath || !attributes) return undefined;\n\n let action: string;\n let mergeRequestIid: number | undefined;\n let priority: NotificationPriority;\n let kind: string;\n let label: string;\n let terminal = false;\n\n if (event === 'Merge Request Hook') {\n action = getString(attributes.action)?.toLowerCase() ?? '';\n mergeRequestIid = getNumber(attributes.iid);\n if (action === 'approved') {\n priority = 'urgent';\n kind = 'review-approved';\n label = 'approved the merge request';\n } else if (action === 'unapproved') {\n priority = 'high';\n kind = 'review-dismissed';\n label = 'revoked an approval';\n } else if (action === 'merge') {\n priority = 'urgent';\n kind = 'pull-request-merged';\n label = 'merged the merge request';\n terminal = true;\n } else if (action === 'close') {\n priority = 'urgent';\n kind = 'pull-request-closed';\n label = 'closed the merge request';\n terminal = true;\n } else if (action === 'reopen') {\n priority = 'high';\n kind = 'pull-request-reopened';\n label = 'reopened the merge request';\n } else if (action === 'update' && getString(attributes.oldrev)) {\n priority = 'medium';\n kind = 'pull-request-synchronize';\n label = 'pushed new commits';\n } else if (action === 'update') {\n priority = 'low';\n kind = 'pull-request-edited';\n label = 'edited the merge request';\n } else {\n return undefined;\n }\n } else if (event === 'Note Hook' && getString(attributes.noteable_type) === 'MergeRequest') {\n action = 'created';\n mergeRequestIid = getNumber(getObject(payload.merge_request)?.iid);\n priority = 'high';\n if (getString(attributes.type) === 'DiffNote') {\n kind = 'review-comment-created';\n label = 'left a review comment';\n } else {\n kind = 'issue-comment-created';\n label = 'commented';\n }\n } else {\n return undefined;\n }\n if (!mergeRequestIid) return undefined;\n\n const actor = sender ? `${sender} ` : '';\n return {\n action,\n kind,\n priority,\n summary: `${actor}${label} on ${projectPath}!${mergeRequestIid}`,\n terminal,\n metadata: {\n event,\n action,\n host,\n projectId,\n projectPath,\n mergeRequestIid,\n ...(sender ? { sender } : {}),\n ...(senderId !== undefined ? { senderId } : {}),\n deliveryId: parsed.deliveryId,\n },\n payload,\n };\n}\n\nfunction notificationTargetUrl(notification: GitLabWebhookNotification): string {\n const attributes = getObject(notification.payload.object_attributes);\n const { host, projectPath, mergeRequestIid } = notification.metadata;\n return getString(attributes?.url) ?? mergeRequestUrl(host, projectPath, mergeRequestIid);\n}\n\n/**\n * Comments and approval changes wake a session only when a trusted project\n * member sent them. The check runs through the subscription's own connection so\n * a deployment answers with the credential that created the link; an\n * unreachable or slow provider fails closed.\n */\nasync function isTrustedGitLabSender(\n notification: GitLabWebhookNotification,\n subscription: GitLabSignalSubscriptionRow,\n gitlab: GitLabWebhookDispatchIntegration | undefined,\n): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n if (!sender || !gitlab) return false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const connectionId = gitlab.resolveActiveConnectionForHost\n ? await gitlab.resolveActiveConnectionForHost(subscription.data.installationExternalId, subscription.data.host)\n : subscription.data.installationExternalId;\n const accessLevel = await Promise.race([\n gitlab.getProjectMemberAccessLevel(connectionId, subscription.data.projectId, sender),\n new Promise<undefined>(resolve => {\n timeout = setTimeout(() => resolve(undefined), PERMISSION_CHECK_TIMEOUT_MS);\n }),\n ]);\n return (accessLevel ?? 0) >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch {\n return false;\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nexport async function dispatchGitLabWebhook(\n parsed: ParsedGitLabWebhook,\n dependencies: GitLabWebhookDispatchDependencies,\n): Promise<{ delivered: number; failed: number; skipped: number; ignored: boolean }> {\n const notification = classifyGitLabWebhook(parsed);\n if (!notification) return { delivered: 0, failed: 0, skipped: 0, ignored: true };\n\n const target: GitLabWebhookMergeRequestTarget = {\n host: notification.metadata.host,\n projectId: String(notification.metadata.projectId),\n changeRequestId: String(notification.metadata.mergeRequestIid),\n };\n const listSubscriptions =\n dependencies.listSubscriptions ??\n ((subscriptionTarget: GitLabWebhookMergeRequestTarget, options?: { includeTerminal?: boolean }) => {\n if (!dependencies.gitlab) throw new Error('GitLab integration is required to load webhook subscriptions.');\n return listMergeRequestSubscriptionsForWebhook(\n subscriptionTarget,\n options,\n dependencies.gitlab.integrationStorage,\n );\n });\n const retireSubscription =\n dependencies.retireSubscription ??\n ((id: string, status: 'open' | 'closed' | 'merged') => {\n if (!dependencies.gitlab) throw new Error('GitLab integration is required to retire webhook subscriptions.');\n return retireMergeRequestSubscription(id, status, dependencies.gitlab.integrationStorage);\n });\n const isAuthorizedSender =\n dependencies.isAuthorizedSender ??\n ((candidate: GitLabWebhookNotification, subscription: GitLabSignalSubscriptionRow) =>\n isTrustedGitLabSender(candidate, subscription, dependencies.gitlab));\n\n const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === 'reopen' });\n let delivered = 0;\n let failed = 0;\n let skipped = 0;\n let rejected = false;\n\n for (const subscription of subscriptions) {\n try {\n // Two connections can reach the same project. Each polled event names\n // its connection, so a subscription is woken once, by its own connection,\n // and never by a credential that did not create the link.\n if (\n dependencies.sourceConnectionId !== undefined &&\n subscription.data.installationExternalId !== dependencies.sourceConnectionId\n ) {\n skipped += 1;\n dependencies.onConnectionMismatch?.(subscription);\n continue;\n }\n if (!(await isAuthorizedSender(notification, subscription))) {\n rejected = true;\n continue;\n }\n const session = await resolveSubscriptionSession(dependencies.controller, subscription, {\n label: 'GitLab',\n sourceControl: dependencies.gitlab?.sourceControlStorage,\n });\n // No session means this deployment does not hold the subscribed thread.\n // Not a delivery failure: leave the subscription for wherever it lives.\n if (!session) {\n skipped += 1;\n dependencies.onTargetSkipped?.(subscription);\n continue;\n }\n const runContext = await subscriptionRunContext(subscription, dependencies.gitlab?.sourceControlStorage);\n // Without a tenant the woken run would fail closed after the terminal\n // subscription had already been retired, so count it as a failed delivery.\n if (!runContext) {\n throw new Error(`GitLab subscription ${subscription.id} has no resolvable tenant identity; not delivered.`);\n }\n const result = await session.sendNotificationSignal(\n {\n source: 'gitlab',\n kind: notification.kind,\n summary: notification.summary,\n priority: notification.priority,\n payload: notification.payload,\n sourceId: parsed.deliveryId,\n dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,\n coalesceKey: `gitlab:${notification.metadata.host}:${notification.metadata.projectId}:merge-request:${notification.metadata.mergeRequestIid}`,\n metadata: {\n event: notification.metadata.event,\n action: notification.action,\n repository: notification.metadata.projectPath,\n mergeRequestIid: notification.metadata.mergeRequestIid,\n targetUrl: notificationTargetUrl(notification),\n deliveryId: parsed.deliveryId,\n },\n },\n { requestContext: runContext },\n );\n await Promise.all([result.persisted, result.accepted].filter(Boolean));\n if (notification.terminal) {\n await retireSubscription(subscription.id, notification.kind === 'pull-request-merged' ? 'merged' : 'closed');\n } else if (notification.action === 'reopen') {\n await retireSubscription(subscription.id, 'open');\n }\n delivered += 1;\n } catch (error) {\n failed += 1;\n dependencies.onTargetError?.(subscription, error);\n }\n }\n if (rejected) dependencies.onSenderRejected?.(notification);\n\n return { delivered, failed, skipped, ignored: false };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import type { GitLabWebhookDispatchDependencies } from './webhook-dispatch.js';
|
|
3
|
+
export declare const SUPPORTED_GITLAB_WEBHOOK_EVENTS: Set<string>;
|
|
4
|
+
export interface ParsedGitLabWebhook {
|
|
5
|
+
event: string;
|
|
6
|
+
deliveryId: string;
|
|
7
|
+
instanceHost?: string;
|
|
8
|
+
payload: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface GitLabWebhookMetadata {
|
|
11
|
+
event: string;
|
|
12
|
+
projectId?: number;
|
|
13
|
+
projectPath?: string;
|
|
14
|
+
issueIid?: number;
|
|
15
|
+
mergeRequestIid?: number;
|
|
16
|
+
noteableType?: string;
|
|
17
|
+
sender?: string;
|
|
18
|
+
}
|
|
19
|
+
export type GitLabWebhookResult = {
|
|
20
|
+
status: 202;
|
|
21
|
+
body: {
|
|
22
|
+
ok: true;
|
|
23
|
+
ignored?: true;
|
|
24
|
+
};
|
|
25
|
+
} | {
|
|
26
|
+
status: 400;
|
|
27
|
+
body: {
|
|
28
|
+
error: 'bad_request';
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
status: 401;
|
|
33
|
+
body: {
|
|
34
|
+
error: 'unauthorized';
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* What processing an already-parsed delivery needs: the rules ingress and,
|
|
40
|
+
* when a controller is mounted, the subscription dispatch. Shared by the
|
|
41
|
+
* direct webhook route and the Platform event poller.
|
|
42
|
+
*/
|
|
43
|
+
export interface GitLabWebhookProcessOptions extends Partial<Omit<GitLabWebhookDispatchDependencies, 'controller'>> {
|
|
44
|
+
ingestFactoryEvent?: (event: ParsedGitLabWebhook) => Promise<unknown>;
|
|
45
|
+
/** When present, merge-request activity is also delivered to subscribed sessions. */
|
|
46
|
+
controller?: GitLabWebhookDispatchDependencies['controller'];
|
|
47
|
+
}
|
|
48
|
+
export interface GitLabWebhookHandlerOptions extends GitLabWebhookProcessOptions {
|
|
49
|
+
webhookSecret?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function verifyGitLabToken(receivedToken: string, secret: string): boolean;
|
|
52
|
+
export declare function parseGitLabWebhook(c: Context, secret: string | undefined): Promise<ParsedGitLabWebhook | GitLabWebhookResult>;
|
|
53
|
+
export declare function normalizeGitLabWebhookMetadata(parsed: ParsedGitLabWebhook): GitLabWebhookMetadata;
|
|
54
|
+
/**
|
|
55
|
+
* Build the parsed form of a GitLab webhook from its raw body alone, for
|
|
56
|
+
* deliveries that arrive without HTTP headers. The event name comes from
|
|
57
|
+
* `object_kind`; the caller supplies a delivery id that is stable across
|
|
58
|
+
* replays so rules replay handling and session dedupe keys behave as they do
|
|
59
|
+
* for a direct delivery. Returns null when the body is not a webhook object or
|
|
60
|
+
* its kind is one Factory does not handle, which callers treat exactly like an
|
|
61
|
+
* unsupported event header: acknowledged and dropped.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseGitLabWebhookBody(body: unknown, deliveryId: string): ParsedGitLabWebhook | null;
|
|
64
|
+
/**
|
|
65
|
+
* Process a parsed delivery: rules ingress first, then session dispatch when a
|
|
66
|
+
* controller is mounted. Ingestion failures propagate so the caller can retry
|
|
67
|
+
* the delivery (GitLab redelivers on a non-2xx; the poller leaves its cursor
|
|
68
|
+
* in place).
|
|
69
|
+
*/
|
|
70
|
+
export declare function processGitLabWebhook(parsed: ParsedGitLabWebhook, options: GitLabWebhookProcessOptions): Promise<GitLabWebhookResult>;
|
|
71
|
+
export declare function handleGitLabWebhook(c: Context, options: GitLabWebhookHandlerOptions): Promise<GitLabWebhookResult>;
|
|
72
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAE/E,eAAO,MAAM,+BAA+B,aAA0E,CAAC;AAEvH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAA;KAAE,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAChE;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,2BAA4B,SAAQ,OAAO,CAAC,IAAI,CAAC,iCAAiC,EAAE,YAAY,CAAC,CAAC;IACjH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,qFAAqF;IACrF,UAAU,CAAC,EAAE,iCAAiC,CAAC,YAAY,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,2BAA4B,SAAQ,2BAA2B;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA8BD,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED,wBAAsB,kBAAkB,CACtC,CAAC,EAAE,OAAO,EACV,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAAC,mBAAmB,GAAG,mBAAmB,CAAC,CAoCpD;AAcD,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,CA2BjG;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAQpG;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC,CA6B9B;AAED,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAK9B"}
|