@mastra/factory 0.17.3-alpha.2 → 0.17.3-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.
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAOxF,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAKD,qBAAa,qBAAsB,YAAW,kBAAkB;;IAC9D,QAAQ,CAAC,EAAE,gBAAgB;IAC3B,QAAQ,CAAC,MAAM,gDAAC;gBAUJ,MAAM,GAAE,2BAAgC;IAmBpD,kFAAkF;IAClF,IAAI,KAAK,IAAI,oBAAoB,CAEhC;IAED;;;;OAIG;IACH,UAAU,CAAC,EACT,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;QACD,QAAQ,EAAE,sBAAsB,CAAC;QACjC,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;KACvB,GAAG,IAAI;IAMR,oFAAoF;IACpF,IAAI,aAAa,IAAI,aAAa,CAKjC;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED,sFAAsF;IACtF,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqB9D,oDAAoD;IACpD,WAAW,IAAI,IAAI;IAInB;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrF,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAS3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGvC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3F,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAOxF,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAKD,qBAAa,qBAAsB,YAAW,kBAAkB;;IAC9D,QAAQ,CAAC,EAAE,gBAAgB;IAC3B,QAAQ,CAAC,MAAM,gDAAC;gBAUJ,MAAM,GAAE,2BAAgC;IAmBpD,kFAAkF;IAClF,IAAI,KAAK,IAAI,oBAAoB,CAEhC;IAED;;;;OAIG;IACH,UAAU,CAAC,EACT,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;QACD,QAAQ,EAAE,sBAAsB,CAAC;QACjC,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;KACvB,GAAG,IAAI;IAMR,oFAAoF;IACpF,IAAI,aAAa,IAAI,aAAa,CAKjC;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED,sFAAsF;IACtF,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqB9D,oDAAoD;IACpD,WAAW,IAAI,IAAI;IAInB;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrF,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAU3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGvC"}
@@ -122,6 +122,7 @@ var IncidentioIntegration = class {
122
122
  routes(ctx) {
123
123
  return buildIncidentioRoutes({
124
124
  incidentio: this,
125
+ mode: "api-key",
125
126
  auth: ctx.auth,
126
127
  intake: ctx.storage.intake,
127
128
  ingestFactoryIssues: attachIncidentioRules(this, ctx)
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":["#endpointHost","#rules","#orgIdByResourceId","#projects","#auth","#intakeStorage"],"sources":["../../../src/integrations/incidentio/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 { RouteAuth } from '../../routes/route.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.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 { buildIncidentioAgentTools } from './agent-tools.js';\nimport { IncidentioApiClient } from './api.js';\nimport { resolveIncidentioRules } from './default-rules.js';\nimport type { IncidentioEventRules, IncidentioRuleOverrides } from './default-rules.js';\nimport { createIncidentioIntake } from './intake.js';\nimport { attachIncidentioIssueReconciler } from './issue-reconciler.js';\nimport { incidentioReconciliationEnabled, incidentioReconciliationInterval } from './reconciliation-config.js';\nimport { buildIncidentioRoutes } from './routes.js';\nimport { attachIncidentioRules } from './rules.js';\n\nexport interface IncidentioIntegrationConfig {\n apiKey?: string;\n fetchImpl?: typeof fetch;\n baseUrl?: string;\n /** Per-event replacements for the default Factory incident.io rules. */\n rules?: IncidentioRuleOverrides;\n}\n\nconst DIRECT_CONNECTION_TOKEN = 'incidentio-direct-api-key';\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport class IncidentioIntegration implements FactoryIntegration {\n readonly id = 'incidentio';\n readonly intake;\n readonly #endpointHost: string;\n readonly #rules: IncidentioEventRules;\n\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n #intakeStorage: IntakeStorage | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n constructor(config: IncidentioIntegrationConfig = {}) {\n this.#rules = resolveIncidentioRules(config.rules);\n const apiKey = config.apiKey?.trim() || process.env.INCIDENT_IO_API_KEY?.trim();\n if (!apiKey) {\n throw new Error('IncidentioIntegration: missing required INCIDENT_IO_API_KEY.');\n }\n const baseUrl = config.baseUrl ?? 'https://api.incident.io';\n this.#endpointHost = new URL(baseUrl).host;\n const api = new IncidentioApiClient({\n baseUrl,\n accessToken: apiKey,\n ...(config.fetchImpl ? { fetchImpl: config.fetchImpl } : {}),\n });\n this.intake = createIncidentioIntake({\n api,\n connection: { type: 'oauth', accessToken: DIRECT_CONNECTION_TOKEN },\n });\n }\n\n /** Event rules driving automatic follow-up materialization and close handling. */\n get rules(): IncidentioEventRules {\n return this.#rules;\n }\n\n /**\n * Bind the projects domain and the host auth seam. incident.io has no\n * per-org connection rows here — credentials are deployment-global\n * constructor config.\n */\n initialize({\n projects,\n auth,\n intake,\n }: {\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n intake: IntakeStorage;\n }): void {\n this.#projects = projects;\n this.#auth = auth;\n this.#intakeStorage = intake;\n }\n\n /** Cross-integration intake selection/binding domain — agent-tool authorization. */\n get intakeStorage(): IntakeStorage {\n if (!this.#intakeStorage) {\n throw new Error('IncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#intakeStorage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('IncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /** Whether the host runs with web auth enabled; every surface is inert without it. */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n /**\n * Map a session's resourceId (the factory project id) to its owning org.\n * Same caching semantics as the Jira integration: definitive misses are\n * cached, transient database failures are not.\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 let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org cache between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n }\n\n /**\n * Org-scoped agent tools: the read-only follow-up detail tool for sessions\n * whose resource is a factory project, matching the Linear/Jira surface.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildIncidentioAgentTools({ requestContext: args.requestContext, incidentio: this });\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!incidentioReconciliationEnabled()) return [];\n const reconcile = attachIncidentioIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = incidentioReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n /**\n * The integration's HTTP surface: `/web/incidentio/*` Mastra `apiRoutes`\n * (status + follow-up listing/detail for Intake). Handlers operate on this\n * instance; a board-scoped listing also feeds the incident.io event rules.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildIncidentioRoutes({\n incidentio: this,\n auth: ctx.auth,\n intake: ctx.storage.intake,\n ingestFactoryIssues: attachIncidentioRules(this, ctx),\n });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'api-key', endpointHost: this.#endpointHost };\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,MAAM,0BAA0B;AAChC,MAAM,eAAe;AAErB,IAAa,wBAAb,MAAiE;CAC/D,KAAc;CACd;CACA;CACA;;CAGA;CACA;CACA;CACA,qCAA8B,IAAI,IAA2B;CAE7D,YAAY,SAAsC,CAAC,GAAG;EACpD,KAAKC,SAAS,uBAAuB,OAAO,KAAK;EACjD,MAAM,SAAS,OAAO,QAAQ,KAAK,KAAK,QAAQ,IAAI,qBAAqB,KAAK;EAC9E,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,8DAA8D;EAEhF,MAAM,UAAU,OAAO,WAAW;EAClC,KAAKD,gBAAgB,IAAI,IAAI,OAAO,CAAC,CAAC;EACtC,MAAM,MAAM,IAAI,oBAAoB;GAClC;GACA,aAAa;GACb,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;EAC5D,CAAC;EACD,KAAK,SAAS,uBAAuB;GACnC;GACA,YAAY;IAAE,MAAM;IAAS,aAAa;GAAwB;EACpE,CAAC;CACH;;CAGA,IAAI,QAA8B;EAChC,OAAO,KAAKC;CACd;;;;;;CAOA,WAAW,EACT,UACA,MACA,UAKO;EACP,KAAKE,YAAY;EACjB,KAAKC,QAAQ;EACb,KAAKC,iBAAiB;CACxB;;CAGA,IAAI,gBAA+B;EACjC,IAAI,CAAC,KAAKA,gBACR,MAAM,IAAI,MAAM,wFAAwF;EAE1G,OAAO,KAAKA;CACd;;CAGA,IAAI,WAAmC;EACrC,IAAI,CAAC,KAAKF,WACR,MAAM,IAAI,MAAM,wFAAwF;EAE1G,OAAO,KAAKA;CACd;;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAKC,OAAO,QAAQ,KAAK;CAClC;;;;;;CAOA,MAAM,aAAa,YAA4C;EAC7D,MAAM,SAAS,KAAKF,mBAAmB,IAAI,UAAU;EACrD,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,IAAI,CAAC,aAAa,KAAK,UAAU,GAAG;GAClC,KAAKA,mBAAmB,IAAI,YAAY,IAAI;GAC5C,OAAO;EACT;EACA,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,SAAS,YAAY;GAEhC,SAAQ,MADc,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,CAAC,EAAA,EAC7C,SAAS;EAC5B,QAAQ;GAGN,OAAO;EACT;EACA,KAAKA,mBAAmB,IAAI,YAAY,KAAK;EAC7C,OAAO;CACT;;CAGA,cAAoB;EAClB,KAAKA,mBAAmB,MAAM;CAChC;;;;;CAMA,MAAM,WAAW,MAAqE;EACpF,OAAO,0BAA0B;GAAE,gBAAgB,KAAK;GAAgB,YAAY;EAAK,CAAC;CAC5F;CAEA,QAAQ,KAAyC;EAC/C,IAAI,CAAC,gCAAgC,GAAG,OAAO,CAAC;EAChD,MAAM,YAAY,gCAAgC,MAAM,GAAG;EAC3D,IAAI,CAAC,WAAW,OAAO,CAAC;EACxB,MAAM,aAAa,iCAAiC;EACpD,OAAO,CACL,IAAI,qBAAqB;GACvB,eAAe,KAAK;GACpB;GACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACrC,CAAC,CACH;CACF;;;;;;CAOA,OAAO,KAAqC;EAC1C,OAAO,sBAAsB;GAC3B,YAAY;GACZ,MAAM,IAAI;GACV,QAAQ,IAAI,QAAQ;GACpB,qBAAqB,sBAAsB,MAAM,GAAG;EACtD,CAAC;CACH;CAEA,cAAuC;EACrC,OAAO;GAAE,MAAM;GAAW,cAAc,KAAKF;EAAc;CAC7D;AACF"}
1
+ {"version":3,"file":"integration.js","names":["#endpointHost","#rules","#orgIdByResourceId","#projects","#auth","#intakeStorage"],"sources":["../../../src/integrations/incidentio/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 { RouteAuth } from '../../routes/route.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.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 { buildIncidentioAgentTools } from './agent-tools.js';\nimport { IncidentioApiClient } from './api.js';\nimport { resolveIncidentioRules } from './default-rules.js';\nimport type { IncidentioEventRules, IncidentioRuleOverrides } from './default-rules.js';\nimport { createIncidentioIntake } from './intake.js';\nimport { attachIncidentioIssueReconciler } from './issue-reconciler.js';\nimport { incidentioReconciliationEnabled, incidentioReconciliationInterval } from './reconciliation-config.js';\nimport { buildIncidentioRoutes } from './routes.js';\nimport { attachIncidentioRules } from './rules.js';\n\nexport interface IncidentioIntegrationConfig {\n apiKey?: string;\n fetchImpl?: typeof fetch;\n baseUrl?: string;\n /** Per-event replacements for the default Factory incident.io rules. */\n rules?: IncidentioRuleOverrides;\n}\n\nconst DIRECT_CONNECTION_TOKEN = 'incidentio-direct-api-key';\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport class IncidentioIntegration implements FactoryIntegration {\n readonly id = 'incidentio';\n readonly intake;\n readonly #endpointHost: string;\n readonly #rules: IncidentioEventRules;\n\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n #intakeStorage: IntakeStorage | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n constructor(config: IncidentioIntegrationConfig = {}) {\n this.#rules = resolveIncidentioRules(config.rules);\n const apiKey = config.apiKey?.trim() || process.env.INCIDENT_IO_API_KEY?.trim();\n if (!apiKey) {\n throw new Error('IncidentioIntegration: missing required INCIDENT_IO_API_KEY.');\n }\n const baseUrl = config.baseUrl ?? 'https://api.incident.io';\n this.#endpointHost = new URL(baseUrl).host;\n const api = new IncidentioApiClient({\n baseUrl,\n accessToken: apiKey,\n ...(config.fetchImpl ? { fetchImpl: config.fetchImpl } : {}),\n });\n this.intake = createIncidentioIntake({\n api,\n connection: { type: 'oauth', accessToken: DIRECT_CONNECTION_TOKEN },\n });\n }\n\n /** Event rules driving automatic follow-up materialization and close handling. */\n get rules(): IncidentioEventRules {\n return this.#rules;\n }\n\n /**\n * Bind the projects domain and the host auth seam. incident.io has no\n * per-org connection rows here — credentials are deployment-global\n * constructor config.\n */\n initialize({\n projects,\n auth,\n intake,\n }: {\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n intake: IntakeStorage;\n }): void {\n this.#projects = projects;\n this.#auth = auth;\n this.#intakeStorage = intake;\n }\n\n /** Cross-integration intake selection/binding domain — agent-tool authorization. */\n get intakeStorage(): IntakeStorage {\n if (!this.#intakeStorage) {\n throw new Error('IncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#intakeStorage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('IncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /** Whether the host runs with web auth enabled; every surface is inert without it. */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n /**\n * Map a session's resourceId (the factory project id) to its owning org.\n * Same caching semantics as the Jira integration: definitive misses are\n * cached, transient database failures are not.\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 let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org cache between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n }\n\n /**\n * Org-scoped agent tools: the read-only follow-up detail tool for sessions\n * whose resource is a factory project, matching the Linear/Jira surface.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildIncidentioAgentTools({ requestContext: args.requestContext, incidentio: this });\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!incidentioReconciliationEnabled()) return [];\n const reconcile = attachIncidentioIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = incidentioReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n /**\n * The integration's HTTP surface: `/web/incidentio/*` Mastra `apiRoutes`\n * (status + follow-up listing/detail for Intake). Handlers operate on this\n * instance; a board-scoped listing also feeds the incident.io event rules.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildIncidentioRoutes({\n incidentio: this,\n mode: 'api-key',\n auth: ctx.auth,\n intake: ctx.storage.intake,\n ingestFactoryIssues: attachIncidentioRules(this, ctx),\n });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'api-key', endpointHost: this.#endpointHost };\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,MAAM,0BAA0B;AAChC,MAAM,eAAe;AAErB,IAAa,wBAAb,MAAiE;CAC/D,KAAc;CACd;CACA;CACA;;CAGA;CACA;CACA;CACA,qCAA8B,IAAI,IAA2B;CAE7D,YAAY,SAAsC,CAAC,GAAG;EACpD,KAAKC,SAAS,uBAAuB,OAAO,KAAK;EACjD,MAAM,SAAS,OAAO,QAAQ,KAAK,KAAK,QAAQ,IAAI,qBAAqB,KAAK;EAC9E,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,8DAA8D;EAEhF,MAAM,UAAU,OAAO,WAAW;EAClC,KAAKD,gBAAgB,IAAI,IAAI,OAAO,CAAC,CAAC;EACtC,MAAM,MAAM,IAAI,oBAAoB;GAClC;GACA,aAAa;GACb,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;EAC5D,CAAC;EACD,KAAK,SAAS,uBAAuB;GACnC;GACA,YAAY;IAAE,MAAM;IAAS,aAAa;GAAwB;EACpE,CAAC;CACH;;CAGA,IAAI,QAA8B;EAChC,OAAO,KAAKC;CACd;;;;;;CAOA,WAAW,EACT,UACA,MACA,UAKO;EACP,KAAKE,YAAY;EACjB,KAAKC,QAAQ;EACb,KAAKC,iBAAiB;CACxB;;CAGA,IAAI,gBAA+B;EACjC,IAAI,CAAC,KAAKA,gBACR,MAAM,IAAI,MAAM,wFAAwF;EAE1G,OAAO,KAAKA;CACd;;CAGA,IAAI,WAAmC;EACrC,IAAI,CAAC,KAAKF,WACR,MAAM,IAAI,MAAM,wFAAwF;EAE1G,OAAO,KAAKA;CACd;;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAKC,OAAO,QAAQ,KAAK;CAClC;;;;;;CAOA,MAAM,aAAa,YAA4C;EAC7D,MAAM,SAAS,KAAKF,mBAAmB,IAAI,UAAU;EACrD,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,IAAI,CAAC,aAAa,KAAK,UAAU,GAAG;GAClC,KAAKA,mBAAmB,IAAI,YAAY,IAAI;GAC5C,OAAO;EACT;EACA,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,SAAS,YAAY;GAEhC,SAAQ,MADc,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,CAAC,EAAA,EAC7C,SAAS;EAC5B,QAAQ;GAGN,OAAO;EACT;EACA,KAAKA,mBAAmB,IAAI,YAAY,KAAK;EAC7C,OAAO;CACT;;CAGA,cAAoB;EAClB,KAAKA,mBAAmB,MAAM;CAChC;;;;;CAMA,MAAM,WAAW,MAAqE;EACpF,OAAO,0BAA0B;GAAE,gBAAgB,KAAK;GAAgB,YAAY;EAAK,CAAC;CAC5F;CAEA,QAAQ,KAAyC;EAC/C,IAAI,CAAC,gCAAgC,GAAG,OAAO,CAAC;EAChD,MAAM,YAAY,gCAAgC,MAAM,GAAG;EAC3D,IAAI,CAAC,WAAW,OAAO,CAAC;EACxB,MAAM,aAAa,iCAAiC;EACpD,OAAO,CACL,IAAI,qBAAqB;GACvB,eAAe,KAAK;GACpB;GACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACrC,CAAC,CACH;CACF;;;;;;CAOA,OAAO,KAAqC;EAC1C,OAAO,sBAAsB;GAC3B,YAAY;GACZ,MAAM;GACN,MAAM,IAAI;GACV,QAAQ,IAAI,QAAQ;GACpB,qBAAqB,sBAAsB,MAAM,GAAG;EACtD,CAAC;CACH;CAEA,cAAuC;EACrC,OAAO;GAAE,MAAM;GAAW,cAAc,KAAKF;EAAc;CAC7D;AACF"}
@@ -34,6 +34,11 @@ export interface MountIncidentioRoutesOptions {
34
34
  intake: Intake;
35
35
  id: string;
36
36
  };
37
+ /**
38
+ * Credential mode reported on the status route so the SPA can tell a
39
+ * deployment-configured API key apart from Platform-managed connections.
40
+ */
41
+ mode?: 'api-key' | 'platform';
37
42
  /** Host auth seam. Intake selections are org-owned, so the feature is inert without it. */
38
43
  auth: RouteAuth;
39
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,2FAA2F;IAC3F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASlC;AAqED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,QAAQ,EAAE,CA8MvF"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAC9B,2FAA2F;IAC3F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASlC;AAqED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,QAAQ,EAAE,CAiNvF"}
@@ -70,8 +70,9 @@ function incidentioFetchError(c, err) {
70
70
  */
71
71
  function buildIncidentioRoutes(options) {
72
72
  const routes = [];
73
- const { incidentio, auth, intake } = options;
73
+ const { incidentio, auth, intake, mode } = options;
74
74
  const enabled = Boolean(incidentio) && auth.enabled();
75
+ const modeField = mode ? { mode } : {};
75
76
  const diagnostics = () => ({
76
77
  incidentioConfigured: Boolean(incidentio),
77
78
  factoryAuthEnabled: auth.enabled()
@@ -83,6 +84,7 @@ function buildIncidentioRoutes(options) {
83
84
  if (!enabled || !incidentio || !intake) return c.json({
84
85
  enabled: false,
85
86
  configured: Boolean(incidentio),
87
+ ...modeField,
86
88
  reason: "missing_config",
87
89
  diagnostics: diagnostics()
88
90
  });
@@ -95,6 +97,7 @@ function buildIncidentioRoutes(options) {
95
97
  if (!tenant.orgId) return c.json({
96
98
  enabled: true,
97
99
  configured: true,
100
+ ...modeField,
98
101
  organizationRequired: true,
99
102
  reason: "organization_required",
100
103
  diagnostics: diagnostics()
@@ -102,6 +105,7 @@ function buildIncidentioRoutes(options) {
102
105
  return c.json({
103
106
  enabled: true,
104
107
  configured: true,
108
+ ...modeField,
105
109
  reason: "ready",
106
110
  diagnostics: diagnostics()
107
111
  });
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","names":[],"sources":["../../../src/integrations/incidentio/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for the incident.io intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the host auth gate.\n * Mirrors the Jira module: no connect/callback flow here — credentials are\n * either deployment-global constructor config (direct) or Platform-managed\n * connections discovered at runtime. Every route re-resolves the\n * authenticated user from the request and scopes intake selections by the\n * caller's org.\n *\n * When the feature is disabled (no auth, or no intake storage),\n * `buildIncidentioRoutes` returns only `GET /web/incidentio/status`, which\n * reports `enabled:false` so the SPA can cleanly hide all incident.io UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { Intake } from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport { IncidentioApiError } from './api.js';\nimport type { IncidentioRulesIngress } from './rules.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 ITEM_REF_RE = /^incidentio:(?:follow-up|incident):[\\w-]+$/;\nconst FOLLOW_UP_PREFIX = 'incidentio:follow-up:';\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 incident.io feature gate, mirroring\n * the Jira diagnostics shape. Only booleans — never values.\n */\nexport interface IncidentioFeatureDiagnostics {\n incidentioConfigured: boolean;\n factoryAuthEnabled: boolean;\n}\n\nexport interface MountIncidentioRoutesOptions {\n /**\n * The integration's intake capability providing incident.io access. Required\n * for everything beyond the disabled `status` route.\n */\n incidentio?: { intake: Intake; id: string };\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 /**\n * Factory rules ingress for observed follow-ups. When present, a\n * board-scoped listing also materializes new cards through the incident.io\n * event rules — the same automatic intake Linear and Jira have.\n */\n ingestFactoryIssues?: (input: IncidentioRulesIngress) => Promise<unknown>;\n}\n\n/**\n * Narrow the caller's selected incident.io sources to the ones that feed this\n * Factory project. Routing is explicit: a source feeds this Factory only when\n * its binding names both the Factory project and a board. Mirrors the Jira\n * routes' scoping semantics — the generic binding storage is provider-neutral.\n * Shared with the agent tools, which enforce the same source-level\n * authorization before fetching item details.\n */\nexport async 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: 'incidentio' })) {\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 an incident.io request. Intake selections\n * are org-owned, so it requires both a signed-in user and an organization.\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: 'incident.io intake requires an organization. Personal accounts cannot use incident.io intake.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque pagination cursor from the query string. Cursors are\n * server-issued base64url blobs, so anything outside a conservative\n * charset/length is rejected rather than decoded.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 2_048 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map an incident.io read failure to the API response for the SPA. */\nfunction incidentioFetchError(c: RouteContext, err: unknown) {\n if (err instanceof IncidentioApiError && (err.status === 401 || err.status === 403)) {\n return c.json(\n {\n error: 'incidentio_auth_failed',\n message: 'incident.io rejected the configured credentials. Reconnect the incident.io account.',\n },\n 409,\n );\n }\n return c.json({ error: 'incidentio_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\ninterface IncidentioIssuePayload {\n id: string;\n identifier: string;\n title: string;\n url: string;\n author: string | null;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n incident: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n sourceId: string | null;\n}\n\n/**\n * Build the incident.io routes as Mastra `apiRoutes`. When the feature is\n * disabled, returns only the `status` route so the SPA can detect the\n * disabled state.\n */\nexport function buildIncidentioRoutes(options: MountIncidentioRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { incidentio, auth, intake } = options;\n const enabled = Boolean(incidentio) && auth.enabled();\n const diagnostics = (): IncidentioFeatureDiagnostics => ({\n incidentioConfigured: Boolean(incidentio),\n factoryAuthEnabled: auth.enabled(),\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/incidentio/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !incidentio || !intake) {\n return c.json({\n enabled: false,\n configured: Boolean(incidentio),\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 if (!tenant.orgId) {\n return c.json({\n enabled: true,\n configured: true,\n organizationRequired: true,\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n return c.json({ enabled: true, configured: true, reason: 'ready', diagnostics: diagnostics() });\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 || !incidentio || !intake) {\n return routes;\n }\n\n // ── List bound follow-ups (cursor-paged) ────────────────────────────────\n // Respects the caller's intake config: disabled incident.io intake 404s,\n // and only sources routed to the requested Factory project are listed.\n // Incidents never appear here — only follow-ups feed intake.\n routes.push(\n registerApiRoute('/web/incidentio/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: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n\n const selectedIds = selection.sourceIds ?? [];\n const intakeBoards = factoryProjectId\n ? await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds,\n })\n : null;\n const sourceIds = intakeBoards ? Object.keys(intakeBoards) : selectedIds;\n if (sourceIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const page = await incidentio.intake.listItems({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n sourceIds,\n ...(after ? { cursor: after } : {}),\n });\n const issuePayload: IncidentioIssuePayload[] = [];\n for (const item of page.items) {\n const externalId = item.source.externalId;\n // Incidents stay out of intake even when a stale binding selects\n // the incidents source — only follow-ups are served.\n if (!externalId.startsWith(FOLLOW_UP_PREFIX)) continue;\n const metadata = item.metadata ?? {};\n const identifier = typeof metadata.identifier === 'string' ? metadata.identifier : externalId;\n const titlePrefix = `${identifier}: `;\n issuePayload.push({\n id: externalId,\n identifier,\n title: item.title.startsWith(titlePrefix) ? item.title.slice(titlePrefix.length) : item.title,\n url: item.source.url ?? '',\n author: typeof metadata.author === 'string' ? metadata.author : null,\n state: item.status ?? '',\n stateType: typeof metadata.stateType === 'string' ? metadata.stateType : '',\n priorityLabel: typeof metadata.priority === 'string' ? metadata.priority : '',\n assignee: item.assignee ?? null,\n incident: typeof metadata.incidentioIncidentId === 'string' ? metadata.incidentioIncidentId : null,\n labels: item.labels ?? [],\n createdAt: item.createdAt ?? '',\n updatedAt: item.updatedAt ?? '',\n sourceId: item.sourceId || null,\n });\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,\n intakeBoards,\n });\n }\n return c.json({ issues: issuePayload, nextCursor: page.nextCursor });\n } catch (err) {\n return incidentioFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── Follow-up detail (description for the card panel) ───────────────────\n routes.push(\n registerApiRoute('/web/incidentio/issues/detail', {\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 factoryProjectId = c.req.query('factoryProjectId');\n const issueRef = c.req.query('issueRef');\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 || !ITEM_REF_RE.test(issueRef)) {\n return c.json({ error: 'invalid_issue_ref' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n const intakeBoards = await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds: selection.sourceIds ?? [],\n });\n if (Object.keys(intakeBoards).length === 0) return c.json({ error: 'issue_not_found' }, 404);\n\n try {\n // The item must resolve through a source routed to this Factory —\n // an item from an unbound source reads exactly like one that\n // doesn't exist (same stance as the Jira detail route).\n const dispatch = await incidentio.intake.resolveIntakeDispatch?.({\n orgId: resolved.tenant.orgId,\n externalSource: { type: 'issue', externalId: issueRef },\n });\n if (!dispatch || !dispatch.sourceId || !(dispatch.sourceId in intakeBoards)) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n const issue = await incidentio.intake.getIssue({\n connection: dispatch.connection,\n issueId: dispatch.issueId,\n });\n if (!issue) return c.json({ error: 'issue_not_found' }, 404);\n return c.json({\n issue: {\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n description: issue.description ?? null,\n },\n });\n } catch (err) {\n return incidentioFetchError(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/incidentio/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for the incident.io intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the host auth gate.\n * Mirrors the Jira module: no connect/callback flow here — credentials are\n * either deployment-global constructor config (direct) or Platform-managed\n * connections discovered at runtime. Every route re-resolves the\n * authenticated user from the request and scopes intake selections by the\n * caller's org.\n *\n * When the feature is disabled (no auth, or no intake storage),\n * `buildIncidentioRoutes` returns only `GET /web/incidentio/status`, which\n * reports `enabled:false` so the SPA can cleanly hide all incident.io UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { Intake } from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport { IncidentioApiError } from './api.js';\nimport type { IncidentioRulesIngress } from './rules.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 ITEM_REF_RE = /^incidentio:(?:follow-up|incident):[\\w-]+$/;\nconst FOLLOW_UP_PREFIX = 'incidentio:follow-up:';\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 incident.io feature gate, mirroring\n * the Jira diagnostics shape. Only booleans — never values.\n */\nexport interface IncidentioFeatureDiagnostics {\n incidentioConfigured: boolean;\n factoryAuthEnabled: boolean;\n}\n\nexport interface MountIncidentioRoutesOptions {\n /**\n * The integration's intake capability providing incident.io access. Required\n * for everything beyond the disabled `status` route.\n */\n incidentio?: { intake: Intake; id: string };\n /**\n * Credential mode reported on the status route so the SPA can tell a\n * deployment-configured API key apart from Platform-managed connections.\n */\n mode?: 'api-key' | 'platform';\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 /**\n * Factory rules ingress for observed follow-ups. When present, a\n * board-scoped listing also materializes new cards through the incident.io\n * event rules — the same automatic intake Linear and Jira have.\n */\n ingestFactoryIssues?: (input: IncidentioRulesIngress) => Promise<unknown>;\n}\n\n/**\n * Narrow the caller's selected incident.io sources to the ones that feed this\n * Factory project. Routing is explicit: a source feeds this Factory only when\n * its binding names both the Factory project and a board. Mirrors the Jira\n * routes' scoping semantics — the generic binding storage is provider-neutral.\n * Shared with the agent tools, which enforce the same source-level\n * authorization before fetching item details.\n */\nexport async 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: 'incidentio' })) {\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 an incident.io request. Intake selections\n * are org-owned, so it requires both a signed-in user and an organization.\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: 'incident.io intake requires an organization. Personal accounts cannot use incident.io intake.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque pagination cursor from the query string. Cursors are\n * server-issued base64url blobs, so anything outside a conservative\n * charset/length is rejected rather than decoded.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 2_048 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map an incident.io read failure to the API response for the SPA. */\nfunction incidentioFetchError(c: RouteContext, err: unknown) {\n if (err instanceof IncidentioApiError && (err.status === 401 || err.status === 403)) {\n return c.json(\n {\n error: 'incidentio_auth_failed',\n message: 'incident.io rejected the configured credentials. Reconnect the incident.io account.',\n },\n 409,\n );\n }\n return c.json({ error: 'incidentio_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\ninterface IncidentioIssuePayload {\n id: string;\n identifier: string;\n title: string;\n url: string;\n author: string | null;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n incident: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n sourceId: string | null;\n}\n\n/**\n * Build the incident.io routes as Mastra `apiRoutes`. When the feature is\n * disabled, returns only the `status` route so the SPA can detect the\n * disabled state.\n */\nexport function buildIncidentioRoutes(options: MountIncidentioRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { incidentio, auth, intake, mode } = options;\n const enabled = Boolean(incidentio) && auth.enabled();\n const modeField = mode ? { mode } : {};\n const diagnostics = (): IncidentioFeatureDiagnostics => ({\n incidentioConfigured: Boolean(incidentio),\n factoryAuthEnabled: auth.enabled(),\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/incidentio/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !incidentio || !intake) {\n return c.json({\n enabled: false,\n configured: Boolean(incidentio),\n ...modeField,\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 if (!tenant.orgId) {\n return c.json({\n enabled: true,\n configured: true,\n ...modeField,\n organizationRequired: true,\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n return c.json({ enabled: true, configured: true, ...modeField, reason: 'ready', diagnostics: diagnostics() });\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 || !incidentio || !intake) {\n return routes;\n }\n\n // ── List bound follow-ups (cursor-paged) ────────────────────────────────\n // Respects the caller's intake config: disabled incident.io intake 404s,\n // and only sources routed to the requested Factory project are listed.\n // Incidents never appear here — only follow-ups feed intake.\n routes.push(\n registerApiRoute('/web/incidentio/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: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n\n const selectedIds = selection.sourceIds ?? [];\n const intakeBoards = factoryProjectId\n ? await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds,\n })\n : null;\n const sourceIds = intakeBoards ? Object.keys(intakeBoards) : selectedIds;\n if (sourceIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const page = await incidentio.intake.listItems({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n sourceIds,\n ...(after ? { cursor: after } : {}),\n });\n const issuePayload: IncidentioIssuePayload[] = [];\n for (const item of page.items) {\n const externalId = item.source.externalId;\n // Incidents stay out of intake even when a stale binding selects\n // the incidents source — only follow-ups are served.\n if (!externalId.startsWith(FOLLOW_UP_PREFIX)) continue;\n const metadata = item.metadata ?? {};\n const identifier = typeof metadata.identifier === 'string' ? metadata.identifier : externalId;\n const titlePrefix = `${identifier}: `;\n issuePayload.push({\n id: externalId,\n identifier,\n title: item.title.startsWith(titlePrefix) ? item.title.slice(titlePrefix.length) : item.title,\n url: item.source.url ?? '',\n author: typeof metadata.author === 'string' ? metadata.author : null,\n state: item.status ?? '',\n stateType: typeof metadata.stateType === 'string' ? metadata.stateType : '',\n priorityLabel: typeof metadata.priority === 'string' ? metadata.priority : '',\n assignee: item.assignee ?? null,\n incident: typeof metadata.incidentioIncidentId === 'string' ? metadata.incidentioIncidentId : null,\n labels: item.labels ?? [],\n createdAt: item.createdAt ?? '',\n updatedAt: item.updatedAt ?? '',\n sourceId: item.sourceId || null,\n });\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,\n intakeBoards,\n });\n }\n return c.json({ issues: issuePayload, nextCursor: page.nextCursor });\n } catch (err) {\n return incidentioFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── Follow-up detail (description for the card panel) ───────────────────\n routes.push(\n registerApiRoute('/web/incidentio/issues/detail', {\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 factoryProjectId = c.req.query('factoryProjectId');\n const issueRef = c.req.query('issueRef');\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 || !ITEM_REF_RE.test(issueRef)) {\n return c.json({ error: 'invalid_issue_ref' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n const intakeBoards = await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds: selection.sourceIds ?? [],\n });\n if (Object.keys(intakeBoards).length === 0) return c.json({ error: 'issue_not_found' }, 404);\n\n try {\n // The item must resolve through a source routed to this Factory —\n // an item from an unbound source reads exactly like one that\n // doesn't exist (same stance as the Jira detail route).\n const dispatch = await incidentio.intake.resolveIntakeDispatch?.({\n orgId: resolved.tenant.orgId,\n externalSource: { type: 'issue', externalId: issueRef },\n });\n if (!dispatch || !dispatch.sourceId || !(dispatch.sourceId in intakeBoards)) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n const issue = await incidentio.intake.getIssue({\n connection: dispatch.connection,\n issueId: dispatch.issueId,\n });\n if (!issue) return c.json({ error: 'issue_not_found' }, 404);\n return c.json({\n issue: {\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n description: issue.description ?? null,\n },\n });\n } catch (err) {\n return incidentioFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/connect/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,EAAE,iBAAiB,EAAoB,MAAM,kBAAkB,CAAC;AASvE,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,wBAAwB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAG9E,CAAC;AAmBF,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AA2CD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iCAAiC,GAAG,QAAQ,EAAE,CAgFjG"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/connect/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,EAAE,iBAAiB,EAAoB,MAAM,kBAAkB,CAAC;AASvE,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,wBAAwB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAI9E,CAAC;AAmBF,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AA2CD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iCAAiC,GAAG,QAAQ,EAAE,CAgFjG"}
@@ -19,6 +19,10 @@ const PLATFORM_CONNECT_PROVIDERS = {
19
19
  "incident-io": {
20
20
  integrationId: "incident-io",
21
21
  connectionIntegrationIds: ["incident-io"]
22
+ },
23
+ gitlab: {
24
+ integrationId: "gitlab",
25
+ connectionIntegrationIds: ["gitlab"]
22
26
  }
23
27
  };
24
28
  async function resolveOrgTenant(c, auth) {
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","names":[],"sources":["../../../../src/integrations/platform/connect/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` that let the Factory SPA connect and reconnect\n * Platform-managed provider accounts without leaving Factory.\n *\n * The browser cannot talk to the integrations service itself — it has no\n * Platform session — so these routes mint Nango connect/reconnect sessions\n * server-side with the deploy's machine credentials\n * (`MASTRA_PLATFORM_ACCESS_TOKEN`/`MASTRA_PLATFORM_SECRET_KEY`) and hand the\n * short-lived session token back to the SPA, which drives the provider's\n * OAuth popup headlessly via `@nangohq/frontend`. Gating mirrors the\n * Platform's own rule for these providers: a signed-in organization member\n * (org:write parity with Linear), not an organization admin.\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 { PlatformApiClient, PlatformApiError } from '../api-client.js';\n\ntype RouteContext = Context;\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\nexport interface PlatformConnectProvider {\n /** Platform integration id used to mint connect sessions. */\n integrationId: string;\n /** Connection `integrationId` variants that belong to this provider. */\n connectionIntegrationIds: readonly string[];\n}\n\n/**\n * Providers the Factory SPA may connect through these routes. Keys are the\n * SPA-facing provider slugs; `integrationId` is the Platform catalog id used\n * for new connect sessions. Provider-specific PRs extend this registry when\n * their Factory integrations are available.\n */\nexport const PLATFORM_CONNECT_PROVIDERS: Record<string, PlatformConnectProvider> = {\n jira: { integrationId: 'jira', connectionIntegrationIds: ['jira'] },\n 'incident-io': { integrationId: 'incident-io', connectionIntegrationIds: ['incident-io'] },\n};\n\ninterface PlatformConnectionRow {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n displayName?: string | null;\n connectedAt?: string;\n}\n\ninterface PlatformSessionResponse {\n connectionId: string;\n integrationId: string;\n connectUrl: string;\n sessionToken: string;\n expiresAt: string;\n}\n\nexport interface BuildPlatformConnectRoutesOptions {\n auth: RouteAuth;\n client: PlatformApiClient;\n}\n\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n if (!auth.enabled()) {\n return { response: c.json({ error: 'auth_disabled', message: 'Factory auth is not enabled.' }, 403) };\n }\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: 'Provider connections require an organization. Personal accounts cannot connect providers.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\nfunction providerFromParam(c: RouteContext): PlatformConnectProvider | null {\n const provider = c.req.param('provider');\n return provider ? (PLATFORM_CONNECT_PROVIDERS[provider] ?? null) : null;\n}\n\nfunction platformError(c: RouteContext, err: unknown) {\n if (err instanceof PlatformApiError) {\n if (err.status === 409) {\n return c.json({ error: 'session_pending', message: err.message }, 409);\n }\n if (err.status === 403) {\n return c.json({ error: 'platform_forbidden', message: err.message }, 403);\n }\n }\n return c.json({ error: 'platform_request_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the `/web/integrations/platform/*` routes. Callers only mount these\n * when Platform machine credentials are configured.\n */\nexport function buildPlatformConnectRoutes(options: BuildPlatformConnectRoutesOptions): ApiRoute[] {\n const { auth, client } = options;\n\n return [\n registerApiRoute('/web/integrations/platform/:provider/connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n try {\n const result = await client.request<{ connections: PlatformConnectionRow[] }>('GET', '/v2/connections');\n const connections = result.connections.filter(connection =>\n provider.connectionIntegrationIds.includes(connection.integrationId),\n );\n return c.json({ connections });\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n registerApiRoute('/web/integrations/platform/:provider/connect-session', {\n method: 'POST',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n try {\n const session = await client.request<PlatformSessionResponse>(\n 'POST',\n `/v2/integrations/${encodeURIComponent(provider.integrationId)}/connect-sessions`,\n {},\n );\n c.header('Cache-Control', 'no-store');\n return c.json(session, 201);\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n registerApiRoute('/web/integrations/platform/:provider/connections/:connectionId/reconnect-session', {\n method: 'POST',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n const connectionId = c.req.param('connectionId');\n if (!connectionId) return c.json({ error: 'connection_required' }, 400);\n try {\n // Confirm the connection belongs to this provider before minting so\n // one provider's page cannot reconnect a different provider's\n // connection through this route family.\n const listed = await client.request<{ connections: PlatformConnectionRow[] }>('GET', '/v2/connections');\n const connection = listed.connections.find(\n candidate =>\n candidate.id === connectionId && provider.connectionIntegrationIds.includes(candidate.integrationId),\n );\n if (!connection) return c.json({ error: 'connection_not_found' }, 404);\n const session = await client.request<PlatformSessionResponse>(\n 'POST',\n `/v2/connections/${encodeURIComponent(connectionId)}/reconnect-session`,\n {},\n );\n c.header('Cache-Control', 'no-store');\n return c.json(session, 201);\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../../../../src/integrations/platform/connect/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` that let the Factory SPA connect and reconnect\n * Platform-managed provider accounts without leaving Factory.\n *\n * The browser cannot talk to the integrations service itself — it has no\n * Platform session — so these routes mint Nango connect/reconnect sessions\n * server-side with the deploy's machine credentials\n * (`MASTRA_PLATFORM_ACCESS_TOKEN`/`MASTRA_PLATFORM_SECRET_KEY`) and hand the\n * short-lived session token back to the SPA, which drives the provider's\n * OAuth popup headlessly via `@nangohq/frontend`. Gating mirrors the\n * Platform's own rule for these providers: a signed-in organization member\n * (org:write parity with Linear), not an organization admin.\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 { PlatformApiClient, PlatformApiError } from '../api-client.js';\n\ntype RouteContext = Context;\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\nexport interface PlatformConnectProvider {\n /** Platform integration id used to mint connect sessions. */\n integrationId: string;\n /** Connection `integrationId` variants that belong to this provider. */\n connectionIntegrationIds: readonly string[];\n}\n\n/**\n * Providers the Factory SPA may connect through these routes. Keys are the\n * SPA-facing provider slugs; `integrationId` is the Platform catalog id used\n * for new connect sessions. Provider-specific PRs extend this registry when\n * their Factory integrations are available.\n */\nexport const PLATFORM_CONNECT_PROVIDERS: Record<string, PlatformConnectProvider> = {\n jira: { integrationId: 'jira', connectionIntegrationIds: ['jira'] },\n 'incident-io': { integrationId: 'incident-io', connectionIntegrationIds: ['incident-io'] },\n gitlab: { integrationId: 'gitlab', connectionIntegrationIds: ['gitlab'] },\n};\n\ninterface PlatformConnectionRow {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n displayName?: string | null;\n connectedAt?: string;\n}\n\ninterface PlatformSessionResponse {\n connectionId: string;\n integrationId: string;\n connectUrl: string;\n sessionToken: string;\n expiresAt: string;\n}\n\nexport interface BuildPlatformConnectRoutesOptions {\n auth: RouteAuth;\n client: PlatformApiClient;\n}\n\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n if (!auth.enabled()) {\n return { response: c.json({ error: 'auth_disabled', message: 'Factory auth is not enabled.' }, 403) };\n }\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: 'Provider connections require an organization. Personal accounts cannot connect providers.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\nfunction providerFromParam(c: RouteContext): PlatformConnectProvider | null {\n const provider = c.req.param('provider');\n return provider ? (PLATFORM_CONNECT_PROVIDERS[provider] ?? null) : null;\n}\n\nfunction platformError(c: RouteContext, err: unknown) {\n if (err instanceof PlatformApiError) {\n if (err.status === 409) {\n return c.json({ error: 'session_pending', message: err.message }, 409);\n }\n if (err.status === 403) {\n return c.json({ error: 'platform_forbidden', message: err.message }, 403);\n }\n }\n return c.json({ error: 'platform_request_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\n/**\n * Build the `/web/integrations/platform/*` routes. Callers only mount these\n * when Platform machine credentials are configured.\n */\nexport function buildPlatformConnectRoutes(options: BuildPlatformConnectRoutesOptions): ApiRoute[] {\n const { auth, client } = options;\n\n return [\n registerApiRoute('/web/integrations/platform/:provider/connections', {\n method: 'GET',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n try {\n const result = await client.request<{ connections: PlatformConnectionRow[] }>('GET', '/v2/connections');\n const connections = result.connections.filter(connection =>\n provider.connectionIntegrationIds.includes(connection.integrationId),\n );\n return c.json({ connections });\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n registerApiRoute('/web/integrations/platform/:provider/connect-session', {\n method: 'POST',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n try {\n const session = await client.request<PlatformSessionResponse>(\n 'POST',\n `/v2/integrations/${encodeURIComponent(provider.integrationId)}/connect-sessions`,\n {},\n );\n c.header('Cache-Control', 'no-store');\n return c.json(session, 201);\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n registerApiRoute('/web/integrations/platform/:provider/connections/:connectionId/reconnect-session', {\n method: 'POST',\n requiresAuth: false,\n handler: async rawContext => {\n const c = loose(rawContext);\n const provider = providerFromParam(c);\n if (!provider) return c.json({ error: 'unknown_provider' }, 404);\n const resolved = await resolveOrgTenant(c, auth);\n if ('response' in resolved) return resolved.response;\n const connectionId = c.req.param('connectionId');\n if (!connectionId) return c.json({ error: 'connection_required' }, 400);\n try {\n // Confirm the connection belongs to this provider before minting so\n // one provider's page cannot reconnect a different provider's\n // connection through this route family.\n const listed = await client.request<{ connections: PlatformConnectionRow[] }>('GET', '/v2/connections');\n const connection = listed.connections.find(\n candidate =>\n candidate.id === connectionId && provider.connectionIntegrationIds.includes(candidate.integrationId),\n );\n if (!connection) return c.json({ error: 'connection_not_found' }, 404);\n const session = await client.request<PlatformSessionResponse>(\n 'POST',\n `/v2/connections/${encodeURIComponent(connectionId)}/reconnect-session`,\n {},\n );\n c.header('Cache-Control', 'no-store');\n return c.json(session, 201);\n } catch (err) {\n return platformError(c, err);\n }\n },\n }),\n ];\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/incidentio/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAIV,MAAM,EAUP,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAI9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAcvG,OAAO,EAAqD,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEnH,MAAM,WAAW,mCAAmC;IAClD,YAAY,CAAC,EAAE,uBAAuB,CAAC;IACvC,wEAAwE;IACxE,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAaD,UAAU,6BAA6B;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,cAAc;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAcD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYzE;AAoDD,qBAAa,6BAA8B,YAAW,kBAAkB;;IACtE,QAAQ,CAAC,EAAE,gBAAgB;gBAaf,MAAM,GAAE,mCAAwC;IAO5D,kFAAkF;IAClF,IAAI,KAAK,IAAI,oBAAoB,CAEhC;IAED,UAAU,CAAC,EACT,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;QACD,QAAQ,EAAE,sBAAsB,CAAC;QACjC,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;KACvB,GAAG,IAAI;IAMR,oFAAoF;IACpF,IAAI,aAAa,IAAI,aAAa,CAKjC;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED,sFAAsF;IACtF,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqB9D;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAK/E,eAAe,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC;IAS3D,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI9C,WAAW,IAAI,IAAI;IAiEnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAQrB;IA4JF,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAS3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGvC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/incidentio/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAIV,MAAM,EAUP,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAI9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAcvG,OAAO,EAAqD,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEnH,MAAM,WAAW,mCAAmC;IAClD,YAAY,CAAC,EAAE,uBAAuB,CAAC;IACvC,wEAAwE;IACxE,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAaD,UAAU,6BAA6B;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,QAAQ,GAAG,cAAc,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,cAAc;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAcD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYzE;AAoDD,qBAAa,6BAA8B,YAAW,kBAAkB;;IACtE,QAAQ,CAAC,EAAE,gBAAgB;gBAaf,MAAM,GAAE,mCAAwC;IAO5D,kFAAkF;IAClF,IAAI,KAAK,IAAI,oBAAoB,CAEhC;IAED,UAAU,CAAC,EACT,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;QACD,QAAQ,EAAE,sBAAsB,CAAC;QACjC,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;KACvB,GAAG,IAAI;IAMR,oFAAoF;IACpF,IAAI,aAAa,IAAI,aAAa,CAKjC;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED,sFAAsF;IACtF,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqB9D;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAK/E,eAAe,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC;IAS3D,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI9C,WAAW,IAAI,IAAI;IAiEnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAQrB;IA4JF,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAU3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGvC"}
@@ -386,6 +386,7 @@ var PlatformIncidentioIntegration = class {
386
386
  routes(ctx) {
387
387
  return buildIncidentioRoutes({
388
388
  incidentio: this,
389
+ mode: "platform",
389
390
  auth: ctx.auth,
390
391
  intake: ctx.storage.intake,
391
392
  ingestFactoryIssues: attachIncidentioRules(this, ctx)
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":["#clientConfig","#platformClient","#endpointHost","#intakeByConnectionId","#rules","#orgIdByResourceId","#projects","#auth","#intakeStorage","#activeConnections","#connectionContexts","#connectionIntake","#intakeForConnection","#resolveIntakeDispatch","#listSources","#listItems","#listIssues","#getIssue","#createComment","#updateIssue","#scopeResolvedDispatch","#withBaseSource"],"sources":["../../../../src/integrations/platform/incidentio/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 { IntakeStorage } from '../../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildIncidentioAgentTools } from '../../incidentio/agent-tools.js';\nimport { IncidentioApiClient, IncidentioApiError } from '../../incidentio/api.js';\nimport { resolveIncidentioRules } from '../../incidentio/default-rules.js';\nimport type { IncidentioEventRules, IncidentioRuleOverrides } from '../../incidentio/default-rules.js';\nimport {\n createIncidentioIntake,\n INCIDENTIO_FOLLOW_UPS_SOURCE_ID,\n INCIDENTIO_INCIDENTS_SOURCE_ID,\n} from '../../incidentio/intake.js';\nimport { attachIncidentioIssueReconciler } from '../../incidentio/issue-reconciler.js';\nimport {\n incidentioReconciliationEnabled,\n incidentioReconciliationInterval,\n} from '../../incidentio/reconciliation-config.js';\nimport { buildIncidentioRoutes } from '../../incidentio/routes.js';\nimport { attachIncidentioRules } from '../../incidentio/rules.js';\nimport { IssueReconcileWorker } from '../../issue-reconcile-worker.js';\nimport { PlatformApiClient, platformApiClientConfigFromEnv, type PlatformApiClientConfig } from '../api-client.js';\n\nexport interface PlatformIncidentioIntegrationConfig {\n clientConfig?: PlatformApiClientConfig;\n /** Per-event replacements for the default Factory incident.io rules. */\n rules?: IncidentioRuleOverrides;\n}\n\n/**\n * Provider config key registered for incident.io in Mastra Platform's\n * integrations catalog. Connections are discovered at runtime — no\n * deploy-time connection ID wiring.\n */\nconst PLATFORM_INCIDENTIO_PROVIDER_CONFIG_KEY = 'incident-io';\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 CONNECTION_TOKEN_PREFIX = 'incidentio-connection:';\nconst SCOPED_SOURCE_PREFIX = 'incidentio-source:';\nconst BASE_SOURCE_IDS = [INCIDENTIO_INCIDENTS_SOURCE_ID, INCIDENTIO_FOLLOW_UPS_SOURCE_ID] as const;\n\ninterface PlatformIntegrationConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\ninterface ScopedSourceId {\n connectionId: string;\n sourceId: string;\n}\n\n/** An intake plus the exact inner connection it was resolved for, so operations discover active connections once. */\ninterface ResolvedIncidentioIntake {\n intake: Intake;\n connection: IntegrationConnection;\n}\n\ninterface AggregatePageCursor {\n /** Connection ID the cursor points at; keyed by ID so the active list can change between pages. */\n connectionId: string;\n inner?: string;\n}\n\nexport function encodeScopedSourceId(connectionId: string, sourceId: string): string {\n return `${SCOPED_SOURCE_PREFIX}${Buffer.from(JSON.stringify({ connectionId, sourceId })).toString('base64url')}`;\n}\n\nexport function decodeScopedSourceId(value: string): ScopedSourceId | null {\n if (!value.startsWith(SCOPED_SOURCE_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(value.slice(SCOPED_SOURCE_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n return typeof parsed.connectionId === 'string' && typeof parsed.sourceId === 'string'\n ? { connectionId: parsed.connectionId, sourceId: parsed.sourceId }\n : null;\n } catch {\n return null;\n }\n}\n\nfunction incidentioConnection(connectionId: string): IntegrationConnection {\n return { type: 'oauth', accessToken: `${CONNECTION_TOKEN_PREFIX}${connectionId}` };\n}\n\nfunction connectionIdFromConnection(connection: IntegrationConnection): string | null {\n if (connection.type !== 'oauth' || !connection.accessToken.startsWith(CONNECTION_TOKEN_PREFIX)) return null;\n return connection.accessToken.slice(CONNECTION_TOKEN_PREFIX.length) || null;\n}\n\n/**\n * Base (per-connection) source ids selected for a connection. Scoped ids\n * select their own connection; legacy unscoped ids (`incidentio:incidents`)\n * from pre-discovery deploys apply to every connection.\n */\nfunction baseSourceIdsFor(connectionId: string, sourceIds: string[]): string[] {\n const selected = new Set<string>();\n for (const sourceId of sourceIds) {\n const scoped = decodeScopedSourceId(sourceId);\n if (scoped) {\n if (scoped.connectionId === connectionId) selected.add(scoped.sourceId);\n continue;\n }\n if ((BASE_SOURCE_IDS as readonly string[]).includes(sourceId)) selected.add(sourceId);\n }\n return [...selected];\n}\n\nfunction encodePageCursor(cursor: AggregatePageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction decodePageCursor(cursor: string | undefined): AggregatePageCursor | null {\n if (!cursor) return null;\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Partial<AggregatePageCursor>;\n if (typeof parsed.connectionId !== 'string' || !parsed.connectionId) return null;\n return {\n connectionId: parsed.connectionId,\n ...(typeof parsed.inner === 'string' && parsed.inner ? { inner: parsed.inner } : {}),\n };\n } catch {\n return null;\n }\n}\n\ninterface IncidentioConnectionContext {\n connection: PlatformIntegrationConnection;\n intake: Intake;\n}\n\nexport class PlatformIncidentioIntegration implements FactoryIntegration {\n readonly id = 'incidentio';\n readonly #clientConfig: PlatformApiClientConfig;\n readonly #platformClient: PlatformApiClient;\n readonly #endpointHost: string;\n readonly #intakeByConnectionId = new Map<string, Intake>();\n readonly #rules: IncidentioEventRules;\n\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n #intakeStorage: IntakeStorage | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n constructor(config: PlatformIncidentioIntegrationConfig = {}) {\n this.#clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#platformClient = new PlatformApiClient(this.#clientConfig);\n this.#endpointHost = new URL(this.#clientConfig.baseUrl).host;\n this.#rules = resolveIncidentioRules(config.rules);\n }\n\n /** Event rules driving automatic follow-up materialization and close handling. */\n get rules(): IncidentioEventRules {\n return this.#rules;\n }\n\n initialize({\n projects,\n auth,\n intake,\n }: {\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n intake: IntakeStorage;\n }): void {\n this.#projects = projects;\n this.#auth = auth;\n this.#intakeStorage = intake;\n }\n\n /** Cross-integration intake selection/binding domain — agent-tool authorization. */\n get intakeStorage(): IntakeStorage {\n if (!this.#intakeStorage) {\n throw new Error('PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#intakeStorage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /** Whether the host runs with web auth enabled; every surface is inert without it. */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n /**\n * Map a session's resourceId (the factory project id) to its owning org.\n * Same caching semantics as the Jira integration: definitive misses are\n * cached, transient database failures are not.\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_RE.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /**\n * Org-scoped agent tools: the read-only follow-up detail tool for sessions\n * whose resource is a factory project with an active Platform-managed\n * incident.io connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n if (!(await this.hasActiveConnections())) return {};\n return buildIncidentioAgentTools({ requestContext: args.requestContext, incidentio: this });\n }\n\n async listConnections(): Promise<PlatformIntegrationConnection[]> {\n const providerKey = encodeURIComponent(PLATFORM_INCIDENTIO_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 clearCaches(): void {\n this.#intakeByConnectionId.clear();\n this.#orgIdByResourceId.clear();\n }\n\n async #activeConnections(): Promise<PlatformIntegrationConnection[]> {\n const connections = (await this.listConnections()).filter(connection => connection.status === 'active');\n // Stable ordering keeps aggregate page cursors valid across requests.\n return connections.slice().sort((left, right) => left.id.localeCompare(right.id));\n }\n\n #connectionIntake(connectionId: string): Intake {\n const cached = this.#intakeByConnectionId.get(connectionId);\n if (cached) return cached;\n const proxyBaseUrl = `${this.#clientConfig.baseUrl.replace(/\\/+$/, '')}/v2/connections/${encodeURIComponent(\n connectionId,\n )}/proxy`;\n const intake = createIncidentioIntake({\n api: new IncidentioApiClient({\n baseUrl: proxyBaseUrl,\n accessToken: this.#clientConfig.accessToken,\n ...(this.#clientConfig.fetchImpl ? { fetchImpl: this.#clientConfig.fetchImpl } : {}),\n }),\n connection: incidentioConnection(connectionId),\n });\n this.#intakeByConnectionId.set(connectionId, intake);\n return intake;\n }\n\n async #connectionContexts(): Promise<IncidentioConnectionContext[]> {\n return (await this.#activeConnections()).map(connection => ({\n connection,\n intake: this.#connectionIntake(connection.id),\n }));\n }\n\n /**\n * Resolve the intake for an explicit connection token, or fall back to the\n * sole active connection so legacy single-connection callers keep working.\n *\n * A caller-supplied connection token is only a claim: the id it carries\n * must match a connection the deployment actually discovered as active\n * before any proxy request is minted with the Platform credential.\n */\n async #intakeForConnection(connection: IntegrationConnection): Promise<ResolvedIncidentioIntake> {\n const connectionId = connectionIdFromConnection(connection);\n const active = await this.#activeConnections();\n if (connectionId) {\n if (!active.some(candidate => candidate.id === connectionId)) {\n throw new IncidentioApiError('incident.io connection is unavailable or requires reauthentication.', 401);\n }\n return { intake: this.#connectionIntake(connectionId), connection: incidentioConnection(connectionId) };\n }\n if (active.length === 1) {\n const soleId = active[0]!.id;\n return { intake: this.#connectionIntake(soleId), connection: incidentioConnection(soleId) };\n }\n throw new IncidentioApiError(\n active.length === 0\n ? 'incident.io connection is unavailable or requires reauthentication.'\n : 'incident.io request must identify a connection when multiple accounts are connected.',\n active.length === 0 ? 401 : 400,\n );\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(input: ResolveIntakeDispatchInput): Promise<ResolvedIntakeDispatch | null> {\n if (input.externalSource.type !== 'issue') return null;\n const externalId = input.externalSource.externalId;\n const contexts = await this.#connectionContexts();\n if (contexts.length === 0) return null;\n if (contexts.length === 1) {\n const resolved = await contexts[0]!.intake.resolveIntakeDispatch?.(input);\n return resolved ? this.#scopeResolvedDispatch(contexts[0]!.connection.id, resolved) : null;\n }\n // Multiple accounts: item references carry no connection id, so probe\n // each connection for the item — first account that knows it wins\n // (incident.io ids are globally unique ULIDs, collisions are not a\n // practical concern and this capability is read-mostly).\n let firstError: unknown;\n for (const context of contexts) {\n try {\n const issue = await context.intake.getIssue({\n connection: incidentioConnection(context.connection.id),\n issueId: externalId,\n });\n if (!issue) continue;\n const resolved = await context.intake.resolveIntakeDispatch?.(input);\n return resolved ? this.#scopeResolvedDispatch(context.connection.id, resolved) : null;\n } catch (error) {\n if (error instanceof IncidentioApiError && error.status === 404) continue;\n firstError ??= error;\n }\n }\n if (firstError !== undefined) throw firstError;\n return null;\n }\n\n #scopeResolvedDispatch(connectionId: string, resolved: ResolvedIntakeDispatch): ResolvedIntakeDispatch {\n return {\n ...resolved,\n connection: incidentioConnection(connectionId),\n ...(resolved.sourceId ? { sourceId: encodeScopedSourceId(connectionId, resolved.sourceId) } : {}),\n };\n }\n\n async #listSources(): Promise<IntakeSource[]> {\n const contexts = await this.#connectionContexts();\n const sources: IntakeSource[] = [];\n for (const context of contexts) {\n const account = context.connection.accountLabel;\n const inner = await context.intake.listSources({ orgId: '', userId: '' });\n for (const source of inner) {\n sources.push({\n ...source,\n id: encodeScopedSourceId(context.connection.id, source.id),\n name: contexts.length > 1 && account ? `${source.name} (${account})` : source.name,\n metadata: {\n ...(source.metadata ?? {}),\n connectionId: context.connection.id,\n ...(account ? { account } : {}),\n },\n });\n }\n }\n return sources;\n }\n\n async #listItems(input: ListIntakeItemsInput): Promise<IntakeItemPage> {\n if (input.sourceIds.length === 0) return { items: [], nextCursor: null };\n const contexts = await this.#connectionContexts();\n const cursor = decodePageCursor(input.cursor);\n\n // Resume by connection ID: the active list can change between pages, so an\n // index would replay or skip connections. When the cursor's connection is\n // gone, resume at its sorted successor and drop the inner cursor.\n let startIndex = 0;\n let exactMatch = false;\n if (cursor) {\n const index = contexts.findIndex(context => context.connection.id === cursor.connectionId);\n exactMatch = index !== -1;\n startIndex = exactMatch\n ? index\n : contexts.findIndex(context => context.connection.id.localeCompare(cursor.connectionId) > 0);\n if (startIndex === -1) return { items: [], nextCursor: null };\n }\n\n for (let index = startIndex; index < contexts.length; index++) {\n const context = contexts[index]!;\n const baseSourceIds = baseSourceIdsFor(context.connection.id, input.sourceIds);\n if (baseSourceIds.length === 0) continue;\n const page = await context.intake.listItems({\n orgId: input.orgId,\n userId: input.userId,\n sourceIds: baseSourceIds,\n ...(index === startIndex && exactMatch && cursor?.inner ? { cursor: cursor.inner } : {}),\n });\n const nextCursor = page.nextCursor\n ? encodePageCursor({ connectionId: context.connection.id, inner: page.nextCursor })\n : index + 1 < contexts.length\n ? encodePageCursor({ connectionId: contexts[index + 1]!.connection.id })\n : null;\n return {\n items: page.items.map(item => ({\n ...item,\n sourceId: encodeScopedSourceId(context.connection.id, item.sourceId),\n metadata: { ...(item.metadata ?? {}), connectionId: context.connection.id },\n })),\n nextCursor,\n };\n }\n return { items: [], nextCursor: null };\n }\n\n async #listIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const resolved = await this.#intakeForConnection(input.connection);\n // Scope to the connection that was actually resolved (which may be the\n // sole-connection fallback), so a source id scoped to a different\n // connection is dropped instead of being read against this one.\n const connectionId = connectionIdFromConnection(resolved.connection);\n const baseSourceIds = connectionId\n ? baseSourceIdsFor(connectionId, input.sourceIds)\n : input.sourceIds.map(sourceId => decodeScopedSourceId(sourceId)?.sourceId ?? sourceId);\n return resolved.intake.listIssues({\n ...input,\n connection: resolved.connection,\n sourceIds: baseSourceIds,\n });\n }\n\n async #getIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.getIssue({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n async #createComment(input: CreateIntakeCommentInput): Promise<CreatedIntakeComment | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.createComment({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n async #updateIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.updateIssue({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n #withBaseSource<T extends { sourceId?: string }>(input: T): T {\n if (!input.sourceId) return input;\n const scoped = decodeScopedSourceId(input.sourceId);\n return scoped ? { ...input, sourceId: scoped.sourceId } : input;\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!incidentioReconciliationEnabled()) return [];\n const reconcile = attachIncidentioIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = incidentioReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n /**\n * The integration's HTTP surface: `/web/incidentio/*` Mastra `apiRoutes`\n * (status + follow-up listing/detail for Intake), aggregated across every\n * discovered Platform connection. A board-scoped listing also feeds the\n * incident.io event rules.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildIncidentioRoutes({\n incidentio: this,\n auth: ctx.auth,\n intake: ctx.storage.intake,\n ingestFactoryIssues: attachIncidentioRules(this, ctx),\n });\n }\n\n diagnostics(): Record<string, unknown> {\n return { configured: true, mode: 'platform', endpointHost: this.#endpointHost };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integration.js","names":["#clientConfig","#platformClient","#endpointHost","#intakeByConnectionId","#rules","#orgIdByResourceId","#projects","#auth","#intakeStorage","#activeConnections","#connectionContexts","#connectionIntake","#intakeForConnection","#resolveIntakeDispatch","#listSources","#listItems","#listIssues","#getIssue","#createComment","#updateIssue","#scopeResolvedDispatch","#withBaseSource"],"sources":["../../../../src/integrations/platform/incidentio/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 { IntakeStorage } from '../../../storage/domains/intake/base.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildIncidentioAgentTools } from '../../incidentio/agent-tools.js';\nimport { IncidentioApiClient, IncidentioApiError } from '../../incidentio/api.js';\nimport { resolveIncidentioRules } from '../../incidentio/default-rules.js';\nimport type { IncidentioEventRules, IncidentioRuleOverrides } from '../../incidentio/default-rules.js';\nimport {\n createIncidentioIntake,\n INCIDENTIO_FOLLOW_UPS_SOURCE_ID,\n INCIDENTIO_INCIDENTS_SOURCE_ID,\n} from '../../incidentio/intake.js';\nimport { attachIncidentioIssueReconciler } from '../../incidentio/issue-reconciler.js';\nimport {\n incidentioReconciliationEnabled,\n incidentioReconciliationInterval,\n} from '../../incidentio/reconciliation-config.js';\nimport { buildIncidentioRoutes } from '../../incidentio/routes.js';\nimport { attachIncidentioRules } from '../../incidentio/rules.js';\nimport { IssueReconcileWorker } from '../../issue-reconcile-worker.js';\nimport { PlatformApiClient, platformApiClientConfigFromEnv, type PlatformApiClientConfig } from '../api-client.js';\n\nexport interface PlatformIncidentioIntegrationConfig {\n clientConfig?: PlatformApiClientConfig;\n /** Per-event replacements for the default Factory incident.io rules. */\n rules?: IncidentioRuleOverrides;\n}\n\n/**\n * Provider config key registered for incident.io in Mastra Platform's\n * integrations catalog. Connections are discovered at runtime — no\n * deploy-time connection ID wiring.\n */\nconst PLATFORM_INCIDENTIO_PROVIDER_CONFIG_KEY = 'incident-io';\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 CONNECTION_TOKEN_PREFIX = 'incidentio-connection:';\nconst SCOPED_SOURCE_PREFIX = 'incidentio-source:';\nconst BASE_SOURCE_IDS = [INCIDENTIO_INCIDENTS_SOURCE_ID, INCIDENTIO_FOLLOW_UPS_SOURCE_ID] as const;\n\ninterface PlatformIntegrationConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\ninterface ScopedSourceId {\n connectionId: string;\n sourceId: string;\n}\n\n/** An intake plus the exact inner connection it was resolved for, so operations discover active connections once. */\ninterface ResolvedIncidentioIntake {\n intake: Intake;\n connection: IntegrationConnection;\n}\n\ninterface AggregatePageCursor {\n /** Connection ID the cursor points at; keyed by ID so the active list can change between pages. */\n connectionId: string;\n inner?: string;\n}\n\nexport function encodeScopedSourceId(connectionId: string, sourceId: string): string {\n return `${SCOPED_SOURCE_PREFIX}${Buffer.from(JSON.stringify({ connectionId, sourceId })).toString('base64url')}`;\n}\n\nexport function decodeScopedSourceId(value: string): ScopedSourceId | null {\n if (!value.startsWith(SCOPED_SOURCE_PREFIX)) return null;\n try {\n const parsed = JSON.parse(\n Buffer.from(value.slice(SCOPED_SOURCE_PREFIX.length), 'base64url').toString('utf8'),\n ) as Record<string, unknown>;\n return typeof parsed.connectionId === 'string' && typeof parsed.sourceId === 'string'\n ? { connectionId: parsed.connectionId, sourceId: parsed.sourceId }\n : null;\n } catch {\n return null;\n }\n}\n\nfunction incidentioConnection(connectionId: string): IntegrationConnection {\n return { type: 'oauth', accessToken: `${CONNECTION_TOKEN_PREFIX}${connectionId}` };\n}\n\nfunction connectionIdFromConnection(connection: IntegrationConnection): string | null {\n if (connection.type !== 'oauth' || !connection.accessToken.startsWith(CONNECTION_TOKEN_PREFIX)) return null;\n return connection.accessToken.slice(CONNECTION_TOKEN_PREFIX.length) || null;\n}\n\n/**\n * Base (per-connection) source ids selected for a connection. Scoped ids\n * select their own connection; legacy unscoped ids (`incidentio:incidents`)\n * from pre-discovery deploys apply to every connection.\n */\nfunction baseSourceIdsFor(connectionId: string, sourceIds: string[]): string[] {\n const selected = new Set<string>();\n for (const sourceId of sourceIds) {\n const scoped = decodeScopedSourceId(sourceId);\n if (scoped) {\n if (scoped.connectionId === connectionId) selected.add(scoped.sourceId);\n continue;\n }\n if ((BASE_SOURCE_IDS as readonly string[]).includes(sourceId)) selected.add(sourceId);\n }\n return [...selected];\n}\n\nfunction encodePageCursor(cursor: AggregatePageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction decodePageCursor(cursor: string | undefined): AggregatePageCursor | null {\n if (!cursor) return null;\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Partial<AggregatePageCursor>;\n if (typeof parsed.connectionId !== 'string' || !parsed.connectionId) return null;\n return {\n connectionId: parsed.connectionId,\n ...(typeof parsed.inner === 'string' && parsed.inner ? { inner: parsed.inner } : {}),\n };\n } catch {\n return null;\n }\n}\n\ninterface IncidentioConnectionContext {\n connection: PlatformIntegrationConnection;\n intake: Intake;\n}\n\nexport class PlatformIncidentioIntegration implements FactoryIntegration {\n readonly id = 'incidentio';\n readonly #clientConfig: PlatformApiClientConfig;\n readonly #platformClient: PlatformApiClient;\n readonly #endpointHost: string;\n readonly #intakeByConnectionId = new Map<string, Intake>();\n readonly #rules: IncidentioEventRules;\n\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n #intakeStorage: IntakeStorage | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n constructor(config: PlatformIncidentioIntegrationConfig = {}) {\n this.#clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#platformClient = new PlatformApiClient(this.#clientConfig);\n this.#endpointHost = new URL(this.#clientConfig.baseUrl).host;\n this.#rules = resolveIncidentioRules(config.rules);\n }\n\n /** Event rules driving automatic follow-up materialization and close handling. */\n get rules(): IncidentioEventRules {\n return this.#rules;\n }\n\n initialize({\n projects,\n auth,\n intake,\n }: {\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n intake: IntakeStorage;\n }): void {\n this.#projects = projects;\n this.#auth = auth;\n this.#intakeStorage = intake;\n }\n\n /** Cross-integration intake selection/binding domain — agent-tool authorization. */\n get intakeStorage(): IntakeStorage {\n if (!this.#intakeStorage) {\n throw new Error('PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#intakeStorage;\n }\n\n /** Factory projects domain — maps a session's resourceId to its owning org. */\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error('PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /** Whether the host runs with web auth enabled; every surface is inert without it. */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n /**\n * Map a session's resourceId (the factory project id) to its owning org.\n * Same caching semantics as the Jira integration: definitive misses are\n * cached, transient database failures are not.\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_RE.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /**\n * Org-scoped agent tools: the read-only follow-up detail tool for sessions\n * whose resource is a factory project with an active Platform-managed\n * incident.io connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n if (!(await this.hasActiveConnections())) return {};\n return buildIncidentioAgentTools({ requestContext: args.requestContext, incidentio: this });\n }\n\n async listConnections(): Promise<PlatformIntegrationConnection[]> {\n const providerKey = encodeURIComponent(PLATFORM_INCIDENTIO_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 clearCaches(): void {\n this.#intakeByConnectionId.clear();\n this.#orgIdByResourceId.clear();\n }\n\n async #activeConnections(): Promise<PlatformIntegrationConnection[]> {\n const connections = (await this.listConnections()).filter(connection => connection.status === 'active');\n // Stable ordering keeps aggregate page cursors valid across requests.\n return connections.slice().sort((left, right) => left.id.localeCompare(right.id));\n }\n\n #connectionIntake(connectionId: string): Intake {\n const cached = this.#intakeByConnectionId.get(connectionId);\n if (cached) return cached;\n const proxyBaseUrl = `${this.#clientConfig.baseUrl.replace(/\\/+$/, '')}/v2/connections/${encodeURIComponent(\n connectionId,\n )}/proxy`;\n const intake = createIncidentioIntake({\n api: new IncidentioApiClient({\n baseUrl: proxyBaseUrl,\n accessToken: this.#clientConfig.accessToken,\n ...(this.#clientConfig.fetchImpl ? { fetchImpl: this.#clientConfig.fetchImpl } : {}),\n }),\n connection: incidentioConnection(connectionId),\n });\n this.#intakeByConnectionId.set(connectionId, intake);\n return intake;\n }\n\n async #connectionContexts(): Promise<IncidentioConnectionContext[]> {\n return (await this.#activeConnections()).map(connection => ({\n connection,\n intake: this.#connectionIntake(connection.id),\n }));\n }\n\n /**\n * Resolve the intake for an explicit connection token, or fall back to the\n * sole active connection so legacy single-connection callers keep working.\n *\n * A caller-supplied connection token is only a claim: the id it carries\n * must match a connection the deployment actually discovered as active\n * before any proxy request is minted with the Platform credential.\n */\n async #intakeForConnection(connection: IntegrationConnection): Promise<ResolvedIncidentioIntake> {\n const connectionId = connectionIdFromConnection(connection);\n const active = await this.#activeConnections();\n if (connectionId) {\n if (!active.some(candidate => candidate.id === connectionId)) {\n throw new IncidentioApiError('incident.io connection is unavailable or requires reauthentication.', 401);\n }\n return { intake: this.#connectionIntake(connectionId), connection: incidentioConnection(connectionId) };\n }\n if (active.length === 1) {\n const soleId = active[0]!.id;\n return { intake: this.#connectionIntake(soleId), connection: incidentioConnection(soleId) };\n }\n throw new IncidentioApiError(\n active.length === 0\n ? 'incident.io connection is unavailable or requires reauthentication.'\n : 'incident.io request must identify a connection when multiple accounts are connected.',\n active.length === 0 ? 401 : 400,\n );\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(input: ResolveIntakeDispatchInput): Promise<ResolvedIntakeDispatch | null> {\n if (input.externalSource.type !== 'issue') return null;\n const externalId = input.externalSource.externalId;\n const contexts = await this.#connectionContexts();\n if (contexts.length === 0) return null;\n if (contexts.length === 1) {\n const resolved = await contexts[0]!.intake.resolveIntakeDispatch?.(input);\n return resolved ? this.#scopeResolvedDispatch(contexts[0]!.connection.id, resolved) : null;\n }\n // Multiple accounts: item references carry no connection id, so probe\n // each connection for the item — first account that knows it wins\n // (incident.io ids are globally unique ULIDs, collisions are not a\n // practical concern and this capability is read-mostly).\n let firstError: unknown;\n for (const context of contexts) {\n try {\n const issue = await context.intake.getIssue({\n connection: incidentioConnection(context.connection.id),\n issueId: externalId,\n });\n if (!issue) continue;\n const resolved = await context.intake.resolveIntakeDispatch?.(input);\n return resolved ? this.#scopeResolvedDispatch(context.connection.id, resolved) : null;\n } catch (error) {\n if (error instanceof IncidentioApiError && error.status === 404) continue;\n firstError ??= error;\n }\n }\n if (firstError !== undefined) throw firstError;\n return null;\n }\n\n #scopeResolvedDispatch(connectionId: string, resolved: ResolvedIntakeDispatch): ResolvedIntakeDispatch {\n return {\n ...resolved,\n connection: incidentioConnection(connectionId),\n ...(resolved.sourceId ? { sourceId: encodeScopedSourceId(connectionId, resolved.sourceId) } : {}),\n };\n }\n\n async #listSources(): Promise<IntakeSource[]> {\n const contexts = await this.#connectionContexts();\n const sources: IntakeSource[] = [];\n for (const context of contexts) {\n const account = context.connection.accountLabel;\n const inner = await context.intake.listSources({ orgId: '', userId: '' });\n for (const source of inner) {\n sources.push({\n ...source,\n id: encodeScopedSourceId(context.connection.id, source.id),\n name: contexts.length > 1 && account ? `${source.name} (${account})` : source.name,\n metadata: {\n ...(source.metadata ?? {}),\n connectionId: context.connection.id,\n ...(account ? { account } : {}),\n },\n });\n }\n }\n return sources;\n }\n\n async #listItems(input: ListIntakeItemsInput): Promise<IntakeItemPage> {\n if (input.sourceIds.length === 0) return { items: [], nextCursor: null };\n const contexts = await this.#connectionContexts();\n const cursor = decodePageCursor(input.cursor);\n\n // Resume by connection ID: the active list can change between pages, so an\n // index would replay or skip connections. When the cursor's connection is\n // gone, resume at its sorted successor and drop the inner cursor.\n let startIndex = 0;\n let exactMatch = false;\n if (cursor) {\n const index = contexts.findIndex(context => context.connection.id === cursor.connectionId);\n exactMatch = index !== -1;\n startIndex = exactMatch\n ? index\n : contexts.findIndex(context => context.connection.id.localeCompare(cursor.connectionId) > 0);\n if (startIndex === -1) return { items: [], nextCursor: null };\n }\n\n for (let index = startIndex; index < contexts.length; index++) {\n const context = contexts[index]!;\n const baseSourceIds = baseSourceIdsFor(context.connection.id, input.sourceIds);\n if (baseSourceIds.length === 0) continue;\n const page = await context.intake.listItems({\n orgId: input.orgId,\n userId: input.userId,\n sourceIds: baseSourceIds,\n ...(index === startIndex && exactMatch && cursor?.inner ? { cursor: cursor.inner } : {}),\n });\n const nextCursor = page.nextCursor\n ? encodePageCursor({ connectionId: context.connection.id, inner: page.nextCursor })\n : index + 1 < contexts.length\n ? encodePageCursor({ connectionId: contexts[index + 1]!.connection.id })\n : null;\n return {\n items: page.items.map(item => ({\n ...item,\n sourceId: encodeScopedSourceId(context.connection.id, item.sourceId),\n metadata: { ...(item.metadata ?? {}), connectionId: context.connection.id },\n })),\n nextCursor,\n };\n }\n return { items: [], nextCursor: null };\n }\n\n async #listIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const resolved = await this.#intakeForConnection(input.connection);\n // Scope to the connection that was actually resolved (which may be the\n // sole-connection fallback), so a source id scoped to a different\n // connection is dropped instead of being read against this one.\n const connectionId = connectionIdFromConnection(resolved.connection);\n const baseSourceIds = connectionId\n ? baseSourceIdsFor(connectionId, input.sourceIds)\n : input.sourceIds.map(sourceId => decodeScopedSourceId(sourceId)?.sourceId ?? sourceId);\n return resolved.intake.listIssues({\n ...input,\n connection: resolved.connection,\n sourceIds: baseSourceIds,\n });\n }\n\n async #getIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.getIssue({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n async #createComment(input: CreateIntakeCommentInput): Promise<CreatedIntakeComment | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.createComment({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n async #updateIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const resolved = await this.#intakeForConnection(input.connection);\n return resolved.intake.updateIssue({\n ...this.#withBaseSource(input),\n connection: resolved.connection,\n });\n }\n\n #withBaseSource<T extends { sourceId?: string }>(input: T): T {\n if (!input.sourceId) return input;\n const scoped = decodeScopedSourceId(input.sourceId);\n return scoped ? { ...input, sourceId: scoped.sourceId } : input;\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!incidentioReconciliationEnabled()) return [];\n const reconcile = attachIncidentioIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = incidentioReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n\n /**\n * The integration's HTTP surface: `/web/incidentio/*` Mastra `apiRoutes`\n * (status + follow-up listing/detail for Intake), aggregated across every\n * discovered Platform connection. A board-scoped listing also feeds the\n * incident.io event rules.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildIncidentioRoutes({\n incidentio: this,\n mode: 'platform',\n auth: ctx.auth,\n intake: ctx.storage.intake,\n ingestFactoryIssues: attachIncidentioRules(this, ctx),\n });\n }\n\n diagnostics(): Record<string, unknown> {\n return { configured: true, mode: 'platform', endpointHost: this.#endpointHost };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.17.3-alpha.2",
3
+ "version": "0.17.3-alpha.3",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,11 +57,11 @@
57
57
  "hono": "^4.13.7",
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.6.4",
60
- "@mastra/auth-studio": "1.3.7",
61
60
  "@mastra/auth-workos": "1.6.5",
62
- "@mastra/core": "1.72.0-alpha.2",
61
+ "@mastra/auth-studio": "1.3.7",
63
62
  "@mastra/slack": "1.7.0-alpha.0",
64
- "@mastra/code-sdk": "1.8.4-alpha.2"
63
+ "@mastra/code-sdk": "1.8.4-alpha.3",
64
+ "@mastra/core": "1.72.0-alpha.3"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",
@@ -70,11 +70,11 @@
70
70
  "typescript": "^6.0.3",
71
71
  "typescript-eslint": "^8.57.0",
72
72
  "vitest": "4.1.11",
73
- "@mastra/libsql": "1.23.4-alpha.0",
74
- "@mastra/pg": "1.27.2-alpha.1",
75
73
  "@internal/lint": "0.0.137",
76
- "@internal/types-builder": "0.0.112",
77
- "@internal/workspace": "0.0.9"
74
+ "@mastra/libsql": "1.24.0-alpha.1",
75
+ "@internal/workspace": "0.0.9",
76
+ "@mastra/pg": "1.28.0-alpha.2",
77
+ "@internal/types-builder": "0.0.112"
78
78
  },
79
79
  "engines": {
80
80
  "node": ">=22.19.0"