@mastra/factory 0.16.0-alpha.7 → 0.16.0-alpha.9

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.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/dist/boards/work.d.ts.map +1 -1
  3. package/dist/boards/work.js +18 -1
  4. package/dist/boards/work.js.map +1 -1
  5. package/dist/factory.d.ts +3 -2
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +3 -2
  8. package/dist/factory.js.map +1 -1
  9. package/dist/integrations/base.d.ts +2 -0
  10. package/dist/integrations/base.d.ts.map +1 -1
  11. package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
  12. package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
  13. package/dist/integrations/incidentio/agent-tools.js +90 -0
  14. package/dist/integrations/incidentio/agent-tools.js.map +1 -0
  15. package/dist/integrations/incidentio/claim.d.ts +8 -0
  16. package/dist/integrations/incidentio/claim.d.ts.map +1 -0
  17. package/dist/integrations/incidentio/claim.js +14 -0
  18. package/dist/integrations/incidentio/claim.js.map +1 -0
  19. package/dist/integrations/incidentio/default-rules.d.ts +46 -0
  20. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
  21. package/dist/integrations/incidentio/default-rules.js +66 -0
  22. package/dist/integrations/incidentio/default-rules.js.map +1 -0
  23. package/dist/integrations/incidentio/integration.d.ts +48 -2
  24. package/dist/integrations/incidentio/integration.d.ts.map +1 -1
  25. package/dist/integrations/incidentio/integration.js +88 -2
  26. package/dist/integrations/incidentio/integration.js.map +1 -1
  27. package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
  28. package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
  29. package/dist/integrations/incidentio/issue-reconciler.js +38 -2
  30. package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
  31. package/dist/integrations/incidentio/routes.d.ts +71 -0
  32. package/dist/integrations/incidentio/routes.d.ts.map +1 -0
  33. package/dist/integrations/incidentio/routes.js +245 -0
  34. package/dist/integrations/incidentio/routes.js.map +1 -0
  35. package/dist/integrations/incidentio/rules.d.ts +55 -0
  36. package/dist/integrations/incidentio/rules.d.ts.map +1 -0
  37. package/dist/integrations/incidentio/rules.js +175 -0
  38. package/dist/integrations/incidentio/rules.js.map +1 -0
  39. package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
  40. package/dist/integrations/platform/connect/routes.js +10 -4
  41. package/dist/integrations/platform/connect/routes.js.map +1 -1
  42. package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
  43. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  44. package/dist/integrations/platform/incidentio/integration.js +371 -22
  45. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  46. package/dist/routes/oauth.js +2 -5
  47. package/dist/routes/oauth.js.map +1 -1
  48. package/dist/rules/dispatcher.d.ts.map +1 -1
  49. package/dist/rules/dispatcher.js +1 -1
  50. package/dist/rules/dispatcher.js.map +1 -1
  51. package/dist/rules/index.d.ts +2 -2
  52. package/dist/rules/index.d.ts.map +1 -1
  53. package/dist/rules/index.js +2 -2
  54. package/dist/rules/types.d.ts +30 -3
  55. package/dist/rules/types.d.ts.map +1 -1
  56. package/dist/rules/types.js +5 -1
  57. package/dist/rules/types.js.map +1 -1
  58. package/dist/rules/validation.d.ts.map +1 -1
  59. package/dist/rules/validation.js +1 -0
  60. package/dist/rules/validation.js.map +1 -1
  61. package/dist/work-item-branch.d.ts +1 -1
  62. package/dist/work-item-branch.d.ts.map +1 -1
  63. package/dist/work-item-branch.js +3 -2
  64. package/dist/work-item-branch.js.map +1 -1
  65. package/package.json +6 -6
@@ -1,32 +1,370 @@
1
- import { platformApiClientConfigFromEnv } from "../api-client.js";
2
- import { IncidentioApiClient } from "../../incidentio/api.js";
3
- import { createIncidentioIntake } from "../../incidentio/intake.js";
1
+ import { PlatformApiClient, platformApiClientConfigFromEnv } from "../api-client.js";
2
+ import { IncidentioApiClient, IncidentioApiError } from "../../incidentio/api.js";
3
+ import { buildIncidentioRoutes } from "../../incidentio/routes.js";
4
+ import { buildIncidentioAgentTools } from "../../incidentio/agent-tools.js";
5
+ import { resolveIncidentioRules } from "../../incidentio/default-rules.js";
6
+ import { INCIDENTIO_FOLLOW_UPS_SOURCE_ID, INCIDENTIO_INCIDENTS_SOURCE_ID, createIncidentioIntake } from "../../incidentio/intake.js";
7
+ import { attachIncidentioRules } from "../../incidentio/rules.js";
4
8
  import { attachIncidentioIssueReconciler } from "../../incidentio/issue-reconciler.js";
5
9
  import { incidentioReconciliationEnabled, incidentioReconciliationInterval } from "../../incidentio/reconciliation-config.js";
6
10
  import { IssueReconcileWorker } from "../../issue-reconcile-worker.js";
7
11
  //#region src/integrations/platform/incidentio/integration.ts
12
+ /**
13
+ * Provider config key registered for incident.io in Mastra Platform's
14
+ * integrations catalog. Connections are discovered at runtime — no
15
+ * deploy-time connection ID wiring.
16
+ */
17
+ const PLATFORM_INCIDENTIO_PROVIDER_CONFIG_KEY = "incident-io";
18
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
19
  const CONNECTION_TOKEN_PREFIX = "incidentio-connection:";
20
+ const SCOPED_SOURCE_PREFIX = "incidentio-source:";
21
+ const BASE_SOURCE_IDS = [INCIDENTIO_INCIDENTS_SOURCE_ID, INCIDENTIO_FOLLOW_UPS_SOURCE_ID];
22
+ function encodeScopedSourceId(connectionId, sourceId) {
23
+ return `${SCOPED_SOURCE_PREFIX}${Buffer.from(JSON.stringify({
24
+ connectionId,
25
+ sourceId
26
+ })).toString("base64url")}`;
27
+ }
28
+ function decodeScopedSourceId(value) {
29
+ if (!value.startsWith(SCOPED_SOURCE_PREFIX)) return null;
30
+ try {
31
+ const parsed = JSON.parse(Buffer.from(value.slice(18), "base64url").toString("utf8"));
32
+ return typeof parsed.connectionId === "string" && typeof parsed.sourceId === "string" ? {
33
+ connectionId: parsed.connectionId,
34
+ sourceId: parsed.sourceId
35
+ } : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function incidentioConnection(connectionId) {
41
+ return {
42
+ type: "oauth",
43
+ accessToken: `${CONNECTION_TOKEN_PREFIX}${connectionId}`
44
+ };
45
+ }
46
+ function connectionIdFromConnection(connection) {
47
+ if (connection.type !== "oauth" || !connection.accessToken.startsWith(CONNECTION_TOKEN_PREFIX)) return null;
48
+ return connection.accessToken.slice(22) || null;
49
+ }
50
+ /**
51
+ * Base (per-connection) source ids selected for a connection. Scoped ids
52
+ * select their own connection; legacy unscoped ids (`incidentio:incidents`)
53
+ * from pre-discovery deploys apply to every connection.
54
+ */
55
+ function baseSourceIdsFor(connectionId, sourceIds) {
56
+ const selected = /* @__PURE__ */ new Set();
57
+ for (const sourceId of sourceIds) {
58
+ const scoped = decodeScopedSourceId(sourceId);
59
+ if (scoped) {
60
+ if (scoped.connectionId === connectionId) selected.add(scoped.sourceId);
61
+ continue;
62
+ }
63
+ if (BASE_SOURCE_IDS.includes(sourceId)) selected.add(sourceId);
64
+ }
65
+ return [...selected];
66
+ }
67
+ function encodePageCursor(cursor) {
68
+ return Buffer.from(JSON.stringify(cursor)).toString("base64url");
69
+ }
70
+ function decodePageCursor(cursor) {
71
+ if (!cursor) return null;
72
+ try {
73
+ const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
74
+ if (typeof parsed.connectionId !== "string" || !parsed.connectionId) return null;
75
+ return {
76
+ connectionId: parsed.connectionId,
77
+ ...typeof parsed.inner === "string" && parsed.inner ? { inner: parsed.inner } : {}
78
+ };
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
9
83
  var PlatformIncidentioIntegration = class {
10
84
  id = "incidentio";
11
- intake;
85
+ #clientConfig;
86
+ #platformClient;
12
87
  #endpointHost;
88
+ #intakeByConnectionId = /* @__PURE__ */ new Map();
89
+ #rules;
90
+ /** Bound once by the factory via `initialize()` before any surface is used. */
91
+ #projects;
92
+ #auth;
93
+ #intakeStorage;
94
+ #orgIdByResourceId = /* @__PURE__ */ new Map();
13
95
  constructor(config = {}) {
14
- const connectionId = config.connectionId?.trim() || process.env.MASTRA_INCIDENT_IO_CONNECTION_ID?.trim();
15
- if (!connectionId) throw new Error("PlatformIncidentioIntegration: missing required MASTRA_INCIDENT_IO_CONNECTION_ID.");
16
- const clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();
17
- this.#endpointHost = new URL(clientConfig.baseUrl).host;
18
- const proxyBaseUrl = `${clientConfig.baseUrl.replace(/\/+$/, "")}/v2/connections/${encodeURIComponent(connectionId)}/proxy`;
19
- this.intake = createIncidentioIntake({
96
+ this.#clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();
97
+ this.#platformClient = new PlatformApiClient(this.#clientConfig);
98
+ this.#endpointHost = new URL(this.#clientConfig.baseUrl).host;
99
+ this.#rules = resolveIncidentioRules(config.rules);
100
+ }
101
+ /** Event rules driving automatic follow-up materialization and close handling. */
102
+ get rules() {
103
+ return this.#rules;
104
+ }
105
+ initialize({ projects, auth, intake }) {
106
+ this.#projects = projects;
107
+ this.#auth = auth;
108
+ this.#intakeStorage = intake;
109
+ }
110
+ /** Cross-integration intake selection/binding domain — agent-tool authorization. */
111
+ get intakeStorage() {
112
+ if (!this.#intakeStorage) throw new Error("PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().");
113
+ return this.#intakeStorage;
114
+ }
115
+ /** Factory projects domain — maps a session's resourceId to its owning org. */
116
+ get projects() {
117
+ if (!this.#projects) throw new Error("PlatformIncidentioIntegration is not initialized — the factory binds storage during prepare().");
118
+ return this.#projects;
119
+ }
120
+ /** Whether the host runs with web auth enabled; every surface is inert without it. */
121
+ get authEnabled() {
122
+ return this.#auth?.enabled() ?? false;
123
+ }
124
+ /**
125
+ * Map a session's resourceId (the factory project id) to its owning org.
126
+ * Same caching semantics as the Jira integration: definitive misses are
127
+ * cached, transient database failures are not.
128
+ */
129
+ async resolveOrgId(resourceId) {
130
+ const cached = this.#orgIdByResourceId.get(resourceId);
131
+ if (cached !== void 0) return cached;
132
+ if (!UUID_RE.test(resourceId)) {
133
+ this.#orgIdByResourceId.set(resourceId, null);
134
+ return null;
135
+ }
136
+ let orgId;
137
+ try {
138
+ await this.projects.ensureReady();
139
+ orgId = (await this.projects.getById({ id: resourceId }))?.orgId ?? null;
140
+ } catch {
141
+ return null;
142
+ }
143
+ this.#orgIdByResourceId.set(resourceId, orgId);
144
+ return orgId;
145
+ }
146
+ /**
147
+ * Org-scoped agent tools: the read-only follow-up detail tool for sessions
148
+ * whose resource is a factory project with an active Platform-managed
149
+ * incident.io connection.
150
+ */
151
+ async agentTools(args) {
152
+ if (!await this.hasActiveConnections()) return {};
153
+ return buildIncidentioAgentTools({
154
+ requestContext: args.requestContext,
155
+ incidentio: this
156
+ });
157
+ }
158
+ async listConnections() {
159
+ const providerKey = encodeURIComponent(PLATFORM_INCIDENTIO_PROVIDER_CONFIG_KEY);
160
+ return (await this.#platformClient.request("GET", `/v2/connections?providerKey=${providerKey}`)).connections;
161
+ }
162
+ async hasActiveConnections() {
163
+ return (await this.#activeConnections()).length > 0;
164
+ }
165
+ clearCaches() {
166
+ this.#intakeByConnectionId.clear();
167
+ this.#orgIdByResourceId.clear();
168
+ }
169
+ async #activeConnections() {
170
+ return (await this.listConnections()).filter((connection) => connection.status === "active").slice().sort((left, right) => left.id.localeCompare(right.id));
171
+ }
172
+ #connectionIntake(connectionId) {
173
+ const cached = this.#intakeByConnectionId.get(connectionId);
174
+ if (cached) return cached;
175
+ const intake = createIncidentioIntake({
20
176
  api: new IncidentioApiClient({
21
- baseUrl: proxyBaseUrl,
22
- accessToken: clientConfig.accessToken,
23
- ...clientConfig.fetchImpl ? { fetchImpl: clientConfig.fetchImpl } : {}
177
+ baseUrl: `${this.#clientConfig.baseUrl.replace(/\/+$/, "")}/v2/connections/${encodeURIComponent(connectionId)}/proxy`,
178
+ accessToken: this.#clientConfig.accessToken,
179
+ ...this.#clientConfig.fetchImpl ? { fetchImpl: this.#clientConfig.fetchImpl } : {}
24
180
  }),
25
- connection: {
26
- type: "oauth",
27
- accessToken: `${CONNECTION_TOKEN_PREFIX}${connectionId}`
28
- }
181
+ connection: incidentioConnection(connectionId)
29
182
  });
183
+ this.#intakeByConnectionId.set(connectionId, intake);
184
+ return intake;
185
+ }
186
+ async #connectionContexts() {
187
+ return (await this.#activeConnections()).map((connection) => ({
188
+ connection,
189
+ intake: this.#connectionIntake(connection.id)
190
+ }));
191
+ }
192
+ /**
193
+ * Resolve the intake for an explicit connection token, or fall back to the
194
+ * sole active connection so legacy single-connection callers keep working.
195
+ *
196
+ * A caller-supplied connection token is only a claim: the id it carries
197
+ * must match a connection the deployment actually discovered as active
198
+ * before any proxy request is minted with the Platform credential.
199
+ */
200
+ async #intakeForConnection(connection) {
201
+ const connectionId = connectionIdFromConnection(connection);
202
+ const active = await this.#activeConnections();
203
+ if (connectionId) {
204
+ if (!active.some((candidate) => candidate.id === connectionId)) throw new IncidentioApiError("incident.io connection is unavailable or requires reauthentication.", 401);
205
+ return {
206
+ intake: this.#connectionIntake(connectionId),
207
+ connection: incidentioConnection(connectionId)
208
+ };
209
+ }
210
+ if (active.length === 1) {
211
+ const soleId = active[0].id;
212
+ return {
213
+ intake: this.#connectionIntake(soleId),
214
+ connection: incidentioConnection(soleId)
215
+ };
216
+ }
217
+ throw new IncidentioApiError(active.length === 0 ? "incident.io connection is unavailable or requires reauthentication." : "incident.io request must identify a connection when multiple accounts are connected.", active.length === 0 ? 401 : 400);
218
+ }
219
+ intake = {
220
+ resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
221
+ listSources: () => this.#listSources(),
222
+ listItems: (input) => this.#listItems(input),
223
+ listIssues: (input) => this.#listIssues(input),
224
+ getIssue: (input) => this.#getIssue(input),
225
+ createComment: (input) => this.#createComment(input),
226
+ updateIssue: (input) => this.#updateIssue(input)
227
+ };
228
+ async #resolveIntakeDispatch(input) {
229
+ if (input.externalSource.type !== "issue") return null;
230
+ const externalId = input.externalSource.externalId;
231
+ const contexts = await this.#connectionContexts();
232
+ if (contexts.length === 0) return null;
233
+ if (contexts.length === 1) {
234
+ const resolved = await contexts[0].intake.resolveIntakeDispatch?.(input);
235
+ return resolved ? this.#scopeResolvedDispatch(contexts[0].connection.id, resolved) : null;
236
+ }
237
+ let firstError;
238
+ for (const context of contexts) try {
239
+ if (!await context.intake.getIssue({
240
+ connection: incidentioConnection(context.connection.id),
241
+ issueId: externalId
242
+ })) continue;
243
+ const resolved = await context.intake.resolveIntakeDispatch?.(input);
244
+ return resolved ? this.#scopeResolvedDispatch(context.connection.id, resolved) : null;
245
+ } catch (error) {
246
+ if (error instanceof IncidentioApiError && error.status === 404) continue;
247
+ firstError ??= error;
248
+ }
249
+ if (firstError !== void 0) throw firstError;
250
+ return null;
251
+ }
252
+ #scopeResolvedDispatch(connectionId, resolved) {
253
+ return {
254
+ ...resolved,
255
+ connection: incidentioConnection(connectionId),
256
+ ...resolved.sourceId ? { sourceId: encodeScopedSourceId(connectionId, resolved.sourceId) } : {}
257
+ };
258
+ }
259
+ async #listSources() {
260
+ const contexts = await this.#connectionContexts();
261
+ const sources = [];
262
+ for (const context of contexts) {
263
+ const account = context.connection.accountLabel;
264
+ const inner = await context.intake.listSources({
265
+ orgId: "",
266
+ userId: ""
267
+ });
268
+ for (const source of inner) sources.push({
269
+ ...source,
270
+ id: encodeScopedSourceId(context.connection.id, source.id),
271
+ name: contexts.length > 1 && account ? `${source.name} (${account})` : source.name,
272
+ metadata: {
273
+ ...source.metadata ?? {},
274
+ connectionId: context.connection.id,
275
+ ...account ? { account } : {}
276
+ }
277
+ });
278
+ }
279
+ return sources;
280
+ }
281
+ async #listItems(input) {
282
+ if (input.sourceIds.length === 0) return {
283
+ items: [],
284
+ nextCursor: null
285
+ };
286
+ const contexts = await this.#connectionContexts();
287
+ const cursor = decodePageCursor(input.cursor);
288
+ let startIndex = 0;
289
+ let exactMatch = false;
290
+ if (cursor) {
291
+ const index = contexts.findIndex((context) => context.connection.id === cursor.connectionId);
292
+ exactMatch = index !== -1;
293
+ startIndex = exactMatch ? index : contexts.findIndex((context) => context.connection.id.localeCompare(cursor.connectionId) > 0);
294
+ if (startIndex === -1) return {
295
+ items: [],
296
+ nextCursor: null
297
+ };
298
+ }
299
+ for (let index = startIndex; index < contexts.length; index++) {
300
+ const context = contexts[index];
301
+ const baseSourceIds = baseSourceIdsFor(context.connection.id, input.sourceIds);
302
+ if (baseSourceIds.length === 0) continue;
303
+ const page = await context.intake.listItems({
304
+ orgId: input.orgId,
305
+ userId: input.userId,
306
+ sourceIds: baseSourceIds,
307
+ ...index === startIndex && exactMatch && cursor?.inner ? { cursor: cursor.inner } : {}
308
+ });
309
+ const nextCursor = page.nextCursor ? encodePageCursor({
310
+ connectionId: context.connection.id,
311
+ inner: page.nextCursor
312
+ }) : index + 1 < contexts.length ? encodePageCursor({ connectionId: contexts[index + 1].connection.id }) : null;
313
+ return {
314
+ items: page.items.map((item) => ({
315
+ ...item,
316
+ sourceId: encodeScopedSourceId(context.connection.id, item.sourceId),
317
+ metadata: {
318
+ ...item.metadata ?? {},
319
+ connectionId: context.connection.id
320
+ }
321
+ })),
322
+ nextCursor
323
+ };
324
+ }
325
+ return {
326
+ items: [],
327
+ nextCursor: null
328
+ };
329
+ }
330
+ async #listIssues(input) {
331
+ const resolved = await this.#intakeForConnection(input.connection);
332
+ const connectionId = connectionIdFromConnection(resolved.connection);
333
+ const baseSourceIds = connectionId ? baseSourceIdsFor(connectionId, input.sourceIds) : input.sourceIds.map((sourceId) => decodeScopedSourceId(sourceId)?.sourceId ?? sourceId);
334
+ return resolved.intake.listIssues({
335
+ ...input,
336
+ connection: resolved.connection,
337
+ sourceIds: baseSourceIds
338
+ });
339
+ }
340
+ async #getIssue(input) {
341
+ const resolved = await this.#intakeForConnection(input.connection);
342
+ return resolved.intake.getIssue({
343
+ ...this.#withBaseSource(input),
344
+ connection: resolved.connection
345
+ });
346
+ }
347
+ async #createComment(input) {
348
+ const resolved = await this.#intakeForConnection(input.connection);
349
+ return resolved.intake.createComment({
350
+ ...this.#withBaseSource(input),
351
+ connection: resolved.connection
352
+ });
353
+ }
354
+ async #updateIssue(input) {
355
+ const resolved = await this.#intakeForConnection(input.connection);
356
+ return resolved.intake.updateIssue({
357
+ ...this.#withBaseSource(input),
358
+ connection: resolved.connection
359
+ });
360
+ }
361
+ #withBaseSource(input) {
362
+ if (!input.sourceId) return input;
363
+ const scoped = decodeScopedSourceId(input.sourceId);
364
+ return scoped ? {
365
+ ...input,
366
+ sourceId: scoped.sourceId
367
+ } : input;
30
368
  }
31
369
  workers(ctx) {
32
370
  if (!incidentioReconciliationEnabled()) return [];
@@ -39,18 +377,29 @@ var PlatformIncidentioIntegration = class {
39
377
  ...intervalMs ? { intervalMs } : {}
40
378
  })];
41
379
  }
42
- routes() {
43
- return [];
380
+ /**
381
+ * The integration's HTTP surface: `/web/incidentio/*` Mastra `apiRoutes`
382
+ * (status + follow-up listing/detail for Intake), aggregated across every
383
+ * discovered Platform connection. A board-scoped listing also feeds the
384
+ * incident.io event rules.
385
+ */
386
+ routes(ctx) {
387
+ return buildIncidentioRoutes({
388
+ incidentio: this,
389
+ auth: ctx.auth,
390
+ intake: ctx.storage.intake,
391
+ ingestFactoryIssues: attachIncidentioRules(this, ctx)
392
+ });
44
393
  }
45
394
  diagnostics() {
46
395
  return {
396
+ configured: true,
47
397
  mode: "platform",
48
- endpointHost: this.#endpointHost,
49
- connectionConfigured: true
398
+ endpointHost: this.#endpointHost
50
399
  };
51
400
  }
52
401
  };
53
402
  //#endregion
54
- export { PlatformIncidentioIntegration };
403
+ export { PlatformIncidentioIntegration, decodeScopedSourceId, encodeScopedSourceId };
55
404
 
56
405
  //# sourceMappingURL=integration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":["#endpointHost"],"sources":["../../../../src/integrations/platform/incidentio/integration.ts"],"sourcesContent":["import type { MastraWorker } from '@mastra/core/worker';\n\nimport type { FactoryIntegration, IntegrationContext } from '../../base.js';\nimport { IncidentioApiClient } from '../../incidentio/api.js';\nimport { createIncidentioIntake } from '../../incidentio/intake.js';\nimport { attachIncidentioIssueReconciler } from '../../incidentio/issue-reconciler.js';\nimport {\n incidentioReconciliationEnabled,\n incidentioReconciliationInterval,\n} from '../../incidentio/reconciliation-config.js';\nimport { IssueReconcileWorker } from '../../issue-reconcile-worker.js';\nimport { platformApiClientConfigFromEnv, type PlatformApiClientConfig } from '../api-client.js';\n\nexport interface PlatformIncidentioIntegrationConfig {\n clientConfig?: PlatformApiClientConfig;\n connectionId?: string;\n}\n\nconst CONNECTION_TOKEN_PREFIX = 'incidentio-connection:';\n\nexport class PlatformIncidentioIntegration implements FactoryIntegration {\n readonly id = 'incidentio';\n readonly intake;\n readonly #endpointHost: string;\n\n constructor(config: PlatformIncidentioIntegrationConfig = {}) {\n const connectionId = config.connectionId?.trim() || process.env.MASTRA_INCIDENT_IO_CONNECTION_ID?.trim();\n if (!connectionId) {\n throw new Error('PlatformIncidentioIntegration: missing required MASTRA_INCIDENT_IO_CONNECTION_ID.');\n }\n\n const clientConfig = config.clientConfig ?? platformApiClientConfigFromEnv();\n this.#endpointHost = new URL(clientConfig.baseUrl).host;\n const proxyBaseUrl = `${clientConfig.baseUrl.replace(/\\/+$/, '')}/v2/connections/${encodeURIComponent(\n connectionId,\n )}/proxy`;\n\n this.intake = createIncidentioIntake({\n api: new IncidentioApiClient({\n baseUrl: proxyBaseUrl,\n accessToken: clientConfig.accessToken,\n ...(clientConfig.fetchImpl ? { fetchImpl: clientConfig.fetchImpl } : {}),\n }),\n connection: { type: 'oauth', accessToken: `${CONNECTION_TOKEN_PREFIX}${connectionId}` },\n });\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 routes(): [] {\n return [];\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost, connectionConfigured: true };\n }\n}\n"],"mappings":";;;;;;;AAkBA,MAAM,0BAA0B;AAEhC,IAAa,gCAAb,MAAyE;CACvE,KAAc;CACd;CACA;CAEA,YAAY,SAA8C,CAAC,GAAG;EAC5D,MAAM,eAAe,OAAO,cAAc,KAAK,KAAK,QAAQ,IAAI,kCAAkC,KAAK;EACvG,IAAI,CAAC,cACH,MAAM,IAAI,MAAM,mFAAmF;EAGrG,MAAM,eAAe,OAAO,gBAAgB,+BAA+B;EAC3E,KAAKA,gBAAgB,IAAI,IAAI,aAAa,OAAO,CAAC,CAAC;EACnD,MAAM,eAAe,GAAG,aAAa,QAAQ,QAAQ,QAAQ,EAAE,EAAE,kBAAkB,mBACjF,YACF,EAAE;EAEF,KAAK,SAAS,uBAAuB;GACnC,KAAK,IAAI,oBAAoB;IAC3B,SAAS;IACT,aAAa,aAAa;IAC1B,GAAI,aAAa,YAAY,EAAE,WAAW,aAAa,UAAU,IAAI,CAAC;GACxE,CAAC;GACD,YAAY;IAAE,MAAM;IAAS,aAAa,GAAG,0BAA0B;GAAe;EACxF,CAAC;CACH;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;CAEA,SAAa;EACX,OAAO,CAAC;CACV;CAEA,cAAuC;EACrC,OAAO;GAAE,MAAM;GAAY,cAAc,KAAKA;GAAe,sBAAsB;EAAK;CAC1F;AACF"}
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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"}
@@ -202,10 +202,7 @@ async function persistOAuthCredential({ ctx, provider, scope, credentials, authS
202
202
  return;
203
203
  }
204
204
  if (!authStorage) throw new Error("Credential storage is not available");
205
- authStorage.set(authProviderId, {
206
- type: "oauth",
207
- ...credentials
208
- });
205
+ await authStorage.addAccount(authProviderId, credentials);
209
206
  }
210
207
  async function readJsonBody(c) {
211
208
  try {
@@ -471,7 +468,7 @@ var OAuthRoutes = class extends Route {
471
468
  onCredentialsChanged(tenant);
472
469
  } else {
473
470
  if (!authStorage) return c.json({ error: "Credential storage is not available" }, 503);
474
- authStorage.remove(authProviderId);
471
+ authStorage.logout(authProviderId);
475
472
  }
476
473
  return c.json({ ok: true });
477
474
  } catch (error) {