@mastra/factory 0.26.0-alpha.1 → 0.26.0-alpha.3
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 +74 -7
- package/dist/boards/define-board.d.ts +19 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +41 -9
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/project-boards.d.ts +78 -0
- package/dist/boards/project-boards.d.ts.map +1 -0
- package/dist/boards/project-boards.js +194 -0
- package/dist/boards/project-boards.js.map +1 -0
- package/dist/boards/review.js +2 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/stored-board.d.ts +101 -0
- package/dist/boards/stored-board.d.ts.map +1 -0
- package/dist/boards/stored-board.js +139 -0
- package/dist/boards/stored-board.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +6 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +6 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/environment/build.d.ts +1 -1
- package/dist/environment/build.d.ts.map +1 -1
- package/dist/environment/build.js +2 -0
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts +15 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +51 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +5 -4
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +10 -8
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -2
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +6 -1
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/jira/binding-scope.d.ts +30 -0
- package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
- package/dist/integrations/jira/binding-scope.js +50 -0
- package/dist/integrations/jira/binding-scope.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +12 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -1
- package/dist/integrations/jira/claim.js +24 -1
- package/dist/integrations/jira/claim.js.map +1 -1
- package/dist/integrations/jira/intake-jql.d.ts +32 -0
- package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
- package/dist/integrations/jira/intake-jql.js +57 -0
- package/dist/integrations/jira/intake-jql.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +4 -3
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +14 -21
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/routes.d.ts +3 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -1
- package/dist/integrations/jira/routes.js +16 -29
- package/dist/integrations/jira/routes.js.map +1 -1
- package/dist/integrations/jira/rules.d.ts.map +1 -1
- package/dist/integrations/jira/rules.js +8 -13
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +5 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +7 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +7 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +26 -24
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/jira/routes.d.ts +3 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
- package/dist/integrations/platform/jira/routes.js +17 -28
- package/dist/integrations/platform/jira/routes.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +2 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +3 -2
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/boards.d.ts +30 -0
- package/dist/routes/boards.d.ts.map +1 -0
- package/dist/routes/boards.js +272 -0
- package/dist/routes/boards.js.map +1 -0
- package/dist/routes/contracts.d.ts +249 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +107 -35
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/intake.d.ts +15 -10
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +109 -22
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +10 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +13 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +9 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +24 -2
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +64 -42
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +4 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +7 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +70 -34
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +3 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +11 -6
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +2 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +3 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +12 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +6 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/boards/base.d.ts +110 -0
- package/dist/storage/domains/boards/base.d.ts.map +1 -0
- package/dist/storage/domains/boards/base.js +292 -0
- package/dist/storage/domains/boards/base.js.map +1 -0
- package/dist/storage/domains/boards/schemas.d.ts +11 -0
- package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
- package/dist/storage/domains/boards/schemas.js +106 -0
- package/dist/storage/domains/boards/schemas.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +4 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +5 -2
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +86 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +298 -59
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +2 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +15 -10
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +16 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +40 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +3 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/session-tools.d.ts +3 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +2 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +3 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -1
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts +3 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +3 -2
- package/dist/tools/work-item-tools.js.map +1 -1
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +14 -5
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +18 -7
- package/package.json +9 -9
|
@@ -3,6 +3,7 @@ import { IssueReconcileWorker } from "../../issue-reconcile-worker.js";
|
|
|
3
3
|
import { adfToText } from "../../jira/adf.js";
|
|
4
4
|
import { JiraApiClient, JiraApiError } from "../../jira/api.js";
|
|
5
5
|
import { resolveJiraRules } from "../../jira/default-rules.js";
|
|
6
|
+
import { buildIntakeJql, unfilteredJiraScopes } from "../../jira/intake-jql.js";
|
|
6
7
|
import { attachJiraRules } from "../../jira/rules.js";
|
|
7
8
|
import { attachJiraIssueReconciler } from "../../jira/issue-reconciler.js";
|
|
8
9
|
import { jiraReconciliationEnabled, jiraReconciliationInterval } from "../../jira/reconciliation-config.js";
|
|
@@ -92,6 +93,7 @@ var PlatformJiraIntegration = class {
|
|
|
92
93
|
return (await this.#activeConnections()).length > 0;
|
|
93
94
|
}
|
|
94
95
|
intake = {
|
|
96
|
+
bindingLabelFilters: true,
|
|
95
97
|
resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
|
|
96
98
|
listSources: () => this.#listSources(),
|
|
97
99
|
listItems: (input) => this.#listItems(input),
|
|
@@ -138,7 +140,7 @@ var PlatformJiraIntegration = class {
|
|
|
138
140
|
items: [],
|
|
139
141
|
nextCursor: null
|
|
140
142
|
};
|
|
141
|
-
const page = await this.listActiveIssues(cursor, sourceIds);
|
|
143
|
+
const page = await this.listActiveIssues(cursor, unfilteredJiraScopes(sourceIds));
|
|
142
144
|
return {
|
|
143
145
|
items: page.issues.map((issue) => ({
|
|
144
146
|
source: {
|
|
@@ -174,14 +176,19 @@ var PlatformJiraIntegration = class {
|
|
|
174
176
|
};
|
|
175
177
|
}
|
|
176
178
|
async #listIssues(input) {
|
|
177
|
-
const page = await this.listActiveIssues(input.cursor, input.sourceIds, input.labels);
|
|
179
|
+
const page = await this.listActiveIssues(input.cursor, unfilteredJiraScopes(input.sourceIds ?? []), input.labels);
|
|
178
180
|
return {
|
|
179
181
|
issues: page.issues,
|
|
180
182
|
nextCursor: page.nextCursor
|
|
181
183
|
};
|
|
182
184
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
/**
|
|
186
|
+
* One page of active issues across connections. Each scope's `projectId` is
|
|
187
|
+
* an encoded Platform source id; sources are grouped per connection and each
|
|
188
|
+
* group runs one JQL built from only that connection's projects.
|
|
189
|
+
*/
|
|
190
|
+
async listActiveIssues(cursor, scopes = [], labels) {
|
|
191
|
+
const grouped = groupSources(scopes);
|
|
185
192
|
if (grouped.length === 0) return {
|
|
186
193
|
issues: [],
|
|
187
194
|
nextCursor: null
|
|
@@ -194,7 +201,7 @@ var PlatformJiraIntegration = class {
|
|
|
194
201
|
for (let groupIndex = decodedCursor.group; groupIndex < grouped.length; groupIndex++) {
|
|
195
202
|
const group = grouped[groupIndex];
|
|
196
203
|
const context = await this.#connectionContextById(group.connectionId);
|
|
197
|
-
const jql = buildIntakeJql(group.
|
|
204
|
+
const jql = buildIntakeJql(group.scopes, labels);
|
|
198
205
|
if (jql === null) continue;
|
|
199
206
|
const page = await context.api.searchIssues({
|
|
200
207
|
jql,
|
|
@@ -206,7 +213,7 @@ var PlatformJiraIntegration = class {
|
|
|
206
213
|
}) : groupIndex + 1 < grouped.length ? encodePageCursor({ group: groupIndex + 1 }) : null;
|
|
207
214
|
return {
|
|
208
215
|
issues: page.issues.map((issue) => {
|
|
209
|
-
const projectId = issue.fields.project?.id ?? group.
|
|
216
|
+
const projectId = issue.fields.project?.id ?? group.scopes[0]?.projectId ?? "";
|
|
210
217
|
return {
|
|
211
218
|
...this.#toIntakeIssue(issue, context.siteUrl),
|
|
212
219
|
sourceId: encodeSourceId(group.connectionId, projectId),
|
|
@@ -411,6 +418,7 @@ var PlatformJiraIntegration = class {
|
|
|
411
418
|
jira: this,
|
|
412
419
|
auth: ctx.auth,
|
|
413
420
|
intake: ctx.storage.intake,
|
|
421
|
+
workItems: ctx.runtime?.workItems,
|
|
414
422
|
appDbConfigured: Boolean(ctx.factoryStorage),
|
|
415
423
|
ingestFactoryIssues: attachJiraRules(this, ctx)
|
|
416
424
|
});
|
|
@@ -474,18 +482,22 @@ function decodeIssueReference(value) {
|
|
|
474
482
|
return null;
|
|
475
483
|
}
|
|
476
484
|
}
|
|
477
|
-
|
|
485
|
+
/** A Platform source scope: `projectId` is the encoded source id, decoded per connection here. */
|
|
486
|
+
function groupSources(scopes) {
|
|
478
487
|
const groups = /* @__PURE__ */ new Map();
|
|
479
|
-
for (const
|
|
480
|
-
const source = decodeSourceId(
|
|
488
|
+
for (const scope of scopes) {
|
|
489
|
+
const source = decodeSourceId(scope.projectId);
|
|
481
490
|
if (!source) continue;
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
491
|
+
const projects = groups.get(source.connectionId) ?? /* @__PURE__ */ new Map();
|
|
492
|
+
if (!projects.has(source.projectId)) projects.set(source.projectId, {
|
|
493
|
+
...scope,
|
|
494
|
+
projectId: source.projectId
|
|
495
|
+
});
|
|
496
|
+
groups.set(source.connectionId, projects);
|
|
485
497
|
}
|
|
486
|
-
return [...groups.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([connectionId,
|
|
498
|
+
return [...groups.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([connectionId, projects]) => ({
|
|
487
499
|
connectionId,
|
|
488
|
-
|
|
500
|
+
scopes: [...projects.values()].sort((left, right) => left.projectId.localeCompare(right.projectId))
|
|
489
501
|
}));
|
|
490
502
|
}
|
|
491
503
|
function encodePageCursor(cursor) {
|
|
@@ -515,16 +527,6 @@ function normalizeSiteUrl(value) {
|
|
|
515
527
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw new JiraApiError("Jira site URL is invalid.", 502);
|
|
516
528
|
return url.origin;
|
|
517
529
|
}
|
|
518
|
-
function buildIntakeJql(projectIds, labels) {
|
|
519
|
-
const clauses = [];
|
|
520
|
-
const safeProjects = (projectIds ?? []).map((id) => id.trim()).filter((id) => /^[A-Za-z0-9_]+$/.test(id)).map((id) => /^\d+$/.test(id) ? id : `"${id}"`);
|
|
521
|
-
if (projectIds && safeProjects.length === 0) return null;
|
|
522
|
-
if (safeProjects.length > 0) clauses.push(`project IN (${safeProjects.join(", ")})`);
|
|
523
|
-
clauses.push("statusCategory != Done");
|
|
524
|
-
const safeLabels = [...new Set((labels ?? []).map((label) => label.trim().replace(/["\\]/g, "")).filter(Boolean))];
|
|
525
|
-
if (safeLabels.length > 0) clauses.push(`labels IN (${safeLabels.map((label) => `"${label}"`).join(", ")})`);
|
|
526
|
-
return `${clauses.join(" AND ")} ORDER BY updated DESC`;
|
|
527
|
-
}
|
|
528
530
|
function currentStatusMatches(issue, target) {
|
|
529
531
|
const status = issue.fields.status;
|
|
530
532
|
if (!status) return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":[],"sources":["../../../../src/integrations/platform/jira/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n CreatedIntakeComment,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n IntakeItemPage,\n IntakeSource,\n ListIntakeIssuesInput,\n ListIntakeItemsInput,\n ResolveIntakeDispatchInput,\n ResolvedIntakeDispatch,\n UpdateIntakeIssueInput,\n} from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { IssueReconcileWorker } from '../../issue-reconcile-worker.js';\nimport { adfToText } from '../../jira/adf.js';\nimport type { JiraComment, JiraIssue, JiraTransition } from '../../jira/api.js';\nimport { JiraApiClient, JiraApiError } from '../../jira/api.js';\nimport type { JiraEventRules, JiraRuleOverrides } from '../../jira/default-rules.js';\nimport { resolveJiraRules } from '../../jira/default-rules.js';\nimport { attachJiraIssueReconciler } from '../../jira/issue-reconciler.js';\nimport { jiraReconciliationEnabled, jiraReconciliationInterval } from '../../jira/reconciliation-config.js';\nimport { attachJiraRules } from '../../jira/rules.js';\nimport { logPlatformInfo, PlatformApiClient, platformApiClientConfigFromEnv } from '../api-client.js';\nimport type { PlatformApiClientConfig } from '../api-client.js';\nimport { buildPlatformJiraAgentTools } from './agent-tools.js';\nimport { buildPlatformJiraRoutes } from './routes.js';\n\ninterface PlatformIntegrationConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\ninterface JiraConnectionContext {\n connection: PlatformIntegrationConnection;\n api: JiraApiClient;\n siteUrl: string;\n}\n\ninterface PlatformConnectionContextResponse {\n connection_config: Record<string, unknown> | null;\n metadata: Record<string, unknown> | null;\n}\n\ninterface JiraIssueReference {\n connectionId: string;\n issueId: string;\n projectId?: string;\n}\n\ninterface JiraPageCursor {\n group: number;\n jira?: string;\n}\n\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Hard stop for project pagination so a misbehaving `isLast` can't loop forever. */\nconst PROJECT_SEARCH_MAX_PAGES = 50;\nconst ISSUE_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*-\\d+$/;\nconst PLATFORM_JIRA_PROVIDER_CONFIG_KEY = 'jira';\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst JIRA_CONNECTION_TOKEN_PREFIX = 'jira-connection:';\nconst JIRA_ISSUE_REF_PREFIX = 'jira-issue:';\nconst JIRA_SOURCE_PREFIX = 'jira-project:';\n\nconst STATE_TYPE_TO_CATEGORY: Record<'unstarted' | 'started' | 'completed', string> = {\n unstarted: 'new',\n started: 'indeterminate',\n completed: 'done',\n};\n\nfunction stateTypeFromCategory(key: string | undefined): string | null {\n switch (key) {\n case 'new':\n return 'unstarted';\n case 'indeterminate':\n return 'started';\n case 'done':\n return 'completed';\n default:\n return null;\n }\n}\n\nexport interface PlatformJiraIntegrationConfig {\n clientConfig?: PlatformApiClientConfig;\n /** Per-event replacements; omitted events retain defaults, null disables. */\n rules?: JiraRuleOverrides;\n}\n\nexport class PlatformJiraIntegration implements FactoryIntegration {\n readonly id = 'jira';\n readonly #clientConfig: PlatformApiClientConfig;\n readonly #platformClient: PlatformApiClient;\n readonly #endpointHost: string;\n readonly #cloudIdByConnectionId = new Map<string, string>();\n readonly #siteUrlByConnectionId = new Map<string, string>();\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n readonly #rules: JiraEventRules;\n\n get rules(): JiraEventRules {\n return this.#rules;\n }\n\n constructor(config: PlatformJiraIntegrationConfig = {}) {\n this.#rules = resolveJiraRules(config.rules);\n this.#clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#platformClient = new PlatformApiClient(this.#clientConfig);\n this.#endpointHost = new URL(this.#clientConfig.baseUrl).host;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Jira integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects)\n throw new Error('PlatformJiraIntegration is not initialized — the factory binds storage during prepare().');\n return this.#projects;\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n const orgId = project?.orgId ?? null;\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n } catch {\n return null;\n }\n }\n\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#cloudIdByConnectionId.clear();\n this.#siteUrlByConnectionId.clear();\n }\n\n async listConnections(): Promise<PlatformIntegrationConnection[]> {\n const providerKey = encodeURIComponent(PLATFORM_JIRA_PROVIDER_CONFIG_KEY);\n const result = await this.#platformClient.request<{ connections: PlatformIntegrationConnection[] }>(\n 'GET',\n `/v2/connections?providerKey=${providerKey}`,\n );\n return result.connections;\n }\n\n async hasActiveConnections(): Promise<boolean> {\n return (await this.#activeConnections()).length > 0;\n }\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: () => this.#listSources(),\n listItems: input => this.#listItems(input),\n listIssues: input => this.#listIssues(input),\n getIssue: input => this.#getIssue(input),\n createComment: input => this.#createComment(input),\n updateIssue: input => this.#updateIssue(input),\n };\n\n async #resolveIntakeDispatch({ externalSource }: ResolveIntakeDispatchInput): Promise<ResolvedIntakeDispatch | null> {\n if (externalSource.type !== 'issue') return null;\n const reference = decodeIssueReference(externalSource.externalId);\n if (!reference) return null;\n return {\n connection: jiraConnection(reference.connectionId),\n ...(reference.projectId ? { sourceId: encodeSourceId(reference.connectionId, reference.projectId) } : {}),\n issueId: reference.issueId,\n };\n }\n\n async #listSources(): Promise<IntakeSource[]> {\n const sources: IntakeSource[] = [];\n for (const connection of await this.#activeConnections()) {\n const context = await this.#connectionContext(connection);\n let startAt = 0;\n for (let pageCount = 0; pageCount < PROJECT_SEARCH_MAX_PAGES; pageCount++) {\n const page = await context.api.listProjects({ startAt });\n for (const project of page.values) {\n sources.push({\n id: encodeSourceId(connection.id, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n key: project.key,\n connectionId: connection.id,\n site: new URL(context.siteUrl).host,\n },\n });\n }\n if (page.isLast || page.values.length === 0) break;\n startAt += page.values.length;\n }\n }\n return sources;\n }\n\n async #listItems({ sourceIds, cursor }: ListIntakeItemsInput): Promise<IntakeItemPage> {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const page = await this.listActiveIssues(cursor, sourceIds);\n return {\n items: page.issues.map(issue => ({\n source: { type: 'issue', externalId: issue.externalId, url: issue.url },\n sourceId: issue.sourceId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state ?? undefined,\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n issueRef: issue.externalId,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priority,\n project: issue.source,\n site: issue.site,\n assignee: issue.assignee,\n assignees: issue.assignees ?? [],\n creator: issue.author,\n author: issue.author,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n },\n })),\n nextCursor: page.nextCursor,\n };\n }\n\n async #listIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const page = await this.listActiveIssues(input.cursor, input.sourceIds, input.labels);\n return { issues: page.issues, nextCursor: page.nextCursor };\n }\n\n async listActiveIssues(\n cursor?: string,\n sourceIds: string[] = [],\n labels?: string[],\n ): Promise<{\n issues: Array<IntakeIssue & { sourceId: string; externalId: string; site: string }>;\n nextCursor: string | null;\n }> {\n const grouped = groupSources(sourceIds);\n if (grouped.length === 0) return { issues: [], nextCursor: null };\n const decodedCursor = decodePageCursor(cursor);\n if (decodedCursor.group >= grouped.length) return { issues: [], nextCursor: null };\n\n for (let groupIndex = decodedCursor.group; groupIndex < grouped.length; groupIndex++) {\n const group = grouped[groupIndex]!;\n const context = await this.#connectionContextById(group.connectionId);\n const jql = buildIntakeJql(group.projectIds, labels);\n if (jql === null) continue;\n const page = await context.api.searchIssues({\n jql,\n ...(groupIndex === decodedCursor.group && decodedCursor.jira ? { nextPageToken: decodedCursor.jira } : {}),\n });\n const nextCursor = page.nextPageToken\n ? encodePageCursor({ group: groupIndex, jira: page.nextPageToken })\n : groupIndex + 1 < grouped.length\n ? encodePageCursor({ group: groupIndex + 1 })\n : null;\n return {\n issues: page.issues.map(issue => {\n const projectId = issue.fields.project?.id ?? group.projectIds[0] ?? '';\n return {\n ...this.#toIntakeIssue(issue, context.siteUrl),\n sourceId: encodeSourceId(group.connectionId, projectId),\n externalId: encodeIssueReference({ connectionId: group.connectionId, issueId: issue.key, projectId }),\n site: new URL(context.siteUrl).host,\n };\n }),\n nextCursor,\n };\n }\n return { issues: [], nextCursor: null };\n }\n\n async #getIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const { context, issue } = (await this.#findIssue(input)) ?? {};\n if (!context || !issue) return null;\n const { comments, total } = await this.#listAllComments(context.api, issue.key);\n return {\n ...this.#toIntakeIssue(issue, context.siteUrl),\n commentCount: total,\n description: issue.fields.description ? adfToText(issue.fields.description) || null : null,\n comments: comments.map(comment => ({\n author: comment.author?.displayName ?? null,\n body: adfToText(comment.body),\n createdAt: comment.created,\n })),\n };\n }\n\n async #listAllComments(api: JiraApiClient, keyOrId: string): Promise<{ comments: JiraComment[]; total: number }> {\n const comments: JiraComment[] = [];\n let total = 0;\n for (let page = 0; page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const result = await api.listComments(keyOrId, { startAt: comments.length });\n comments.push(...result.comments);\n total = result.total;\n if (result.comments.length === 0 || comments.length >= total) break;\n }\n return { comments, total };\n }\n\n async #createComment(input: CreateIntakeCommentInput): Promise<CreatedIntakeComment | null> {\n const resolved = await this.#resolveRequest(input);\n let key = resolved.issueId;\n if (!ISSUE_KEY_PATTERN.test(key)) {\n try {\n key = (await resolved.context.api.getIssue(key)).key;\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n }\n let comment;\n try {\n comment = await resolved.context.api.createComment(key, input.body);\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n return {\n id: comment.id,\n url: `${resolved.context.siteUrl}/browse/${key}?focusedCommentId=${comment.id}`,\n };\n }\n\n async #updateIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const resolved = await this.#resolveRequest(input);\n let issue: JiraIssue;\n try {\n issue = await resolved.context.api.getIssue(resolved.issueId);\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n if (currentStatusMatches(issue, input.state)) return this.#toIntakeIssue(issue, resolved.context.siteUrl);\n const transitions = await resolved.context.api.listTransitions(issue.key);\n const transition = resolveTransition(transitions, input.state);\n if (!transition) return null;\n await resolved.context.api.applyTransition(issue.key, transition.id);\n return this.#toIntakeIssue(await resolved.context.api.getIssue(issue.key), resolved.context.siteUrl);\n }\n\n async #resolveRequest(input: GetIntakeIssueInput): Promise<{ context: JiraConnectionContext; issueId: string }> {\n const issueReference = decodeIssueReference(input.issueId);\n const source = input.sourceId ? decodeSourceId(input.sourceId) : null;\n const connectionId =\n issueReference?.connectionId ?? source?.connectionId ?? connectionIdFromConnection(input.connection);\n const issueId = issueReference?.issueId ?? input.issueId;\n if (connectionId) return { context: await this.#connectionContextById(connectionId), issueId };\n const active = await this.#activeConnections();\n if (active.length !== 1) {\n throw new JiraApiError('Jira issue reference must identify a connection when multiple sites are connected.', 400);\n }\n return { context: await this.#connectionContext(active[0]!), issueId };\n }\n\n /**\n * Locate an issue for a read. A connection-scoped reference resolves\n * directly; a plain issue key (the agent tool's normal input) is looked up\n * across every active connection so multi-site organizations can still\n * fetch by key — the first site that knows the key wins, which is safe\n * because Jira project-key prefixes make cross-site collisions unlikely and\n * the tool is read-only.\n */\n async #findIssue(input: GetIntakeIssueInput): Promise<{ context: JiraConnectionContext; issue: JiraIssue } | null> {\n const issueReference = decodeIssueReference(input.issueId);\n const source = input.sourceId ? decodeSourceId(input.sourceId) : null;\n const connectionId =\n issueReference?.connectionId ?? source?.connectionId ?? connectionIdFromConnection(input.connection);\n const issueId = issueReference?.issueId ?? input.issueId;\n\n const candidates: JiraConnectionContext[] = [];\n if (connectionId) {\n candidates.push(await this.#connectionContextById(connectionId));\n } else {\n const active = await this.#activeConnections();\n if (active.length === 0) {\n throw new JiraApiError('Jira connection is unavailable or requires reauthentication.', 401);\n }\n for (const connection of active) candidates.push(await this.#connectionContext(connection));\n }\n\n let firstError: unknown;\n for (const context of candidates) {\n try {\n return { context, issue: await context.api.getIssue(issueId) };\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) continue;\n firstError ??= error;\n }\n }\n // Not found anywhere. If a site failed for a non-404 reason, surface that\n // failure instead of a silent \"not found\".\n if (firstError !== undefined) throw firstError;\n return null;\n }\n\n #toIntakeIssue(issue: JiraIssue, siteUrl: string): IntakeIssue {\n const { fields } = issue;\n return {\n id: issue.id,\n identifier: issue.key,\n title: fields.summary ?? issue.key,\n url: `${siteUrl}/browse/${issue.key}`,\n author: fields.reporter?.displayName ?? null,\n state: fields.status?.name ?? null,\n stateType: stateTypeFromCategory(fields.status?.statusCategory?.key),\n priority: fields.priority?.name ?? null,\n assignee: fields.assignee?.displayName ?? null,\n source: fields.project?.key ?? null,\n labels: fields.labels ?? [],\n commentCount: null,\n createdAt: fields.created ?? '',\n updatedAt: fields.updated ?? '',\n };\n }\n\n async #activeConnections(): Promise<PlatformIntegrationConnection[]> {\n return (await this.listConnections()).filter(connection => connection.status === 'active');\n }\n\n async #connectionContextById(connectionId: string): Promise<JiraConnectionContext> {\n const connection = (await this.#activeConnections()).find(candidate => candidate.id === connectionId);\n if (!connection) {\n throw new JiraApiError('Jira connection is unavailable or requires reauthentication.', 401);\n }\n return this.#connectionContext(connection);\n }\n\n async #cloudId(connectionId: string): Promise<string> {\n const cached = this.#cloudIdByConnectionId.get(connectionId);\n if (cached) return cached;\n\n const context = await this.#platformClient.request<PlatformConnectionContextResponse>(\n 'GET',\n `/v2/connections/${encodeURIComponent(connectionId)}/context`,\n );\n const cloudId = context.connection_config?.cloudId;\n if (typeof cloudId !== 'string' || !UUID_PATTERN.test(cloudId)) {\n throw new JiraApiError('Platform Jira connection context is missing a valid cloudId.', 502);\n }\n this.#cloudIdByConnectionId.set(connectionId, cloudId);\n return cloudId;\n }\n\n async #connectionContext(connection: PlatformIntegrationConnection): Promise<JiraConnectionContext> {\n const proxyBaseUrl = `${this.#clientConfig.baseUrl.replace(/\\/+$/, '')}/v2/connections/${encodeURIComponent(\n connection.id,\n )}/proxy`;\n const cloudId = await this.#cloudId(connection.id);\n const api = new JiraApiClient({\n baseUrl: `${proxyBaseUrl}/ex/jira/${encodeURIComponent(cloudId)}`,\n accessToken: this.#clientConfig.accessToken,\n ...(this.#clientConfig.projectId ? { projectId: this.#clientConfig.projectId } : {}),\n ...(this.#clientConfig.fetchImpl ? { fetchImpl: this.#clientConfig.fetchImpl } : {}),\n });\n let siteUrl = this.#siteUrlByConnectionId.get(connection.id);\n if (!siteUrl) {\n siteUrl = connection.accountLabel\n ? normalizeSiteUrl(connection.accountLabel)\n : normalizeSiteUrl((await api.getServerInfo()).baseUrl);\n this.#siteUrlByConnectionId.set(connection.id, siteUrl);\n }\n return { connection, api, siteUrl };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!jiraReconciliationEnabled()) return [];\n const reconcile = attachJiraIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = jiraReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildPlatformJiraRoutes({\n jira: this,\n auth: ctx.auth,\n intake: ctx.storage.intake,\n appDbConfigured: Boolean(ctx.factoryStorage),\n ingestFactoryIssues: attachJiraRules(this, ctx),\n });\n }\n\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildPlatformJiraAgentTools({ requestContext: args.requestContext, jira: this });\n }\n\n diagnostics(): Record<string, unknown> {\n return { configured: true, mode: 'platform', endpointHost: this.#endpointHost };\n }\n}\n\nfunction jiraConnection(connectionId: string): IntegrationConnection {\n return { type: 'oauth', accessToken: `${JIRA_CONNECTION_TOKEN_PREFIX}${connectionId}` };\n}\n\nfunction connectionIdFromConnection(connection: IntegrationConnection): string | null {\n if (connection.type !== 'oauth' || !connection.accessToken.startsWith(JIRA_CONNECTION_TOKEN_PREFIX)) return null;\n return connection.accessToken.slice(JIRA_CONNECTION_TOKEN_PREFIX.length) || null;\n}\n\nexport function encodeSourceId(connectionId: string, projectId: string): string {\n return `${JIRA_SOURCE_PREFIX}${Buffer.from(JSON.stringify({ connectionId, projectId })).toString('base64url')}`;\n}\n\nexport function decodeSourceId(sourceId: string): { connectionId: string; projectId: string } | null {\n if (!sourceId.startsWith(JIRA_SOURCE_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(sourceId.slice(JIRA_SOURCE_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n return typeof parsed.connectionId === 'string' && typeof parsed.projectId === 'string'\n ? { connectionId: parsed.connectionId, projectId: parsed.projectId }\n : null;\n } catch {\n return null;\n }\n}\n\nexport function encodeIssueReference(reference: JiraIssueReference): string {\n return `${JIRA_ISSUE_REF_PREFIX}${Buffer.from(JSON.stringify(reference)).toString('base64url')}`;\n}\n\nexport function decodeIssueReference(value: string): JiraIssueReference | null {\n if (!value.startsWith(JIRA_ISSUE_REF_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(value.slice(JIRA_ISSUE_REF_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n if (typeof parsed.connectionId !== 'string' || typeof parsed.issueId !== 'string') return null;\n return {\n connectionId: parsed.connectionId,\n issueId: parsed.issueId,\n ...(typeof parsed.projectId === 'string' ? { projectId: parsed.projectId } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction groupSources(sourceIds: string[]): Array<{ connectionId: string; projectIds: string[] }> {\n const groups = new Map<string, Set<string>>();\n for (const sourceId of sourceIds) {\n const source = decodeSourceId(sourceId);\n if (!source) continue;\n const projectIds = groups.get(source.connectionId) ?? new Set<string>();\n projectIds.add(source.projectId);\n groups.set(source.connectionId, projectIds);\n }\n return [...groups.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([connectionId, projectIds]) => ({ connectionId, projectIds: [...projectIds].sort() }));\n}\n\nfunction encodePageCursor(cursor: JiraPageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction decodePageCursor(cursor: string | undefined): JiraPageCursor {\n if (!cursor) return { group: 0 };\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Record<string, unknown>;\n if (!Number.isInteger(parsed.group) || (parsed.group as number) < 0) throw new Error();\n return {\n group: parsed.group as number,\n ...(typeof parsed.jira === 'string' ? { jira: parsed.jira } : {}),\n };\n } catch {\n throw new JiraApiError('Jira pagination cursor is invalid.', 400);\n }\n}\n\nfunction normalizeSiteUrl(value: string): string {\n const candidate = /^https?:\\/\\//i.test(value) ? value : `https://${value}`;\n // `accountLabel` is free-form Platform data — an unparsable value must\n // surface as the classified 502 the routes already handle, not a raw\n // TypeError escaping the connection context.\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new JiraApiError('Jira site URL is invalid.', 502);\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new JiraApiError('Jira site URL is invalid.', 502);\n return url.origin;\n}\n\nfunction buildIntakeJql(projectIds?: string[], labels?: string[]): string | null {\n const clauses: string[] = [];\n const safeProjects = (projectIds ?? [])\n .map(id => id.trim())\n .filter(id => /^[A-Za-z0-9_]+$/.test(id))\n .map(id => (/^\\d+$/.test(id) ? id : `\"${id}\"`));\n if (projectIds && safeProjects.length === 0) return null;\n if (safeProjects.length > 0) clauses.push(`project IN (${safeProjects.join(', ')})`);\n clauses.push('statusCategory != Done');\n const safeLabels = [...new Set((labels ?? []).map(label => label.trim().replace(/[\"\\\\]/g, '')).filter(Boolean))];\n if (safeLabels.length > 0) clauses.push(`labels IN (${safeLabels.map(label => `\"${label}\"`).join(', ')})`);\n return `${clauses.join(' AND ')} ORDER BY updated DESC`;\n}\n\nfunction currentStatusMatches(issue: JiraIssue, target: UpdateIntakeIssueInput['state']): boolean {\n const status = issue.fields.status;\n if (!status) return false;\n if (target.kind === 'byName') return status.name.toLowerCase() === target.name.toLowerCase();\n if (target.stateType === 'canceled') return status.statusCategory?.key === 'done' && /cancel/i.test(status.name);\n return status.statusCategory?.key === STATE_TYPE_TO_CATEGORY[target.stateType];\n}\n\nfunction resolveTransition(\n transitions: JiraTransition[],\n target: UpdateIntakeIssueInput['state'],\n): JiraTransition | null {\n if (target.kind === 'byName') {\n const wanted = target.name.toLowerCase();\n return transitions.find(transition => transition.to?.name.toLowerCase() === wanted) ?? null;\n }\n if (target.stateType === 'canceled') {\n return (\n transitions.find(\n transition => transition.to?.statusCategory?.key === 'done' && /cancel/i.test(transition.to.name),\n ) ?? null\n );\n }\n const category = STATE_TYPE_TO_CATEGORY[target.stateType];\n return transitions.find(transition => transition.to?.statusCategory?.key === category) ?? null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../../../../src/integrations/platform/jira/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n CreatedIntakeComment,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n IntakeItemPage,\n IntakeSource,\n ListIntakeIssuesInput,\n ListIntakeItemsInput,\n ResolveIntakeDispatchInput,\n ResolvedIntakeDispatch,\n UpdateIntakeIssueInput,\n} from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { IssueReconcileWorker } from '../../issue-reconcile-worker.js';\nimport { adfToText } from '../../jira/adf.js';\nimport type { JiraComment, JiraIssue, JiraTransition } from '../../jira/api.js';\nimport { JiraApiClient, JiraApiError } from '../../jira/api.js';\nimport type { JiraEventRules, JiraRuleOverrides } from '../../jira/default-rules.js';\nimport { resolveJiraRules } from '../../jira/default-rules.js';\nimport { buildIntakeJql, unfilteredJiraScopes } from '../../jira/intake-jql.js';\nimport type { JiraIntakeScope } from '../../jira/intake-jql.js';\nimport { attachJiraIssueReconciler } from '../../jira/issue-reconciler.js';\nimport { jiraReconciliationEnabled, jiraReconciliationInterval } from '../../jira/reconciliation-config.js';\nimport { attachJiraRules } from '../../jira/rules.js';\nimport { logPlatformInfo, PlatformApiClient, platformApiClientConfigFromEnv } from '../api-client.js';\nimport type { PlatformApiClientConfig } from '../api-client.js';\nimport { buildPlatformJiraAgentTools } from './agent-tools.js';\nimport { buildPlatformJiraRoutes } from './routes.js';\n\ninterface PlatformIntegrationConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\ninterface JiraConnectionContext {\n connection: PlatformIntegrationConnection;\n api: JiraApiClient;\n siteUrl: string;\n}\n\ninterface PlatformConnectionContextResponse {\n connection_config: Record<string, unknown> | null;\n metadata: Record<string, unknown> | null;\n}\n\ninterface JiraIssueReference {\n connectionId: string;\n issueId: string;\n projectId?: string;\n}\n\ninterface JiraPageCursor {\n group: number;\n jira?: string;\n}\n\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Hard stop for project pagination so a misbehaving `isLast` can't loop forever. */\nconst PROJECT_SEARCH_MAX_PAGES = 50;\nconst ISSUE_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*-\\d+$/;\nconst PLATFORM_JIRA_PROVIDER_CONFIG_KEY = 'jira';\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst JIRA_CONNECTION_TOKEN_PREFIX = 'jira-connection:';\nconst JIRA_ISSUE_REF_PREFIX = 'jira-issue:';\nconst JIRA_SOURCE_PREFIX = 'jira-project:';\n\nconst STATE_TYPE_TO_CATEGORY: Record<'unstarted' | 'started' | 'completed', string> = {\n unstarted: 'new',\n started: 'indeterminate',\n completed: 'done',\n};\n\nfunction stateTypeFromCategory(key: string | undefined): string | null {\n switch (key) {\n case 'new':\n return 'unstarted';\n case 'indeterminate':\n return 'started';\n case 'done':\n return 'completed';\n default:\n return null;\n }\n}\n\nexport interface PlatformJiraIntegrationConfig {\n clientConfig?: PlatformApiClientConfig;\n /** Per-event replacements; omitted events retain defaults, null disables. */\n rules?: JiraRuleOverrides;\n}\n\nexport class PlatformJiraIntegration implements FactoryIntegration {\n readonly id = 'jira';\n readonly #clientConfig: PlatformApiClientConfig;\n readonly #platformClient: PlatformApiClient;\n readonly #endpointHost: string;\n readonly #cloudIdByConnectionId = new Map<string, string>();\n readonly #siteUrlByConnectionId = new Map<string, string>();\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n readonly #rules: JiraEventRules;\n\n get rules(): JiraEventRules {\n return this.#rules;\n }\n\n constructor(config: PlatformJiraIntegrationConfig = {}) {\n this.#rules = resolveJiraRules(config.rules);\n this.#clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#platformClient = new PlatformApiClient(this.#clientConfig);\n this.#endpointHost = new URL(this.#clientConfig.baseUrl).host;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Jira integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects)\n throw new Error('PlatformJiraIntegration is not initialized — the factory binds storage during prepare().');\n return this.#projects;\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n const orgId = project?.orgId ?? null;\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n } catch {\n return null;\n }\n }\n\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#cloudIdByConnectionId.clear();\n this.#siteUrlByConnectionId.clear();\n }\n\n async listConnections(): Promise<PlatformIntegrationConnection[]> {\n const providerKey = encodeURIComponent(PLATFORM_JIRA_PROVIDER_CONFIG_KEY);\n const result = await this.#platformClient.request<{ connections: PlatformIntegrationConnection[] }>(\n 'GET',\n `/v2/connections?providerKey=${providerKey}`,\n );\n return result.connections;\n }\n\n async hasActiveConnections(): Promise<boolean> {\n return (await this.#activeConnections()).length > 0;\n }\n\n readonly intake: Intake = {\n bindingLabelFilters: true,\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: () => this.#listSources(),\n listItems: input => this.#listItems(input),\n listIssues: input => this.#listIssues(input),\n getIssue: input => this.#getIssue(input),\n createComment: input => this.#createComment(input),\n updateIssue: input => this.#updateIssue(input),\n };\n\n async #resolveIntakeDispatch({ externalSource }: ResolveIntakeDispatchInput): Promise<ResolvedIntakeDispatch | null> {\n if (externalSource.type !== 'issue') return null;\n const reference = decodeIssueReference(externalSource.externalId);\n if (!reference) return null;\n return {\n connection: jiraConnection(reference.connectionId),\n ...(reference.projectId ? { sourceId: encodeSourceId(reference.connectionId, reference.projectId) } : {}),\n issueId: reference.issueId,\n };\n }\n\n async #listSources(): Promise<IntakeSource[]> {\n const sources: IntakeSource[] = [];\n for (const connection of await this.#activeConnections()) {\n const context = await this.#connectionContext(connection);\n let startAt = 0;\n for (let pageCount = 0; pageCount < PROJECT_SEARCH_MAX_PAGES; pageCount++) {\n const page = await context.api.listProjects({ startAt });\n for (const project of page.values) {\n sources.push({\n id: encodeSourceId(connection.id, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n key: project.key,\n connectionId: connection.id,\n site: new URL(context.siteUrl).host,\n },\n });\n }\n if (page.isLast || page.values.length === 0) break;\n startAt += page.values.length;\n }\n }\n return sources;\n }\n\n async #listItems({ sourceIds, cursor }: ListIntakeItemsInput): Promise<IntakeItemPage> {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const page = await this.listActiveIssues(cursor, unfilteredJiraScopes(sourceIds));\n return {\n items: page.issues.map(issue => ({\n source: { type: 'issue', externalId: issue.externalId, url: issue.url },\n sourceId: issue.sourceId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state ?? undefined,\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n issueRef: issue.externalId,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priority,\n project: issue.source,\n site: issue.site,\n assignee: issue.assignee,\n assignees: issue.assignees ?? [],\n creator: issue.author,\n author: issue.author,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n },\n })),\n nextCursor: page.nextCursor,\n };\n }\n\n async #listIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const page = await this.listActiveIssues(input.cursor, unfilteredJiraScopes(input.sourceIds ?? []), input.labels);\n return { issues: page.issues, nextCursor: page.nextCursor };\n }\n\n /**\n * One page of active issues across connections. Each scope's `projectId` is\n * an encoded Platform source id; sources are grouped per connection and each\n * group runs one JQL built from only that connection's projects.\n */\n async listActiveIssues(\n cursor?: string,\n scopes: JiraIntakeScope[] = [],\n labels?: string[],\n ): Promise<{\n issues: Array<IntakeIssue & { sourceId: string; externalId: string; site: string }>;\n nextCursor: string | null;\n }> {\n const grouped = groupSources(scopes);\n if (grouped.length === 0) return { issues: [], nextCursor: null };\n const decodedCursor = decodePageCursor(cursor);\n if (decodedCursor.group >= grouped.length) return { issues: [], nextCursor: null };\n\n for (let groupIndex = decodedCursor.group; groupIndex < grouped.length; groupIndex++) {\n const group = grouped[groupIndex]!;\n const context = await this.#connectionContextById(group.connectionId);\n const jql = buildIntakeJql(group.scopes, labels);\n if (jql === null) continue;\n const page = await context.api.searchIssues({\n jql,\n ...(groupIndex === decodedCursor.group && decodedCursor.jira ? { nextPageToken: decodedCursor.jira } : {}),\n });\n const nextCursor = page.nextPageToken\n ? encodePageCursor({ group: groupIndex, jira: page.nextPageToken })\n : groupIndex + 1 < grouped.length\n ? encodePageCursor({ group: groupIndex + 1 })\n : null;\n return {\n issues: page.issues.map(issue => {\n const projectId = issue.fields.project?.id ?? group.scopes[0]?.projectId ?? '';\n return {\n ...this.#toIntakeIssue(issue, context.siteUrl),\n sourceId: encodeSourceId(group.connectionId, projectId),\n externalId: encodeIssueReference({ connectionId: group.connectionId, issueId: issue.key, projectId }),\n site: new URL(context.siteUrl).host,\n };\n }),\n nextCursor,\n };\n }\n return { issues: [], nextCursor: null };\n }\n\n async #getIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const { context, issue } = (await this.#findIssue(input)) ?? {};\n if (!context || !issue) return null;\n const { comments, total } = await this.#listAllComments(context.api, issue.key);\n return {\n ...this.#toIntakeIssue(issue, context.siteUrl),\n commentCount: total,\n description: issue.fields.description ? adfToText(issue.fields.description) || null : null,\n comments: comments.map(comment => ({\n author: comment.author?.displayName ?? null,\n body: adfToText(comment.body),\n createdAt: comment.created,\n })),\n };\n }\n\n async #listAllComments(api: JiraApiClient, keyOrId: string): Promise<{ comments: JiraComment[]; total: number }> {\n const comments: JiraComment[] = [];\n let total = 0;\n for (let page = 0; page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const result = await api.listComments(keyOrId, { startAt: comments.length });\n comments.push(...result.comments);\n total = result.total;\n if (result.comments.length === 0 || comments.length >= total) break;\n }\n return { comments, total };\n }\n\n async #createComment(input: CreateIntakeCommentInput): Promise<CreatedIntakeComment | null> {\n const resolved = await this.#resolveRequest(input);\n let key = resolved.issueId;\n if (!ISSUE_KEY_PATTERN.test(key)) {\n try {\n key = (await resolved.context.api.getIssue(key)).key;\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n }\n let comment;\n try {\n comment = await resolved.context.api.createComment(key, input.body);\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n return {\n id: comment.id,\n url: `${resolved.context.siteUrl}/browse/${key}?focusedCommentId=${comment.id}`,\n };\n }\n\n async #updateIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const resolved = await this.#resolveRequest(input);\n let issue: JiraIssue;\n try {\n issue = await resolved.context.api.getIssue(resolved.issueId);\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) return null;\n throw error;\n }\n if (currentStatusMatches(issue, input.state)) return this.#toIntakeIssue(issue, resolved.context.siteUrl);\n const transitions = await resolved.context.api.listTransitions(issue.key);\n const transition = resolveTransition(transitions, input.state);\n if (!transition) return null;\n await resolved.context.api.applyTransition(issue.key, transition.id);\n return this.#toIntakeIssue(await resolved.context.api.getIssue(issue.key), resolved.context.siteUrl);\n }\n\n async #resolveRequest(input: GetIntakeIssueInput): Promise<{ context: JiraConnectionContext; issueId: string }> {\n const issueReference = decodeIssueReference(input.issueId);\n const source = input.sourceId ? decodeSourceId(input.sourceId) : null;\n const connectionId =\n issueReference?.connectionId ?? source?.connectionId ?? connectionIdFromConnection(input.connection);\n const issueId = issueReference?.issueId ?? input.issueId;\n if (connectionId) return { context: await this.#connectionContextById(connectionId), issueId };\n const active = await this.#activeConnections();\n if (active.length !== 1) {\n throw new JiraApiError('Jira issue reference must identify a connection when multiple sites are connected.', 400);\n }\n return { context: await this.#connectionContext(active[0]!), issueId };\n }\n\n /**\n * Locate an issue for a read. A connection-scoped reference resolves\n * directly; a plain issue key (the agent tool's normal input) is looked up\n * across every active connection so multi-site organizations can still\n * fetch by key — the first site that knows the key wins, which is safe\n * because Jira project-key prefixes make cross-site collisions unlikely and\n * the tool is read-only.\n */\n async #findIssue(input: GetIntakeIssueInput): Promise<{ context: JiraConnectionContext; issue: JiraIssue } | null> {\n const issueReference = decodeIssueReference(input.issueId);\n const source = input.sourceId ? decodeSourceId(input.sourceId) : null;\n const connectionId =\n issueReference?.connectionId ?? source?.connectionId ?? connectionIdFromConnection(input.connection);\n const issueId = issueReference?.issueId ?? input.issueId;\n\n const candidates: JiraConnectionContext[] = [];\n if (connectionId) {\n candidates.push(await this.#connectionContextById(connectionId));\n } else {\n const active = await this.#activeConnections();\n if (active.length === 0) {\n throw new JiraApiError('Jira connection is unavailable or requires reauthentication.', 401);\n }\n for (const connection of active) candidates.push(await this.#connectionContext(connection));\n }\n\n let firstError: unknown;\n for (const context of candidates) {\n try {\n return { context, issue: await context.api.getIssue(issueId) };\n } catch (error) {\n if (error instanceof JiraApiError && error.status === 404) continue;\n firstError ??= error;\n }\n }\n // Not found anywhere. If a site failed for a non-404 reason, surface that\n // failure instead of a silent \"not found\".\n if (firstError !== undefined) throw firstError;\n return null;\n }\n\n #toIntakeIssue(issue: JiraIssue, siteUrl: string): IntakeIssue {\n const { fields } = issue;\n return {\n id: issue.id,\n identifier: issue.key,\n title: fields.summary ?? issue.key,\n url: `${siteUrl}/browse/${issue.key}`,\n author: fields.reporter?.displayName ?? null,\n state: fields.status?.name ?? null,\n stateType: stateTypeFromCategory(fields.status?.statusCategory?.key),\n priority: fields.priority?.name ?? null,\n assignee: fields.assignee?.displayName ?? null,\n source: fields.project?.key ?? null,\n labels: fields.labels ?? [],\n commentCount: null,\n createdAt: fields.created ?? '',\n updatedAt: fields.updated ?? '',\n };\n }\n\n async #activeConnections(): Promise<PlatformIntegrationConnection[]> {\n return (await this.listConnections()).filter(connection => connection.status === 'active');\n }\n\n async #connectionContextById(connectionId: string): Promise<JiraConnectionContext> {\n const connection = (await this.#activeConnections()).find(candidate => candidate.id === connectionId);\n if (!connection) {\n throw new JiraApiError('Jira connection is unavailable or requires reauthentication.', 401);\n }\n return this.#connectionContext(connection);\n }\n\n async #cloudId(connectionId: string): Promise<string> {\n const cached = this.#cloudIdByConnectionId.get(connectionId);\n if (cached) return cached;\n\n const context = await this.#platformClient.request<PlatformConnectionContextResponse>(\n 'GET',\n `/v2/connections/${encodeURIComponent(connectionId)}/context`,\n );\n const cloudId = context.connection_config?.cloudId;\n if (typeof cloudId !== 'string' || !UUID_PATTERN.test(cloudId)) {\n throw new JiraApiError('Platform Jira connection context is missing a valid cloudId.', 502);\n }\n this.#cloudIdByConnectionId.set(connectionId, cloudId);\n return cloudId;\n }\n\n async #connectionContext(connection: PlatformIntegrationConnection): Promise<JiraConnectionContext> {\n const proxyBaseUrl = `${this.#clientConfig.baseUrl.replace(/\\/+$/, '')}/v2/connections/${encodeURIComponent(\n connection.id,\n )}/proxy`;\n const cloudId = await this.#cloudId(connection.id);\n const api = new JiraApiClient({\n baseUrl: `${proxyBaseUrl}/ex/jira/${encodeURIComponent(cloudId)}`,\n accessToken: this.#clientConfig.accessToken,\n ...(this.#clientConfig.projectId ? { projectId: this.#clientConfig.projectId } : {}),\n ...(this.#clientConfig.fetchImpl ? { fetchImpl: this.#clientConfig.fetchImpl } : {}),\n });\n let siteUrl = this.#siteUrlByConnectionId.get(connection.id);\n if (!siteUrl) {\n siteUrl = connection.accountLabel\n ? normalizeSiteUrl(connection.accountLabel)\n : normalizeSiteUrl((await api.getServerInfo()).baseUrl);\n this.#siteUrlByConnectionId.set(connection.id, siteUrl);\n }\n return { connection, api, siteUrl };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!jiraReconciliationEnabled()) return [];\n const reconcile = attachJiraIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = jiraReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildPlatformJiraRoutes({\n jira: this,\n auth: ctx.auth,\n intake: ctx.storage.intake,\n workItems: ctx.runtime?.workItems,\n appDbConfigured: Boolean(ctx.factoryStorage),\n ingestFactoryIssues: attachJiraRules(this, ctx),\n });\n }\n\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildPlatformJiraAgentTools({ requestContext: args.requestContext, jira: this });\n }\n\n diagnostics(): Record<string, unknown> {\n return { configured: true, mode: 'platform', endpointHost: this.#endpointHost };\n }\n}\n\nfunction jiraConnection(connectionId: string): IntegrationConnection {\n return { type: 'oauth', accessToken: `${JIRA_CONNECTION_TOKEN_PREFIX}${connectionId}` };\n}\n\nfunction connectionIdFromConnection(connection: IntegrationConnection): string | null {\n if (connection.type !== 'oauth' || !connection.accessToken.startsWith(JIRA_CONNECTION_TOKEN_PREFIX)) return null;\n return connection.accessToken.slice(JIRA_CONNECTION_TOKEN_PREFIX.length) || null;\n}\n\nexport function encodeSourceId(connectionId: string, projectId: string): string {\n return `${JIRA_SOURCE_PREFIX}${Buffer.from(JSON.stringify({ connectionId, projectId })).toString('base64url')}`;\n}\n\nexport function decodeSourceId(sourceId: string): { connectionId: string; projectId: string } | null {\n if (!sourceId.startsWith(JIRA_SOURCE_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(sourceId.slice(JIRA_SOURCE_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n return typeof parsed.connectionId === 'string' && typeof parsed.projectId === 'string'\n ? { connectionId: parsed.connectionId, projectId: parsed.projectId }\n : null;\n } catch {\n return null;\n }\n}\n\nexport function encodeIssueReference(reference: JiraIssueReference): string {\n return `${JIRA_ISSUE_REF_PREFIX}${Buffer.from(JSON.stringify(reference)).toString('base64url')}`;\n}\n\nexport function decodeIssueReference(value: string): JiraIssueReference | null {\n if (!value.startsWith(JIRA_ISSUE_REF_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(value.slice(JIRA_ISSUE_REF_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n if (typeof parsed.connectionId !== 'string' || typeof parsed.issueId !== 'string') return null;\n return {\n connectionId: parsed.connectionId,\n issueId: parsed.issueId,\n ...(typeof parsed.projectId === 'string' ? { projectId: parsed.projectId } : {}),\n };\n } catch {\n return null;\n }\n}\n\n/** A Platform source scope: `projectId` is the encoded source id, decoded per connection here. */\nfunction groupSources(scopes: JiraIntakeScope[]): Array<{ connectionId: string; scopes: JiraIntakeScope[] }> {\n const groups = new Map<string, Map<string, JiraIntakeScope>>();\n for (const scope of scopes) {\n const source = decodeSourceId(scope.projectId);\n if (!source) continue;\n const projects = groups.get(source.connectionId) ?? new Map<string, JiraIntakeScope>();\n if (!projects.has(source.projectId)) projects.set(source.projectId, { ...scope, projectId: source.projectId });\n groups.set(source.connectionId, projects);\n }\n return [...groups.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([connectionId, projects]) => ({\n connectionId,\n scopes: [...projects.values()].sort((left, right) => left.projectId.localeCompare(right.projectId)),\n }));\n}\n\nfunction encodePageCursor(cursor: JiraPageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction decodePageCursor(cursor: string | undefined): JiraPageCursor {\n if (!cursor) return { group: 0 };\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Record<string, unknown>;\n if (!Number.isInteger(parsed.group) || (parsed.group as number) < 0) throw new Error();\n return {\n group: parsed.group as number,\n ...(typeof parsed.jira === 'string' ? { jira: parsed.jira } : {}),\n };\n } catch {\n throw new JiraApiError('Jira pagination cursor is invalid.', 400);\n }\n}\n\nfunction normalizeSiteUrl(value: string): string {\n const candidate = /^https?:\\/\\//i.test(value) ? value : `https://${value}`;\n // `accountLabel` is free-form Platform data — an unparsable value must\n // surface as the classified 502 the routes already handle, not a raw\n // TypeError escaping the connection context.\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new JiraApiError('Jira site URL is invalid.', 502);\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new JiraApiError('Jira site URL is invalid.', 502);\n return url.origin;\n}\n\nfunction currentStatusMatches(issue: JiraIssue, target: UpdateIntakeIssueInput['state']): boolean {\n const status = issue.fields.status;\n if (!status) return false;\n if (target.kind === 'byName') return status.name.toLowerCase() === target.name.toLowerCase();\n if (target.stateType === 'canceled') return status.statusCategory?.key === 'done' && /cancel/i.test(status.name);\n return status.statusCategory?.key === STATE_TYPE_TO_CATEGORY[target.stateType];\n}\n\nfunction resolveTransition(\n transitions: JiraTransition[],\n target: UpdateIntakeIssueInput['state'],\n): JiraTransition | null {\n if (target.kind === 'byName') {\n const wanted = target.name.toLowerCase();\n return transitions.find(transition => transition.to?.name.toLowerCase() === wanted) ?? null;\n }\n if (target.stateType === 'canceled') {\n return (\n transitions.find(\n transition => transition.to?.statusCategory?.key === 'done' && /cancel/i.test(transition.to.name),\n ) ?? null\n );\n }\n const category = STATE_TYPE_TO_CATEGORY[target.stateType];\n return transitions.find(transition => transition.to?.statusCategory?.key === category) ?? null;\n}\n"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type { ApiRoute } from '@mastra/core/server';
|
|
15
15
|
import type { RouteAuth } from '../../../routes/route.js';
|
|
16
16
|
import type { IntakeStorage } from '../../../storage/domains/intake/base.js';
|
|
17
|
+
import type { WorkItemsStorage } from '../../../storage/domains/work-items/base.js';
|
|
17
18
|
import type { JiraRulesIngress } from '../../jira/rules.js';
|
|
18
19
|
import type { PlatformJiraIntegration } from './integration.js';
|
|
19
20
|
/**
|
|
@@ -46,6 +47,8 @@ export interface MountJiraRoutesOptions {
|
|
|
46
47
|
* the same automatic intake Linear has.
|
|
47
48
|
*/
|
|
48
49
|
ingestFactoryIssues?: (input: JiraRulesIngress) => Promise<unknown>;
|
|
50
|
+
/** Filed cards, so a Factory keeps reading issues it filed after a label filter moves them to another slice. */
|
|
51
|
+
workItems?: Pick<WorkItemsStorage, 'getByClaimKey' | 'listBySource'>;
|
|
49
52
|
}
|
|
50
53
|
/**
|
|
51
54
|
* Build the Jira routes as Mastra `apiRoutes`. When the feature is disabled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/jira/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/jira/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAKpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAYhE;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,OAAO,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,uBAAuB,CAAC;IAC/B,2FAA2F;IAC3F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,iFAAiF;IACjF,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,gHAAgH;IAChH,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,GAAG,cAAc,CAAC,CAAC;CACtE;AAqDD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB,GAAG,QAAQ,EAAE,CAoPnF"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { JiraApiError } from "../../jira/api.js";
|
|
2
|
+
import { jiraIssueFiledIn } from "../../jira/claim.js";
|
|
3
|
+
import { unfilteredJiraScopes } from "../../jira/intake-jql.js";
|
|
4
|
+
import { scopeJiraSourcesToProject } from "../../jira/binding-scope.js";
|
|
2
5
|
import { registerApiRoute } from "@mastra/core/server";
|
|
3
6
|
// src/integrations/platform/jira/routes.ts
|
|
4
7
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -8,26 +11,6 @@ function loose(c) {
|
|
|
8
11
|
return c;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
|
-
* Narrow the caller's selected Jira projects to the ones that feed this
|
|
12
|
-
* Factory project.
|
|
13
|
-
*
|
|
14
|
-
* A Jira issue carries no Factory project of its own, so without a binding
|
|
15
|
-
* every board view would show every selected project's issues on whichever
|
|
16
|
-
* Factory happened to be on screen. Only sources explicitly bound to a board
|
|
17
|
-
* of this Factory pass — nothing is routed implicitly. Mirrors the Linear
|
|
18
|
-
* routes' scoping semantics locally — the generic binding storage is
|
|
19
|
-
* provider-neutral.
|
|
20
|
-
*/
|
|
21
|
-
async function scopeSourceIdsToProject({ intake, orgId, factoryProjectId, selectedIds }) {
|
|
22
|
-
const selected = new Set(selectedIds);
|
|
23
|
-
const intakeBoards = {};
|
|
24
|
-
for (const binding of await intake.listBindings({
|
|
25
|
-
orgId,
|
|
26
|
-
integrationId: "jira"
|
|
27
|
-
})) if (binding.factoryProjectId === factoryProjectId && binding.board && selected.has(binding.sourceId)) intakeBoards[binding.sourceId] = binding.board;
|
|
28
|
-
return intakeBoards;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
14
|
* Resolve the org-scoped tenant for a Jira request. Intake selections are
|
|
32
15
|
* org-owned, so it requires both a signed-in user and an organization — same
|
|
33
16
|
* tenancy rules as the Linear routes.
|
|
@@ -72,7 +55,7 @@ function jiraFetchError(c, err) {
|
|
|
72
55
|
*/
|
|
73
56
|
function buildPlatformJiraRoutes(options) {
|
|
74
57
|
const routes = [];
|
|
75
|
-
const { jira, auth, intake } = options;
|
|
58
|
+
const { jira, auth, intake, workItems } = options;
|
|
76
59
|
const enabled = Boolean(jira) && auth.enabled();
|
|
77
60
|
const diagnostics = () => ({
|
|
78
61
|
jiraConfigured: Boolean(jira),
|
|
@@ -168,19 +151,20 @@ function buildPlatformJiraRoutes(options) {
|
|
|
168
151
|
message: "Jira intake is turned off in Settings."
|
|
169
152
|
}, 404);
|
|
170
153
|
const selectedIds = selection.sourceIds ?? [];
|
|
171
|
-
const
|
|
154
|
+
const projectScope = factoryProjectId ? await scopeJiraSourcesToProject({
|
|
172
155
|
intake,
|
|
173
156
|
orgId: resolved.tenant.orgId,
|
|
174
157
|
factoryProjectId,
|
|
175
158
|
selectedIds
|
|
176
159
|
}) : null;
|
|
177
|
-
const
|
|
178
|
-
|
|
160
|
+
const intakeBoards = projectScope?.intakeBoards ?? null;
|
|
161
|
+
const scopes = projectScope?.scopes ?? unfilteredJiraScopes(selectedIds);
|
|
162
|
+
if (scopes.length === 0) return c.json({
|
|
179
163
|
issues: [],
|
|
180
164
|
nextCursor: null
|
|
181
165
|
});
|
|
182
166
|
try {
|
|
183
|
-
const { issues, nextCursor } = await jira.listActiveIssues(after,
|
|
167
|
+
const { issues, nextCursor } = await jira.listActiveIssues(after, scopes);
|
|
184
168
|
const issuePayload = issues.map((issue) => ({
|
|
185
169
|
id: issue.externalId,
|
|
186
170
|
identifier: issue.identifier,
|
|
@@ -238,7 +222,7 @@ function buildPlatformJiraRoutes(options) {
|
|
|
238
222
|
error: "jira_intake_disabled",
|
|
239
223
|
message: "Jira intake is turned off in Settings."
|
|
240
224
|
}, 404);
|
|
241
|
-
const
|
|
225
|
+
const projectScope = await scopeJiraSourcesToProject({
|
|
242
226
|
intake,
|
|
243
227
|
orgId: resolved.tenant.orgId,
|
|
244
228
|
factoryProjectId,
|
|
@@ -251,10 +235,15 @@ function buildPlatformJiraRoutes(options) {
|
|
|
251
235
|
externalId: issueRef
|
|
252
236
|
}
|
|
253
237
|
});
|
|
254
|
-
|
|
238
|
+
const sourceId = dispatch?.sourceId;
|
|
239
|
+
if (!dispatch || !sourceId || !(sourceId in projectScope.intakeBoards)) return c.json({ error: "issue_not_found" }, 404);
|
|
255
240
|
try {
|
|
256
241
|
const issue = await jira.intake.getIssue(dispatch);
|
|
257
|
-
if (!issue || issue.identifier.toUpperCase() !== identifier.toUpperCase()
|
|
242
|
+
if (!issue || issue.identifier.toUpperCase() !== identifier.toUpperCase() || !(projectScope.takes(sourceId, issue.labels) || await jiraIssueFiledIn(workItems, {
|
|
243
|
+
orgId: resolved.tenant.orgId,
|
|
244
|
+
factoryProjectId,
|
|
245
|
+
issueRef
|
|
246
|
+
}))) return c.json({ error: "issue_not_found" }, 404);
|
|
258
247
|
return c.json({
|
|
259
248
|
identifier: issue.identifier,
|
|
260
249
|
title: issue.title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../../../src/integrations/platform/jira/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for the Jira intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the host auth gate.\n * Jira connections are owned by the caller's Mastra Platform organization;\n * provider requests stay server-side and flow through the integrations v2\n * proxy. Every route re-resolves the authenticated user and scopes intake\n * selections by the caller's org.\n *\n * When the feature is disabled (no auth, or no intake storage),\n * `buildPlatformJiraRoutes` returns only `GET /web/jira/status`, which reports\n * `enabled:false` so the SPA can cleanly hide all Jira UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { IntakeStorage } from '../../../storage/domains/intake/base.js';\nimport { JiraApiError } from '../../jira/api.js';\nimport type { JiraRulesIngress } from '../../jira/rules.js';\nimport type { PlatformJiraIntegration } from './integration.js';\n\ntype RouteContext = Context;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst ISSUE_KEY_RE = /^[A-Za-z][A-Za-z0-9_]*-\\d+$/;\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): RouteContext {\n return c as RouteContext;\n}\n\n/**\n * Non-secret diagnostic snapshot of every Jira feature gate, mirroring the\n * Linear diagnostics shape. Only booleans — never values.\n */\nexport interface JiraFeatureDiagnostics {\n jiraConfigured: boolean;\n factoryAuthEnabled: boolean;\n appDbConfigured: boolean;\n}\n\nexport interface MountJiraRoutesOptions {\n /**\n * The integration instance providing REST access. Required for everything\n * beyond the disabled `status` route.\n */\n jira?: PlatformJiraIntegration;\n /** Host auth seam. Intake selections are org-owned, so the feature is inert without it. */\n auth: RouteAuth;\n /**\n * Cross-integration intake selection domain. Required for the issues route's\n * project filter; when absent, only the disabled `status` route is served.\n */\n intake?: IntakeStorage;\n /** Whether the host configured the application database backing intake state. */\n appDbConfigured: boolean;\n /**\n * Factory rules ingress for observed issues. When present, a board-scoped\n * issue listing also materializes new cards through the Jira event rules —\n * the same automatic intake Linear has.\n */\n ingestFactoryIssues?: (input: JiraRulesIngress) => Promise<unknown>;\n}\n\n/**\n * Narrow the caller's selected Jira projects to the ones that feed this\n * Factory project.\n *\n * A Jira issue carries no Factory project of its own, so without a binding\n * every board view would show every selected project's issues on whichever\n * Factory happened to be on screen. Only sources explicitly bound to a board\n * of this Factory pass — nothing is routed implicitly. Mirrors the Linear\n * routes' scoping semantics locally — the generic binding storage is\n * provider-neutral.\n */\nasync function scopeSourceIdsToProject({\n intake,\n orgId,\n factoryProjectId,\n selectedIds,\n}: {\n intake: IntakeStorage;\n orgId: string;\n factoryProjectId: string;\n selectedIds: string[];\n}): Promise<Record<string, string>> {\n const selected = new Set(selectedIds);\n const intakeBoards: Record<string, string> = {};\n for (const binding of await intake.listBindings({ orgId, integrationId: 'jira' })) {\n if (binding.factoryProjectId === factoryProjectId && binding.board && selected.has(binding.sourceId)) {\n intakeBoards[binding.sourceId] = binding.board;\n }\n }\n return intakeBoards;\n}\n\n/**\n * Resolve the org-scoped tenant for a Jira request. Intake selections are\n * org-owned, so it requires both a signed-in user and an organization — same\n * tenancy rules as the Linear routes.\n */\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n {\n error: 'organization_required',\n message: 'Jira intake requires an organization. Personal accounts cannot use Jira intake.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque Jira pagination cursor from the query string. Cursors are\n * server-issued (`nextPageToken`), so anything outside a conservative\n * charset/length is rejected rather than forwarded to Jira.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 512 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map a Jira read failure to the API response for the SPA. */\nfunction jiraFetchError(c: RouteContext, err: unknown) {\n if (err instanceof JiraApiError && err.code === 'jira_auth_failed') {\n return c.json(\n {\n error: 'jira_auth_failed',\n message: 'Jira rejected the connected account. Reconnect it in Mastra Platform.',\n },\n 409,\n );\n }\n return c.json({ error: 'jira_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the Jira routes as Mastra `apiRoutes`. When the feature is disabled,\n * returns only the `status` route so the SPA can detect the disabled state.\n */\nexport function buildPlatformJiraRoutes(options: MountJiraRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { jira, auth, intake } = options;\n const enabled = Boolean(jira) && auth.enabled();\n const diagnostics = (): JiraFeatureDiagnostics => ({\n jiraConfigured: Boolean(jira),\n factoryAuthEnabled: auth.enabled(),\n appDbConfigured: options.appDbConfigured,\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !jira || !intake) {\n return c.json({\n enabled: false,\n configured: Boolean(jira),\n mode: 'platform',\n site: null,\n reason: 'missing_config',\n diagnostics: diagnostics(),\n });\n }\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized', reason: 'auth_required' }, 401);\n\n if (!tenant.orgId) {\n return c.json({\n enabled: true,\n configured: false,\n mode: 'platform',\n organizationRequired: true,\n site: null,\n sites: [],\n connections: [],\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n\n try {\n const connections = await jira.listConnections();\n const active = connections.filter(connection => connection.status === 'active');\n const sites = active.flatMap(connection => (connection.accountLabel ? [connection.accountLabel] : []));\n return c.json({\n enabled: true,\n configured: active.length > 0,\n mode: 'platform',\n site: sites.length === 1 ? sites[0] : null,\n sites,\n connections,\n reason: active.length > 0 ? 'ready' : 'not_connected',\n diagnostics: diagnostics(),\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // Without the integration instance or the intake domain the feature can't\n // serve org-scoped data — serve only the disabled `status` route.\n if (!enabled || !jira || !intake) {\n return routes;\n }\n\n // ── List the site's projects (Settings intake-source picker) ────────────\n routes.push(\n registerApiRoute('/web/jira/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n try {\n const sources = await jira.intake.listSources(resolved.tenant);\n return c.json({\n projects: sources.map(source => ({\n id: source.id,\n name: source.name,\n key: typeof source.metadata?.key === 'string' ? source.metadata.key : null,\n connectionId: typeof source.metadata?.connectionId === 'string' ? source.metadata.connectionId : null,\n site: typeof source.metadata?.site === 'string' ? source.metadata.site : null,\n })),\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── List the site's active issues (cursor-paged) ────────────────────────\n // Respects the caller's intake config: disabled Jira intake 404s the\n // source, and an explicit project selection narrows the issue filter.\n routes.push(\n registerApiRoute('/web/jira/issues', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const after = parseAfterCursor(c.req.query('after'));\n if (after === null) return c.json({ error: 'invalid_cursor' }, 400);\n const factoryProjectId = c.req.query('factoryProjectId');\n if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['jira'] });\n const selection = config.jira!;\n if (!selection.enabled) {\n return c.json({ error: 'jira_intake_disabled', message: 'Jira intake is turned off in Settings.' }, 404);\n }\n\n // No projects selected means nothing is synced — don't fan out to Jira.\n const selectedIds = selection.sourceIds ?? [];\n // A board request only ever sees the projects routed to a board of\n // that Factory project; unscoped listing stays available to callers\n // that don't view a specific board.\n const intakeBoards = factoryProjectId\n ? await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds,\n })\n : null;\n const projectIds = intakeBoards ? Object.keys(intakeBoards) : selectedIds;\n if (projectIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const { issues, nextCursor } = await jira.listActiveIssues(after, projectIds);\n const issuePayload = issues.map(issue => ({\n id: issue.externalId,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.author,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee,\n project: issue.source,\n site: issue.site,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n sourceId: issue.sourceId || null,\n }));\n if (factoryProjectId && intakeBoards && options.ingestFactoryIssues) {\n await options.ingestFactoryIssues({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n factoryProjectId,\n issues: issuePayload.map(issue => ({ ...issue, site: issue.site ?? null })),\n intakeBoards,\n });\n }\n return c.json({ issues: issuePayload, nextCursor });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n routes.push(\n registerApiRoute('/web/jira/issues/:identifier', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const identifier = c.req.param('identifier');\n const factoryProjectId = c.req.query('factoryProjectId');\n const issueRef = c.req.query('issueRef');\n if (!ISSUE_KEY_RE.test(identifier)) return c.json({ error: 'invalid_issue_identifier' }, 400);\n if (!factoryProjectId || !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n if (!issueRef || issueRef.length > 2_048) return c.json({ error: 'invalid_issue_ref' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['jira'] });\n const selection = config.jira!;\n if (!selection.enabled) {\n return c.json({ error: 'jira_intake_disabled', message: 'Jira intake is turned off in Settings.' }, 404);\n }\n\n const intakeBoards = await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds: selection.sourceIds ?? [],\n });\n const dispatch = await jira.intake.resolveIntakeDispatch?.({\n orgId: resolved.tenant.orgId,\n externalSource: { type: 'issue', externalId: issueRef },\n });\n if (!dispatch?.sourceId || !(dispatch.sourceId in intakeBoards)) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n\n try {\n const issue = await jira.intake.getIssue(dispatch);\n if (!issue || issue.identifier.toUpperCase() !== identifier.toUpperCase()) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n return c.json({\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n description: issue.description,\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../../../src/integrations/platform/jira/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for the Jira intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the host auth gate.\n * Jira connections are owned by the caller's Mastra Platform organization;\n * provider requests stay server-side and flow through the integrations v2\n * proxy. Every route re-resolves the authenticated user and scopes intake\n * selections by the caller's org.\n *\n * When the feature is disabled (no auth, or no intake storage),\n * `buildPlatformJiraRoutes` returns only `GET /web/jira/status`, which reports\n * `enabled:false` so the SPA can cleanly hide all Jira UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { IntakeStorage } from '../../../storage/domains/intake/base.js';\nimport type { WorkItemsStorage } from '../../../storage/domains/work-items/base.js';\nimport { JiraApiError } from '../../jira/api.js';\nimport { scopeJiraSourcesToProject } from '../../jira/binding-scope.js';\nimport { jiraIssueFiledIn } from '../../jira/claim.js';\nimport { unfilteredJiraScopes } from '../../jira/intake-jql.js';\nimport type { JiraRulesIngress } from '../../jira/rules.js';\nimport type { PlatformJiraIntegration } from './integration.js';\n\ntype RouteContext = Context;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst ISSUE_KEY_RE = /^[A-Za-z][A-Za-z0-9_]*-\\d+$/;\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): RouteContext {\n return c as RouteContext;\n}\n\n/**\n * Non-secret diagnostic snapshot of every Jira feature gate, mirroring the\n * Linear diagnostics shape. Only booleans — never values.\n */\nexport interface JiraFeatureDiagnostics {\n jiraConfigured: boolean;\n factoryAuthEnabled: boolean;\n appDbConfigured: boolean;\n}\n\nexport interface MountJiraRoutesOptions {\n /**\n * The integration instance providing REST access. Required for everything\n * beyond the disabled `status` route.\n */\n jira?: PlatformJiraIntegration;\n /** Host auth seam. Intake selections are org-owned, so the feature is inert without it. */\n auth: RouteAuth;\n /**\n * Cross-integration intake selection domain. Required for the issues route's\n * project filter; when absent, only the disabled `status` route is served.\n */\n intake?: IntakeStorage;\n /** Whether the host configured the application database backing intake state. */\n appDbConfigured: boolean;\n /**\n * Factory rules ingress for observed issues. When present, a board-scoped\n * issue listing also materializes new cards through the Jira event rules —\n * the same automatic intake Linear has.\n */\n ingestFactoryIssues?: (input: JiraRulesIngress) => Promise<unknown>;\n /** Filed cards, so a Factory keeps reading issues it filed after a label filter moves them to another slice. */\n workItems?: Pick<WorkItemsStorage, 'getByClaimKey' | 'listBySource'>;\n}\n\n/**\n * Resolve the org-scoped tenant for a Jira request. Intake selections are\n * org-owned, so it requires both a signed-in user and an organization — same\n * tenancy rules as the Linear routes.\n */\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n {\n error: 'organization_required',\n message: 'Jira intake requires an organization. Personal accounts cannot use Jira intake.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque Jira pagination cursor from the query string. Cursors are\n * server-issued (`nextPageToken`), so anything outside a conservative\n * charset/length is rejected rather than forwarded to Jira.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 512 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map a Jira read failure to the API response for the SPA. */\nfunction jiraFetchError(c: RouteContext, err: unknown) {\n if (err instanceof JiraApiError && err.code === 'jira_auth_failed') {\n return c.json(\n {\n error: 'jira_auth_failed',\n message: 'Jira rejected the connected account. Reconnect it in Mastra Platform.',\n },\n 409,\n );\n }\n return c.json({ error: 'jira_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the Jira routes as Mastra `apiRoutes`. When the feature is disabled,\n * returns only the `status` route so the SPA can detect the disabled state.\n */\nexport function buildPlatformJiraRoutes(options: MountJiraRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { jira, auth, intake, workItems } = options;\n const enabled = Boolean(jira) && auth.enabled();\n const diagnostics = (): JiraFeatureDiagnostics => ({\n jiraConfigured: Boolean(jira),\n factoryAuthEnabled: auth.enabled(),\n appDbConfigured: options.appDbConfigured,\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !jira || !intake) {\n return c.json({\n enabled: false,\n configured: Boolean(jira),\n mode: 'platform',\n site: null,\n reason: 'missing_config',\n diagnostics: diagnostics(),\n });\n }\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized', reason: 'auth_required' }, 401);\n\n if (!tenant.orgId) {\n return c.json({\n enabled: true,\n configured: false,\n mode: 'platform',\n organizationRequired: true,\n site: null,\n sites: [],\n connections: [],\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n\n try {\n const connections = await jira.listConnections();\n const active = connections.filter(connection => connection.status === 'active');\n const sites = active.flatMap(connection => (connection.accountLabel ? [connection.accountLabel] : []));\n return c.json({\n enabled: true,\n configured: active.length > 0,\n mode: 'platform',\n site: sites.length === 1 ? sites[0] : null,\n sites,\n connections,\n reason: active.length > 0 ? 'ready' : 'not_connected',\n diagnostics: diagnostics(),\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // Without the integration instance or the intake domain the feature can't\n // serve org-scoped data — serve only the disabled `status` route.\n if (!enabled || !jira || !intake) {\n return routes;\n }\n\n // ── List the site's projects (Settings intake-source picker) ────────────\n routes.push(\n registerApiRoute('/web/jira/projects', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n try {\n const sources = await jira.intake.listSources(resolved.tenant);\n return c.json({\n projects: sources.map(source => ({\n id: source.id,\n name: source.name,\n key: typeof source.metadata?.key === 'string' ? source.metadata.key : null,\n connectionId: typeof source.metadata?.connectionId === 'string' ? source.metadata.connectionId : null,\n site: typeof source.metadata?.site === 'string' ? source.metadata.site : null,\n })),\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── List the site's active issues (cursor-paged) ────────────────────────\n // Respects the caller's intake config: disabled Jira intake 404s the\n // source, and an explicit project selection narrows the issue filter.\n routes.push(\n registerApiRoute('/web/jira/issues', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const after = parseAfterCursor(c.req.query('after'));\n if (after === null) return c.json({ error: 'invalid_cursor' }, 400);\n const factoryProjectId = c.req.query('factoryProjectId');\n if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['jira'] });\n const selection = config.jira!;\n if (!selection.enabled) {\n return c.json({ error: 'jira_intake_disabled', message: 'Jira intake is turned off in Settings.' }, 404);\n }\n\n // No projects selected means nothing is synced — don't fan out to Jira.\n const selectedIds = selection.sourceIds ?? [];\n // A board request only ever sees the projects routed to a board of\n // that Factory project; unscoped listing stays available to callers\n // that don't view a specific board.\n const projectScope = factoryProjectId\n ? await scopeJiraSourcesToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds,\n })\n : null;\n const intakeBoards = projectScope?.intakeBoards ?? null;\n const scopes = projectScope?.scopes ?? unfilteredJiraScopes(selectedIds);\n if (scopes.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const { issues, nextCursor } = await jira.listActiveIssues(after, scopes);\n const issuePayload = issues.map(issue => ({\n id: issue.externalId,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.author,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee,\n project: issue.source,\n site: issue.site,\n labels: issue.labels,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n sourceId: issue.sourceId || null,\n }));\n if (factoryProjectId && intakeBoards && options.ingestFactoryIssues) {\n await options.ingestFactoryIssues({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n factoryProjectId,\n issues: issuePayload.map(issue => ({ ...issue, site: issue.site ?? null })),\n intakeBoards,\n });\n }\n return c.json({ issues: issuePayload, nextCursor });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n routes.push(\n registerApiRoute('/web/jira/issues/:identifier', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const identifier = c.req.param('identifier');\n const factoryProjectId = c.req.query('factoryProjectId');\n const issueRef = c.req.query('issueRef');\n if (!ISSUE_KEY_RE.test(identifier)) return c.json({ error: 'invalid_issue_identifier' }, 400);\n if (!factoryProjectId || !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n if (!issueRef || issueRef.length > 2_048) return c.json({ error: 'invalid_issue_ref' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['jira'] });\n const selection = config.jira!;\n if (!selection.enabled) {\n return c.json({ error: 'jira_intake_disabled', message: 'Jira intake is turned off in Settings.' }, 404);\n }\n\n const projectScope = await scopeJiraSourcesToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds: selection.sourceIds ?? [],\n });\n const dispatch = await jira.intake.resolveIntakeDispatch?.({\n orgId: resolved.tenant.orgId,\n externalSource: { type: 'issue', externalId: issueRef },\n });\n const sourceId = dispatch?.sourceId;\n if (!dispatch || !sourceId || !(sourceId in projectScope.intakeBoards)) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n\n try {\n const issue = await jira.intake.getIssue(dispatch);\n // An issue in another Factory's slice that this Factory never filed reads like one that doesn't exist.\n if (\n !issue ||\n issue.identifier.toUpperCase() !== identifier.toUpperCase() ||\n !(\n projectScope.takes(sourceId, issue.labels) ||\n (await jiraIssueFiledIn(workItems, { orgId: resolved.tenant.orgId, factoryProjectId, issueRef }))\n )\n ) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n return c.json({\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n description: issue.description,\n });\n } catch (err) {\n return jiraFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BoardRegistry } from '../boards/index.js';
|
|
2
|
+
import type { ProjectBoardSource } from '../boards/project-boards.js';
|
|
2
3
|
import type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';
|
|
3
4
|
import type { FactoryAttentionIdentity, FactoryAttentionKind, FactoryAttentionReceiptRecord, FactoryDeferredDecisionRecord, FactoryDispatchStatus, WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
4
5
|
export type FactoryAttentionView = 'open' | 'unread' | 'archived';
|
|
@@ -94,7 +95,7 @@ export declare class DecisionAttentionProvider implements AttentionProvider {
|
|
|
94
95
|
readonly kind: FactoryAttentionKind;
|
|
95
96
|
constructor({ workItems, boards }: {
|
|
96
97
|
workItems: WorkItemsStorage;
|
|
97
|
-
boards: BoardRegistry;
|
|
98
|
+
boards: BoardRegistry | ProjectBoardSource;
|
|
98
99
|
}, spec: DecisionAttentionSpec);
|
|
99
100
|
counts(scope: AttentionScope): Promise<AttentionCounts>;
|
|
100
101
|
latest(scope: AttentionScope): Promise<AttentionLatest | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attention-providers.d.ts","sourceRoot":"","sources":["../../src/routes/attention-providers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"attention-providers.d.ts","sourceRoot":"","sources":["../../src/routes/attention-providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { boardForWorkItem } from "../boards/semantics.js";
|
|
2
2
|
import "../boards/index.js";
|
|
3
|
+
import { toBoardSource } from "../boards/project-boards.js";
|
|
3
4
|
import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
|
|
4
5
|
import { factoryAttentionKey, factoryDecisionAttentionIdentity, factoryMentionAttentionIdentity } from "../storage/domains/work-items/base.js";
|
|
5
6
|
import { decisionRetryable, overtakenDecisionIds } from "../rules/decision-applicability.js";
|
|
@@ -94,7 +95,7 @@ var DecisionAttentionProvider = class {
|
|
|
94
95
|
#boards;
|
|
95
96
|
constructor({ workItems, boards }, spec) {
|
|
96
97
|
this.#workItems = workItems;
|
|
97
|
-
this.#boards = boards;
|
|
98
|
+
this.#boards = toBoardSource(boards);
|
|
98
99
|
this.#spec = spec;
|
|
99
100
|
this.kind = spec.kind;
|
|
100
101
|
}
|
|
@@ -162,7 +163,7 @@ var DecisionAttentionProvider = class {
|
|
|
162
163
|
const [receiptByKey, itemById, overtaken] = await Promise.all([
|
|
163
164
|
this.#receiptsFor(scope, decisions),
|
|
164
165
|
this.#linkedItems(scope, decisions),
|
|
165
|
-
overtakenDecisionIds(this.#workItems,
|
|
166
|
+
this.#boards.load(scope).then((boards) => overtakenDecisionIds(this.#workItems, boards, scope, decisions))
|
|
166
167
|
]);
|
|
167
168
|
const entries = [];
|
|
168
169
|
for (const decision of decisions) {
|