@mastra/factory 0.0.0-esbuild-bundle-worker-20260807173433
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 +1008 -0
- package/LICENSE.md +30 -0
- package/README.md +27 -0
- package/dist/auth.d.ts +183 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +507 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/intake.d.ts +141 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/version-control.d.ts +247 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/factory.d.ts +159 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +491 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/integrations/base.d.ts +254 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +42 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +220 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +1162 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- 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 +59 -0
- package/dist/integrations/github/pat.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +75 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/reconcile-worker.d.ts +41 -0
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -0
- package/dist/integrations/github/reconcile-worker.js +125 -0
- package/dist/integrations/github/reconcile-worker.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +69 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1335 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/rules.d.ts +105 -0
- package/dist/integrations/github/rules.d.ts.map +1 -0
- package/dist/integrations/github/rules.js +551 -0
- package/dist/integrations/github/rules.js.map +1 -0
- package/dist/integrations/github/sandbox-release.d.ts +53 -0
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
- package/dist/integrations/github/sandbox-release.js +115 -0
- package/dist/integrations/github/sandbox-release.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +281 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +547 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +26 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +192 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +62 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/token-refresh.d.ts +12 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -0
- package/dist/integrations/github/token-refresh.js +24 -0
- package/dist/integrations/github/token-refresh.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +107 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +371 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +85 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +235 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +676 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +240 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/rules.d.ts +44 -0
- package/dist/integrations/linear/rules.d.ts.map +1 -0
- package/dist/integrations/linear/rules.js +133 -0
- package/dist/integrations/linear/rules.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/platform/api-client.d.ts +26 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +156 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +349 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +64 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +1006 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +483 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/slack/connect-route.d.ts +46 -0
- package/dist/integrations/slack/connect-route.d.ts.map +1 -0
- package/dist/integrations/slack/connect-route.js +186 -0
- package/dist/integrations/slack/connect-route.js.map +1 -0
- package/dist/integrations/slack/integration.d.ts +62 -0
- package/dist/integrations/slack/integration.d.ts.map +1 -0
- package/dist/integrations/slack/integration.js +68 -0
- package/dist/integrations/slack/integration.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts +200 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -0
- package/dist/integrations/slack/slack.js +411 -0
- package/dist/integrations/slack/slack.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +91 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +235 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +972 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/custom-provider-source.d.ts +52 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -0
- package/dist/routes/custom-provider-source.js +124 -0
- package/dist/routes/custom-provider-source.js.map +1 -0
- package/dist/routes/fs.d.ts +151 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +708 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +463 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +461 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +86 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +15 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +168 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +107 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +297 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +156 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +695 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +28 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +322 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +43 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +547 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +5 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +378 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +30 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +60 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +175 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +59 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +49 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +232 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +261 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +43 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +308 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +222 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +322 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +16 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +16 -0
- package/dist/server-error.js.map +1 -0
- package/dist/session/factory-session.d.ts +116 -0
- package/dist/session/factory-session.d.ts.map +1 -0
- package/dist/session/factory-session.js +162 -0
- package/dist/session/factory-session.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +40 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +33 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +104 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +59 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +83 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +66 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +184 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +76 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +269 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/channel-identity/base.d.ts +96 -0
- package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
- package/dist/storage/domains/channel-identity/base.js +149 -0
- package/dist/storage/domains/channel-identity/base.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +273 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/custom-providers/base.d.ts +57 -0
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
- package/dist/storage/domains/custom-providers/base.js +145 -0
- package/dist/storage/domains/custom-providers/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +80 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +317 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/memory-settings/base.d.ts +62 -0
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
- package/dist/storage/domains/memory-settings/base.js +126 -0
- package/dist/storage/domains/memory-settings/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +96 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +58 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +111 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +122 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +360 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +914 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +380 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1320 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +221 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +24 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +287 -0
- package/dist/workspace.js.map +1 -0
- package/factory-skills/configure-factory-rules/SKILL.md +67 -0
- package/factory-skills/factory-plan/SKILL.md +61 -0
- package/factory-skills/factory-review/SKILL.md +157 -0
- package/factory-skills/factory-triage/SKILL.md +99 -0
- package/package.json +83 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { MountedMastraCode } from '@mastra/code-sdk';
|
|
2
|
+
import type { NotificationPriority } from '@mastra/core/notifications';
|
|
3
|
+
import type { Context } from 'hono';
|
|
4
|
+
import type { GithubIntegration, GithubRepositoryPermission } from './integration.js';
|
|
5
|
+
import type { GithubIssueTriageInput, GithubIssueTriageResult } from './issue-triage.js';
|
|
6
|
+
import type { GithubSignalSubscriptionRow, GithubSubscriptionStorage, GithubWebhookPullRequestTarget } from './subscriptions.js';
|
|
7
|
+
export type GithubIssueTriageRunInput = GithubIssueTriageInput;
|
|
8
|
+
export type GithubIssueTriageRunResult = GithubIssueTriageResult;
|
|
9
|
+
export interface GithubWebhookHandlerOptions {
|
|
10
|
+
/** Integration providing webhook-secret verification + collaborator permission checks. */
|
|
11
|
+
github: GithubIntegration;
|
|
12
|
+
runIssueTriage?: (input: GithubIssueTriageRunInput) => Promise<GithubIssueTriageRunResult>;
|
|
13
|
+
ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface GithubWebhookMetadata {
|
|
16
|
+
event: string;
|
|
17
|
+
action?: string;
|
|
18
|
+
deliveryId: string;
|
|
19
|
+
repository?: string;
|
|
20
|
+
repositoryId?: number;
|
|
21
|
+
issueNumber?: number;
|
|
22
|
+
pullRequestNumber?: number;
|
|
23
|
+
sender?: string;
|
|
24
|
+
senderType?: string;
|
|
25
|
+
installationId?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ParsedGithubWebhook {
|
|
28
|
+
event: string;
|
|
29
|
+
deliveryId: string;
|
|
30
|
+
payload: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export type GithubWebhookResult = {
|
|
33
|
+
status: 202;
|
|
34
|
+
body: {
|
|
35
|
+
ok: true;
|
|
36
|
+
ignored?: true;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
status: 400;
|
|
40
|
+
body: {
|
|
41
|
+
error: 'bad_request';
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
status: 401;
|
|
46
|
+
body: {
|
|
47
|
+
error: 'unauthorized';
|
|
48
|
+
message: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export interface GithubWebhookNotification {
|
|
52
|
+
action: string;
|
|
53
|
+
kind: string;
|
|
54
|
+
priority: NotificationPriority;
|
|
55
|
+
summary: string;
|
|
56
|
+
terminal: boolean;
|
|
57
|
+
metadata: GithubWebhookMetadata & {
|
|
58
|
+
pullRequestNumber: number;
|
|
59
|
+
repositoryId: number;
|
|
60
|
+
installationId: number;
|
|
61
|
+
};
|
|
62
|
+
payload: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/** The Factory session row fields a woken session has to run as. */
|
|
65
|
+
export type FactorySessionOwner = {
|
|
66
|
+
userId: string;
|
|
67
|
+
orgId: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The integration surface this dispatch uses. Narrow on purpose: the GitHub App
|
|
71
|
+
* integration and the platform-backed one are unrelated classes, and only this
|
|
72
|
+
* much is common to both.
|
|
73
|
+
*/
|
|
74
|
+
export interface GithubWebhookDispatchIntegration {
|
|
75
|
+
readonly integrationStorage: GithubSubscriptionStorage;
|
|
76
|
+
readonly sourceControlStorage: {
|
|
77
|
+
sessions: {
|
|
78
|
+
getBySessionId(sessionId: string): Promise<FactorySessionOwner | null>;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
getRepositoryCollaboratorPermission(installationId: number, repoFullName: string, username: string, signal?: AbortSignal): Promise<GithubRepositoryPermission | undefined>;
|
|
82
|
+
}
|
|
83
|
+
export interface GithubWebhookDispatchDependencies {
|
|
84
|
+
controller: MountedMastraCode['controller'];
|
|
85
|
+
/**
|
|
86
|
+
* Integration used by the default sender-authorization check (collaborator
|
|
87
|
+
* permission lookup) and to resolve the owner of a session being recreated.
|
|
88
|
+
* Author-gated notifications fail closed when neither this nor an
|
|
89
|
+
* `isAuthorizedSender` override is supplied.
|
|
90
|
+
*/
|
|
91
|
+
github?: GithubWebhookDispatchIntegration;
|
|
92
|
+
listSubscriptions?: (target: GithubWebhookPullRequestTarget, options?: {
|
|
93
|
+
includeTerminal?: boolean;
|
|
94
|
+
}) => Promise<GithubSignalSubscriptionRow[]>;
|
|
95
|
+
retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;
|
|
96
|
+
isAuthorizedSender?: (notification: GithubWebhookNotification) => Promise<boolean>;
|
|
97
|
+
onTargetError?: (subscription: GithubSignalSubscriptionRow, error: unknown) => void;
|
|
98
|
+
}
|
|
99
|
+
export declare function normalizeGithubWebhookMetadata(parsed: ParsedGithubWebhook): GithubWebhookMetadata;
|
|
100
|
+
export declare function classifyGithubWebhook(parsed: ParsedGithubWebhook): GithubWebhookNotification | undefined;
|
|
101
|
+
export declare function dispatchGithubWebhook(parsed: ParsedGithubWebhook, dependencies: GithubWebhookDispatchDependencies): Promise<{
|
|
102
|
+
delivered: number;
|
|
103
|
+
failed: number;
|
|
104
|
+
ignored: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
export declare function handleGithubWebhook(c: Context, options: GithubWebhookHandlerOptions & Partial<Omit<GithubWebhookDispatchDependencies, 'github'>>): Promise<GithubWebhookResult>;
|
|
107
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from "./subscriptions.js";
|
|
2
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
+
import { RequestContext } from "@mastra/core/request-context";
|
|
4
|
+
//#region src/integrations/github/webhook.ts
|
|
5
|
+
const SUPPORTED_GITHUB_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
6
|
+
"issues",
|
|
7
|
+
"issue_comment",
|
|
8
|
+
"pull_request",
|
|
9
|
+
"pull_request_review",
|
|
10
|
+
"pull_request_review_comment"
|
|
11
|
+
]);
|
|
12
|
+
function normalizeHeader(value) {
|
|
13
|
+
if (!value) return null;
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
16
|
+
}
|
|
17
|
+
function verifySignature(rawBody, signature, secret) {
|
|
18
|
+
if (!signature.startsWith("sha256=")) return false;
|
|
19
|
+
const signatureHex = signature.slice(7);
|
|
20
|
+
if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;
|
|
21
|
+
const expectedHex = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
22
|
+
const received = Buffer.from(signatureHex, "hex");
|
|
23
|
+
const expected = Buffer.from(expectedHex, "hex");
|
|
24
|
+
return received.length === expected.length && timingSafeEqual(received, expected);
|
|
25
|
+
}
|
|
26
|
+
async function parseGithubWebhook(c, secret) {
|
|
27
|
+
if (!secret) return {
|
|
28
|
+
status: 401,
|
|
29
|
+
body: {
|
|
30
|
+
error: "unauthorized",
|
|
31
|
+
message: "GitHub webhook secret is not configured"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const event = normalizeHeader(c.req.header("x-github-event"));
|
|
35
|
+
const deliveryId = normalizeHeader(c.req.header("x-github-delivery"));
|
|
36
|
+
const signature = normalizeHeader(c.req.header("x-hub-signature-256"));
|
|
37
|
+
if (!event) return {
|
|
38
|
+
status: 400,
|
|
39
|
+
body: {
|
|
40
|
+
error: "bad_request",
|
|
41
|
+
message: "Missing x-github-event header"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
if (!deliveryId) return {
|
|
45
|
+
status: 400,
|
|
46
|
+
body: {
|
|
47
|
+
error: "bad_request",
|
|
48
|
+
message: "Missing x-github-delivery header"
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (!signature) return {
|
|
52
|
+
status: 401,
|
|
53
|
+
body: {
|
|
54
|
+
error: "unauthorized",
|
|
55
|
+
message: "Missing x-hub-signature-256 header"
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const rawBody = await c.req.text();
|
|
59
|
+
if (!verifySignature(rawBody, signature, secret)) return {
|
|
60
|
+
status: 401,
|
|
61
|
+
body: {
|
|
62
|
+
error: "unauthorized",
|
|
63
|
+
message: "Invalid GitHub webhook signature"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
let payload;
|
|
67
|
+
try {
|
|
68
|
+
payload = JSON.parse(rawBody);
|
|
69
|
+
} catch {
|
|
70
|
+
return {
|
|
71
|
+
status: 400,
|
|
72
|
+
body: {
|
|
73
|
+
error: "bad_request",
|
|
74
|
+
message: "Malformed JSON payload"
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {
|
|
79
|
+
status: 400,
|
|
80
|
+
body: {
|
|
81
|
+
error: "bad_request",
|
|
82
|
+
message: "Payload must be a JSON object"
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
event,
|
|
87
|
+
deliveryId,
|
|
88
|
+
payload
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function getObject(value) {
|
|
92
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
93
|
+
}
|
|
94
|
+
function getString(value) {
|
|
95
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
96
|
+
}
|
|
97
|
+
function getNumber(value) {
|
|
98
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
99
|
+
}
|
|
100
|
+
function getBoolean(value) {
|
|
101
|
+
return typeof value === "boolean" ? value : void 0;
|
|
102
|
+
}
|
|
103
|
+
function normalizeGithubWebhookMetadata(parsed) {
|
|
104
|
+
const { event, deliveryId, payload } = parsed;
|
|
105
|
+
const repository = getObject(payload.repository);
|
|
106
|
+
const issue = getObject(payload.issue);
|
|
107
|
+
const pullRequest = getObject(payload.pull_request);
|
|
108
|
+
const sender = getObject(payload.sender);
|
|
109
|
+
const installation = getObject(payload.installation);
|
|
110
|
+
const issuePullRequest = getObject(issue?.pull_request);
|
|
111
|
+
return {
|
|
112
|
+
event,
|
|
113
|
+
action: getString(payload.action),
|
|
114
|
+
deliveryId,
|
|
115
|
+
repository: getString(repository?.full_name),
|
|
116
|
+
repositoryId: getNumber(repository?.id),
|
|
117
|
+
issueNumber: getNumber(issue?.number),
|
|
118
|
+
pullRequestNumber: getNumber(pullRequest?.number) ?? (event === "issue_comment" && issuePullRequest ? getNumber(issue?.number) : void 0),
|
|
119
|
+
sender: getString(sender?.login),
|
|
120
|
+
senderType: getString(sender?.type),
|
|
121
|
+
installationId: getNumber(installation?.id)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function notificationSummary(metadata, label) {
|
|
125
|
+
return `${metadata.sender ? `${metadata.sender} ` : ""}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;
|
|
126
|
+
}
|
|
127
|
+
function notificationTargetUrl(event, payload) {
|
|
128
|
+
if (event === "issue_comment" || event === "pull_request_review_comment") return getString(getObject(payload.comment)?.html_url);
|
|
129
|
+
if (event === "pull_request_review") return getString(getObject(payload.review)?.html_url);
|
|
130
|
+
return getString(getObject(payload.pull_request)?.html_url);
|
|
131
|
+
}
|
|
132
|
+
function classifyGithubWebhook(parsed) {
|
|
133
|
+
const metadata = normalizeGithubWebhookMetadata(parsed);
|
|
134
|
+
const { event, payload } = parsed;
|
|
135
|
+
const action = metadata.action;
|
|
136
|
+
if (!action || !metadata.repositoryId || !metadata.installationId || !metadata.pullRequestNumber || !metadata.repository) return;
|
|
137
|
+
let priority;
|
|
138
|
+
let kind;
|
|
139
|
+
let label;
|
|
140
|
+
let terminal = false;
|
|
141
|
+
if (event === "pull_request_review" && action === "submitted") {
|
|
142
|
+
const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll("_", "-");
|
|
143
|
+
priority = state === "approved" || state === "changes-requested" ? "urgent" : "high";
|
|
144
|
+
kind = state === "approved" ? "review-approved" : state === "changes-requested" ? "review-changes-requested" : "review-submitted";
|
|
145
|
+
label = state === "approved" ? "approved the pull request" : state === "changes-requested" ? "requested changes" : "submitted a review";
|
|
146
|
+
} else if (event === "pull_request" && action === "closed") {
|
|
147
|
+
const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;
|
|
148
|
+
priority = "urgent";
|
|
149
|
+
kind = merged ? "pull-request-merged" : "pull-request-closed";
|
|
150
|
+
label = merged ? "merged the pull request" : "closed the pull request";
|
|
151
|
+
terminal = true;
|
|
152
|
+
} else if (event === "issue_comment" && action === "created") {
|
|
153
|
+
priority = "high";
|
|
154
|
+
kind = "issue-comment-created";
|
|
155
|
+
label = "commented";
|
|
156
|
+
} else if (event === "pull_request_review_comment" && action === "created") {
|
|
157
|
+
priority = "high";
|
|
158
|
+
kind = "review-comment-created";
|
|
159
|
+
label = "left a review comment";
|
|
160
|
+
} else if (event === "pull_request" && action === "reopened") {
|
|
161
|
+
priority = "high";
|
|
162
|
+
kind = "pull-request-reopened";
|
|
163
|
+
label = "reopened the pull request";
|
|
164
|
+
} else if (event === "pull_request_review" && action === "dismissed") {
|
|
165
|
+
priority = "high";
|
|
166
|
+
kind = "review-dismissed";
|
|
167
|
+
label = "dismissed a review";
|
|
168
|
+
} else if (event === "pull_request" && [
|
|
169
|
+
"synchronize",
|
|
170
|
+
"ready_for_review",
|
|
171
|
+
"converted_to_draft",
|
|
172
|
+
"assigned",
|
|
173
|
+
"unassigned",
|
|
174
|
+
"review_requested",
|
|
175
|
+
"review_request_removed"
|
|
176
|
+
].includes(action)) {
|
|
177
|
+
priority = "medium";
|
|
178
|
+
kind = `pull-request-${action.replaceAll("_", "-")}`;
|
|
179
|
+
label = action.replaceAll("_", " ");
|
|
180
|
+
} else if (event === "pull_request" && [
|
|
181
|
+
"edited",
|
|
182
|
+
"labeled",
|
|
183
|
+
"unlabeled",
|
|
184
|
+
"milestoned",
|
|
185
|
+
"demilestoned"
|
|
186
|
+
].includes(action)) {
|
|
187
|
+
priority = "low";
|
|
188
|
+
kind = `pull-request-${action.replaceAll("_", "-")}`;
|
|
189
|
+
label = action.replaceAll("_", " ");
|
|
190
|
+
} else return;
|
|
191
|
+
return {
|
|
192
|
+
action,
|
|
193
|
+
kind,
|
|
194
|
+
priority,
|
|
195
|
+
summary: notificationSummary(metadata, label),
|
|
196
|
+
terminal,
|
|
197
|
+
metadata: {
|
|
198
|
+
...metadata,
|
|
199
|
+
pullRequestNumber: metadata.pullRequestNumber,
|
|
200
|
+
repositoryId: metadata.repositoryId,
|
|
201
|
+
installationId: metadata.installationId
|
|
202
|
+
},
|
|
203
|
+
payload
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function resolveSubscriptionSession(controller, subscription, github) {
|
|
207
|
+
const { sessionId, resourceId, threadId } = subscription;
|
|
208
|
+
if (!sessionId || !resourceId || !threadId) throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);
|
|
209
|
+
const scope = subscription.sessionScope || void 0;
|
|
210
|
+
let session = await controller.getSessionByResource(resourceId, scope);
|
|
211
|
+
if (!session) {
|
|
212
|
+
const tags = {
|
|
213
|
+
factoryProjectId: resourceId,
|
|
214
|
+
projectRepositoryId: subscription.data.projectRepositoryId,
|
|
215
|
+
...scope ? { worktreePath: scope } : {}
|
|
216
|
+
};
|
|
217
|
+
const sessionRow = await github?.sourceControlStorage.sessions.getBySessionId(resourceId);
|
|
218
|
+
if (!sessionRow) throw new Error(`GitHub subscription ${subscription.id} has no Factory session ${resourceId} to run as.`);
|
|
219
|
+
const requestContext = new RequestContext();
|
|
220
|
+
requestContext.set("user", {
|
|
221
|
+
workosId: sessionRow.userId,
|
|
222
|
+
organizationId: sessionRow.orgId
|
|
223
|
+
});
|
|
224
|
+
session = await controller.createSession({
|
|
225
|
+
id: sessionId,
|
|
226
|
+
ownerId: sessionRow.userId,
|
|
227
|
+
resourceId,
|
|
228
|
+
scope,
|
|
229
|
+
tags,
|
|
230
|
+
requestContext
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (session.thread.getId() !== threadId) await session.thread.switch({
|
|
234
|
+
threadId,
|
|
235
|
+
emitEvent: false
|
|
236
|
+
});
|
|
237
|
+
if (session.thread.getId() !== threadId) throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
|
|
238
|
+
return session;
|
|
239
|
+
}
|
|
240
|
+
const AUTHORIZED_BOTS = /* @__PURE__ */ new Set(["coderabbitai[bot]", "devin-ai-integration[bot]"]);
|
|
241
|
+
const AUTHORIZED_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
242
|
+
"admin",
|
|
243
|
+
"maintain",
|
|
244
|
+
"write"
|
|
245
|
+
]);
|
|
246
|
+
const PERMISSION_CHECK_TIMEOUT_MS = 5e3;
|
|
247
|
+
const AUTHOR_GATED_KINDS = /* @__PURE__ */ new Set([
|
|
248
|
+
"issue-comment-created",
|
|
249
|
+
"review-comment-created",
|
|
250
|
+
"review-submitted",
|
|
251
|
+
"review-approved",
|
|
252
|
+
"review-changes-requested",
|
|
253
|
+
"review-dismissed"
|
|
254
|
+
]);
|
|
255
|
+
async function isAuthorizedGithubSender(notification, github) {
|
|
256
|
+
if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;
|
|
257
|
+
const sender = notification.metadata.sender;
|
|
258
|
+
const repository = notification.metadata.repository;
|
|
259
|
+
if (!sender || !repository) return false;
|
|
260
|
+
const normalizedSender = sender.toLowerCase();
|
|
261
|
+
if (notification.metadata.senderType?.toLowerCase() === "bot" || normalizedSender.endsWith("[bot]")) return AUTHORIZED_BOTS.has(normalizedSender);
|
|
262
|
+
if (!github) return false;
|
|
263
|
+
const abortController = new AbortController();
|
|
264
|
+
let timeout;
|
|
265
|
+
try {
|
|
266
|
+
const permission = await Promise.race([github.getRepositoryCollaboratorPermission(notification.metadata.installationId, repository, sender, abortController.signal), new Promise((resolve) => {
|
|
267
|
+
timeout = setTimeout(() => {
|
|
268
|
+
abortController.abort();
|
|
269
|
+
resolve(void 0);
|
|
270
|
+
}, PERMISSION_CHECK_TIMEOUT_MS);
|
|
271
|
+
})]);
|
|
272
|
+
return permission !== void 0 && AUTHORIZED_PERMISSIONS.has(permission);
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
} finally {
|
|
276
|
+
if (timeout) clearTimeout(timeout);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function dispatchGithubWebhook(parsed, dependencies) {
|
|
280
|
+
const notification = classifyGithubWebhook(parsed);
|
|
281
|
+
if (!notification) return {
|
|
282
|
+
delivered: 0,
|
|
283
|
+
failed: 0,
|
|
284
|
+
ignored: true
|
|
285
|
+
};
|
|
286
|
+
if (!await (dependencies.isAuthorizedSender ?? ((n) => isAuthorizedGithubSender(n, dependencies.github)))(notification)) return {
|
|
287
|
+
delivered: 0,
|
|
288
|
+
failed: 0,
|
|
289
|
+
ignored: true
|
|
290
|
+
};
|
|
291
|
+
const target = {
|
|
292
|
+
installationExternalId: notification.metadata.installationId.toString(),
|
|
293
|
+
repositoryExternalId: notification.metadata.repositoryId.toString(),
|
|
294
|
+
changeRequestId: notification.metadata.pullRequestNumber.toString()
|
|
295
|
+
};
|
|
296
|
+
const listSubscriptions = dependencies.listSubscriptions ?? ((subscriptionTarget, options) => {
|
|
297
|
+
if (!dependencies.github) throw new Error("GitHub integration is required to load webhook subscriptions.");
|
|
298
|
+
return listPullRequestSubscriptionsForWebhook(subscriptionTarget, options, dependencies.github.integrationStorage);
|
|
299
|
+
});
|
|
300
|
+
const retireSubscription = dependencies.retireSubscription ?? ((id, status) => {
|
|
301
|
+
if (!dependencies.github) throw new Error("GitHub integration is required to retire webhook subscriptions.");
|
|
302
|
+
return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);
|
|
303
|
+
});
|
|
304
|
+
const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === "reopened" });
|
|
305
|
+
let delivered = 0;
|
|
306
|
+
let failed = 0;
|
|
307
|
+
for (const subscription of subscriptions) try {
|
|
308
|
+
const result = await (await resolveSubscriptionSession(dependencies.controller, subscription, dependencies.github)).sendNotificationSignal({
|
|
309
|
+
source: "github",
|
|
310
|
+
kind: notification.kind,
|
|
311
|
+
summary: notification.summary,
|
|
312
|
+
priority: notification.priority,
|
|
313
|
+
payload: notification.payload,
|
|
314
|
+
sourceId: parsed.deliveryId,
|
|
315
|
+
dedupeKey: `${parsed.deliveryId}:${subscription.sessionId}:${subscription.threadId}`,
|
|
316
|
+
coalesceKey: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,
|
|
317
|
+
metadata: {
|
|
318
|
+
event: notification.metadata.event,
|
|
319
|
+
action: notification.action,
|
|
320
|
+
repository: notification.metadata.repository,
|
|
321
|
+
issueNumber: notification.metadata.issueNumber,
|
|
322
|
+
pullRequestNumber: notification.metadata.pullRequestNumber,
|
|
323
|
+
targetUrl: notificationTargetUrl(parsed.event, parsed.payload),
|
|
324
|
+
deliveryId: parsed.deliveryId
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
await Promise.all([result.persisted, result.accepted].filter(Boolean));
|
|
328
|
+
if (notification.terminal) await retireSubscription(subscription.id, notification.kind === "pull-request-merged" ? "merged" : "closed");
|
|
329
|
+
else if (notification.action === "reopened") await retireSubscription(subscription.id, "open");
|
|
330
|
+
delivered += 1;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
failed += 1;
|
|
333
|
+
dependencies.onTargetError?.(subscription, error);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
delivered,
|
|
337
|
+
failed,
|
|
338
|
+
ignored: false
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async function handleGithubWebhook(c, options) {
|
|
342
|
+
const parsed = await parseGithubWebhook(c, options.github.webhookSecret);
|
|
343
|
+
if ("status" in parsed) return parsed;
|
|
344
|
+
if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) return {
|
|
345
|
+
status: 202,
|
|
346
|
+
body: {
|
|
347
|
+
ok: true,
|
|
348
|
+
ignored: true
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const metadata = normalizeGithubWebhookMetadata(parsed);
|
|
352
|
+
console.info("[GitHub Webhook]", metadata);
|
|
353
|
+
if (options.ingestFactoryEvent) await options.ingestFactoryEvent(parsed);
|
|
354
|
+
if (!options.controller) return {
|
|
355
|
+
status: 202,
|
|
356
|
+
body: { ok: true }
|
|
357
|
+
};
|
|
358
|
+
const result = await dispatchGithubWebhook(parsed, options);
|
|
359
|
+
if (result.failed > 0) console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);
|
|
360
|
+
return {
|
|
361
|
+
status: 202,
|
|
362
|
+
body: {
|
|
363
|
+
ok: true,
|
|
364
|
+
...result.ignored ? { ignored: true } : {}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
export { classifyGithubWebhook, dispatchGithubWebhook, handleGithubWebhook, normalizeGithubWebhookMetadata };
|
|
370
|
+
|
|
371
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","names":[],"sources":["../../../src/integrations/github/webhook.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport type { NotificationPriority } from '@mastra/core/notifications';\nimport { RequestContext } from '@mastra/core/request-context';\nimport type { Context } from 'hono';\nimport type { GithubIntegration, GithubRepositoryPermission } from './integration.js';\nimport type { GithubIssueTriageInput, GithubIssueTriageResult } from './issue-triage.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from './subscriptions.js';\nimport type {\n GithubSignalSubscriptionRow,\n GithubSubscriptionStorage,\n GithubWebhookPullRequestTarget,\n} from './subscriptions.js';\n\nexport type GithubIssueTriageRunInput = GithubIssueTriageInput;\nexport type GithubIssueTriageRunResult = GithubIssueTriageResult;\n\nexport interface GithubWebhookHandlerOptions {\n /** Integration providing webhook-secret verification + collaborator permission checks. */\n github: GithubIntegration;\n runIssueTriage?: (input: GithubIssueTriageRunInput) => Promise<GithubIssueTriageRunResult>;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n}\n\nconst SUPPORTED_GITHUB_WEBHOOK_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n]);\n\nexport interface GithubWebhookMetadata {\n event: string;\n action?: string;\n deliveryId: string;\n repository?: string;\n repositoryId?: number;\n issueNumber?: number;\n pullRequestNumber?: number;\n sender?: string;\n senderType?: string;\n installationId?: number;\n}\n\nexport interface ParsedGithubWebhook {\n event: string;\n deliveryId: string;\n payload: Record<string, unknown>;\n}\n\nexport type GithubWebhookResult =\n | { status: 202; body: { ok: true; ignored?: true } }\n | { status: 400; body: { error: 'bad_request'; message: string } }\n | { status: 401; body: { error: 'unauthorized'; message: string } };\n\nexport interface GithubWebhookNotification {\n action: string;\n kind: string;\n priority: NotificationPriority;\n summary: string;\n terminal: boolean;\n metadata: GithubWebhookMetadata & { pullRequestNumber: number; repositoryId: number; installationId: number };\n payload: Record<string, unknown>;\n}\n\n/** The Factory session row fields a woken session has to run as. */\nexport type FactorySessionOwner = { userId: string; orgId: string };\n\n/**\n * The integration surface this dispatch uses. Narrow on purpose: the GitHub App\n * integration and the platform-backed one are unrelated classes, and only this\n * much is common to both.\n */\nexport interface GithubWebhookDispatchIntegration {\n readonly integrationStorage: GithubSubscriptionStorage;\n readonly sourceControlStorage: {\n sessions: { getBySessionId(sessionId: string): Promise<FactorySessionOwner | null> };\n };\n getRepositoryCollaboratorPermission(\n installationId: number,\n repoFullName: string,\n username: string,\n signal?: AbortSignal,\n ): Promise<GithubRepositoryPermission | undefined>;\n}\n\nexport interface GithubWebhookDispatchDependencies {\n controller: MountedMastraCode['controller'];\n /**\n * Integration used by the default sender-authorization check (collaborator\n * permission lookup) and to resolve the owner of a session being recreated.\n * Author-gated notifications fail closed when neither this nor an\n * `isAuthorizedSender` override is supplied.\n */\n github?: GithubWebhookDispatchIntegration;\n listSubscriptions?: (\n target: GithubWebhookPullRequestTarget,\n options?: { includeTerminal?: boolean },\n ) => Promise<GithubSignalSubscriptionRow[]>;\n retireSubscription?: (id: string, status: 'open' | 'closed' | 'merged') => Promise<void>;\n isAuthorizedSender?: (notification: GithubWebhookNotification) => Promise<boolean>;\n onTargetError?: (subscription: GithubSignalSubscriptionRow, error: unknown) => void;\n}\n\nfunction normalizeHeader(value: string | undefined | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\nfunction verifySignature(rawBody: string, signature: string, secret: string): boolean {\n if (!signature.startsWith('sha256=')) return false;\n const signatureHex = signature.slice('sha256='.length);\n if (!/^[a-fA-F0-9]{64}$/.test(signatureHex)) return false;\n\n const expectedHex = createHmac('sha256', secret).update(rawBody).digest('hex');\n const received = Buffer.from(signatureHex, 'hex');\n const expected = Buffer.from(expectedHex, 'hex');\n return received.length === expected.length && timingSafeEqual(received, expected);\n}\n\nasync function parseGithubWebhook(\n c: Context,\n secret: string | undefined,\n): Promise<ParsedGithubWebhook | GithubWebhookResult> {\n if (!secret) {\n return { status: 401, body: { error: 'unauthorized', message: 'GitHub webhook secret is not configured' } };\n }\n\n const event = normalizeHeader(c.req.header('x-github-event'));\n const deliveryId = normalizeHeader(c.req.header('x-github-delivery'));\n const signature = normalizeHeader(c.req.header('x-hub-signature-256'));\n\n if (!event) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-event header' } };\n if (!deliveryId) return { status: 400, body: { error: 'bad_request', message: 'Missing x-github-delivery header' } };\n if (!signature)\n return { status: 401, body: { error: 'unauthorized', message: 'Missing x-hub-signature-256 header' } };\n\n const rawBody = await c.req.text();\n if (!verifySignature(rawBody, signature, secret)) {\n return { status: 401, body: { error: 'unauthorized', message: 'Invalid GitHub webhook signature' } };\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n return { status: 400, body: { error: 'bad_request', message: 'Malformed JSON payload' } };\n }\n\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return { status: 400, body: { error: 'bad_request', message: 'Payload must be a JSON object' } };\n }\n\n return { event, deliveryId, payload: payload as Record<string, unknown> };\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\nfunction getBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nexport function normalizeGithubWebhookMetadata(parsed: ParsedGithubWebhook): GithubWebhookMetadata {\n const { event, deliveryId, payload } = parsed;\n const repository = getObject(payload.repository);\n const issue = getObject(payload.issue);\n const pullRequest = getObject(payload.pull_request);\n const sender = getObject(payload.sender);\n const installation = getObject(payload.installation);\n const issuePullRequest = getObject(issue?.pull_request);\n\n return {\n event,\n action: getString(payload.action),\n deliveryId,\n repository: getString(repository?.full_name),\n repositoryId: getNumber(repository?.id),\n issueNumber: getNumber(issue?.number),\n pullRequestNumber:\n getNumber(pullRequest?.number) ??\n (event === 'issue_comment' && issuePullRequest ? getNumber(issue?.number) : undefined),\n sender: getString(sender?.login),\n senderType: getString(sender?.type),\n installationId: getNumber(installation?.id),\n };\n}\n\nfunction notificationSummary(metadata: GithubWebhookMetadata, label: string): string {\n const actor = metadata.sender ? `${metadata.sender} ` : '';\n return `${actor}${label} on ${metadata.repository}#${metadata.pullRequestNumber}`;\n}\n\nfunction notificationTargetUrl(event: string, payload: Record<string, unknown>): string | undefined {\n if (event === 'issue_comment' || event === 'pull_request_review_comment') {\n return getString(getObject(payload.comment)?.html_url);\n }\n if (event === 'pull_request_review') {\n return getString(getObject(payload.review)?.html_url);\n }\n return getString(getObject(payload.pull_request)?.html_url);\n}\n\nexport function classifyGithubWebhook(parsed: ParsedGithubWebhook): GithubWebhookNotification | undefined {\n const metadata = normalizeGithubWebhookMetadata(parsed);\n const { event, payload } = parsed;\n const action = metadata.action;\n if (\n !action ||\n !metadata.repositoryId ||\n !metadata.installationId ||\n !metadata.pullRequestNumber ||\n !metadata.repository\n ) {\n return undefined;\n }\n\n let priority: NotificationPriority;\n let kind: string;\n let label: string;\n let terminal = false;\n\n if (event === 'pull_request_review' && action === 'submitted') {\n const state = getString(getObject(payload.review)?.state)?.toLowerCase().replaceAll('_', '-');\n priority = state === 'approved' || state === 'changes-requested' ? 'urgent' : 'high';\n kind =\n state === 'approved'\n ? 'review-approved'\n : state === 'changes-requested'\n ? 'review-changes-requested'\n : 'review-submitted';\n label =\n state === 'approved'\n ? 'approved the pull request'\n : state === 'changes-requested'\n ? 'requested changes'\n : 'submitted a review';\n } else if (event === 'pull_request' && action === 'closed') {\n const merged = getBoolean(getObject(payload.pull_request)?.merged) === true;\n priority = 'urgent';\n kind = merged ? 'pull-request-merged' : 'pull-request-closed';\n label = merged ? 'merged the pull request' : 'closed the pull request';\n terminal = true;\n } else if (event === 'issue_comment' && action === 'created') {\n priority = 'high';\n kind = 'issue-comment-created';\n label = 'commented';\n } else if (event === 'pull_request_review_comment' && action === 'created') {\n priority = 'high';\n kind = 'review-comment-created';\n label = 'left a review comment';\n } else if (event === 'pull_request' && action === 'reopened') {\n priority = 'high';\n kind = 'pull-request-reopened';\n label = 'reopened the pull request';\n } else if (event === 'pull_request_review' && action === 'dismissed') {\n priority = 'high';\n kind = 'review-dismissed';\n label = 'dismissed a review';\n } else if (\n event === 'pull_request' &&\n [\n 'synchronize',\n 'ready_for_review',\n 'converted_to_draft',\n 'assigned',\n 'unassigned',\n 'review_requested',\n 'review_request_removed',\n ].includes(action)\n ) {\n priority = 'medium';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else if (\n event === 'pull_request' &&\n ['edited', 'labeled', 'unlabeled', 'milestoned', 'demilestoned'].includes(action)\n ) {\n priority = 'low';\n kind = `pull-request-${action.replaceAll('_', '-')}`;\n label = action.replaceAll('_', ' ');\n } else {\n return undefined;\n }\n\n return {\n action,\n kind,\n priority,\n summary: notificationSummary(metadata, label),\n terminal,\n metadata: {\n ...metadata,\n pullRequestNumber: metadata.pullRequestNumber,\n repositoryId: metadata.repositoryId,\n installationId: metadata.installationId,\n },\n payload,\n };\n}\n\nasync function resolveSubscriptionSession(\n controller: MountedMastraCode['controller'],\n subscription: GithubSignalSubscriptionRow,\n github?: GithubWebhookDispatchIntegration,\n) {\n const { sessionId, resourceId, threadId } = subscription;\n if (!sessionId || !resourceId || !threadId) {\n throw new Error(`GitHub subscription ${subscription.id} is missing its session binding.`);\n }\n const scope = subscription.sessionScope || undefined;\n let session = await controller.getSessionByResource(resourceId, scope);\n if (!session) {\n const tags = {\n factoryProjectId: resourceId,\n projectRepositoryId: subscription.data.projectRepositoryId,\n ...(scope ? { worktreePath: scope } : {}),\n };\n // Creating the session resolves its workspace, which authorizes the caller\n // against the Factory session row — no signed-in user, so run as its owner.\n const sessionRow = await github?.sourceControlStorage.sessions.getBySessionId(resourceId);\n if (!sessionRow) {\n throw new Error(`GitHub subscription ${subscription.id} has no Factory session ${resourceId} to run as.`);\n }\n const requestContext = new RequestContext();\n requestContext.set('user', { workosId: sessionRow.userId, organizationId: sessionRow.orgId });\n session = await controller.createSession({\n id: sessionId,\n ownerId: sessionRow.userId,\n resourceId,\n scope,\n tags,\n requestContext,\n });\n }\n if (session.thread.getId() !== threadId) {\n await session.thread.switch({ threadId, emitEvent: false });\n }\n if (session.thread.getId() !== threadId) {\n throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);\n }\n return session;\n}\n\nconst AUTHORIZED_BOTS = new Set(['coderabbitai[bot]', 'devin-ai-integration[bot]']);\nconst AUTHORIZED_PERMISSIONS = new Set(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-submitted',\n 'review-approved',\n 'review-changes-requested',\n 'review-dismissed',\n]);\n\nasync function isAuthorizedGithubSender(\n notification: GithubWebhookNotification,\n github: Pick<GithubWebhookDispatchIntegration, 'getRepositoryCollaboratorPermission'> | undefined,\n): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n const normalizedSender = sender.toLowerCase();\n if (notification.metadata.senderType?.toLowerCase() === 'bot' || normalizedSender.endsWith('[bot]')) {\n return AUTHORIZED_BOTS.has(normalizedSender);\n }\n if (!github) return false;\n const abortController = new AbortController();\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const permission = await Promise.race([\n github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n ),\n new Promise<undefined>(resolve => {\n timeout = setTimeout(() => {\n abortController.abort();\n resolve(undefined);\n }, PERMISSION_CHECK_TIMEOUT_MS);\n }),\n ]);\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\nexport async function dispatchGithubWebhook(\n parsed: ParsedGithubWebhook,\n dependencies: GithubWebhookDispatchDependencies,\n): Promise<{ delivered: number; failed: number; ignored: boolean }> {\n const notification = classifyGithubWebhook(parsed);\n if (!notification) return { delivered: 0, failed: 0, ignored: true };\n const isAuthorizedSender =\n dependencies.isAuthorizedSender ??\n ((n: GithubWebhookNotification) => isAuthorizedGithubSender(n, dependencies.github));\n if (!(await isAuthorizedSender(notification))) {\n return { delivered: 0, failed: 0, ignored: true };\n }\n\n const target = {\n installationExternalId: notification.metadata.installationId.toString(),\n repositoryExternalId: notification.metadata.repositoryId.toString(),\n changeRequestId: notification.metadata.pullRequestNumber.toString(),\n };\n const listSubscriptions =\n dependencies.listSubscriptions ??\n ((subscriptionTarget: GithubWebhookPullRequestTarget, options?: { includeTerminal?: boolean }) => {\n if (!dependencies.github) throw new Error('GitHub integration is required to load webhook subscriptions.');\n return listPullRequestSubscriptionsForWebhook(\n subscriptionTarget,\n options,\n dependencies.github.integrationStorage,\n );\n });\n const retireSubscription =\n dependencies.retireSubscription ??\n ((id: string, status: 'open' | 'closed' | 'merged') => {\n if (!dependencies.github) throw new Error('GitHub integration is required to retire webhook subscriptions.');\n return retirePullRequestSubscription(id, status, dependencies.github.integrationStorage);\n });\n const subscriptions = await listSubscriptions(target, { includeTerminal: notification.action === 'reopened' });\n let delivered = 0;\n let failed = 0;\n\n for (const subscription of subscriptions) {\n try {\n const session = await resolveSubscriptionSession(dependencies.controller, subscription, dependencies.github);\n const result = await session.sendNotificationSignal({\n source: 'github',\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: `github:${subscription.data.repositoryExternalId}:pull-request:${subscription.data.changeRequestId}`,\n metadata: {\n event: notification.metadata.event,\n action: notification.action,\n repository: notification.metadata.repository,\n issueNumber: notification.metadata.issueNumber,\n pullRequestNumber: notification.metadata.pullRequestNumber,\n targetUrl: notificationTargetUrl(parsed.event, parsed.payload),\n deliveryId: parsed.deliveryId,\n },\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 === 'reopened') {\n await retireSubscription(subscription.id, 'open');\n }\n delivered += 1;\n } catch (error) {\n failed += 1;\n dependencies.onTargetError?.(subscription, error);\n }\n }\n\n return { delivered, failed, ignored: false };\n}\n\nexport async function handleGithubWebhook(\n c: Context,\n options: GithubWebhookHandlerOptions & Partial<Omit<GithubWebhookDispatchDependencies, 'github'>>,\n): Promise<GithubWebhookResult> {\n const parsed = await parseGithubWebhook(c, options.github.webhookSecret);\n if ('status' in parsed) return parsed;\n\n if (!SUPPORTED_GITHUB_WEBHOOK_EVENTS.has(parsed.event)) {\n return { status: 202, body: { ok: true, ignored: true } };\n }\n\n const metadata = normalizeGithubWebhookMetadata(parsed);\n console.info('[GitHub Webhook]', metadata);\n\n if (options.ingestFactoryEvent) {\n await options.ingestFactoryEvent(parsed);\n }\n\n if (!options.controller) {\n return { status: 202, body: { ok: true } };\n }\n\n const result = await dispatchGithubWebhook(parsed, options as GithubWebhookDispatchDependencies);\n if (result.failed > 0) {\n console.warn(`[GitHub Webhook] ${result.failed} subscribed target(s) failed for delivery ${parsed.deliveryId}.`);\n }\n return { status: 202, body: { ok: true, ...(result.ignored ? { ignored: true as const } : {}) } };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear tools exposed to the coding agent.
|
|
3
|
+
*
|
|
4
|
+
* Wired into the agent through the SDK's async `extraTools` provider: on each
|
|
5
|
+
* tool-set resolution we map the session's resourceId (the factory project id)
|
|
6
|
+
* to its owning org and only expose the Linear tools when that org has a
|
|
7
|
+
* Linear connection. Projects whose org never connected Linear (or when the
|
|
8
|
+
* feature is disabled) see no Linear tools at all — the model is never shown
|
|
9
|
+
* tools it can't use.
|
|
10
|
+
*
|
|
11
|
+
* Tenancy mirrors the Linear API routes: everything is scoped by the org that
|
|
12
|
+
* owns the project, and tokens are refreshed through the integration's shared
|
|
13
|
+
* connection lifecycle.
|
|
14
|
+
*/
|
|
15
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
16
|
+
import type { LinearIntegration } from './integration.js';
|
|
17
|
+
declare function createLinearGetIssueTool(linear: LinearIntegration, orgId: string): import("@mastra/core/tools").Tool<{
|
|
18
|
+
issue: string;
|
|
19
|
+
}, unknown, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "linear_get_issue", unknown>;
|
|
20
|
+
declare function createLinearCommentTool(linear: LinearIntegration, orgId: string): import("@mastra/core/tools").Tool<{
|
|
21
|
+
issue: string;
|
|
22
|
+
body: string;
|
|
23
|
+
}, unknown, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "linear_create_comment", unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Async `extraTools` provider: expose Linear tools only when the session's
|
|
26
|
+
* project belongs to an org with an active Linear connection.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildLinearAgentTools({ requestContext, linear, }: {
|
|
29
|
+
requestContext: RequestContext;
|
|
30
|
+
/** The integration instance providing the Linear API client. */
|
|
31
|
+
linear: LinearIntegration;
|
|
32
|
+
}): Promise<Record<string, ReturnType<typeof createLinearGetIssueTool> | ReturnType<typeof createLinearCommentTool>>>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/agent-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,iBAAS,wBAAwB,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM;;yIA+BzE;AAED,iBAAS,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM;;;8IAsCxE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,EAC1C,cAAc,EACd,MAAM,GACP,EAAE;IACD,cAAc,EAAE,cAAc,CAAC;IAC/B,gEAAgE;IAChE,MAAM,EAAE,iBAAiB,CAAC;CAC3B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,CAmBpH"}
|