@mastra/factory 0.15.0 → 0.16.0-alpha.10

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 (212) hide show
  1. package/README.md +4 -3
  2. package/dist/boards/index.d.ts +1 -1
  3. package/dist/boards/index.d.ts.map +1 -1
  4. package/dist/boards/index.js +2 -2
  5. package/dist/boards/index.js.map +1 -1
  6. package/dist/boards/review.d.ts.map +1 -1
  7. package/dist/boards/review.js +4 -1
  8. package/dist/boards/review.js.map +1 -1
  9. package/dist/boards/semantics.d.ts +7 -0
  10. package/dist/boards/semantics.d.ts.map +1 -1
  11. package/dist/boards/semantics.js +15 -1
  12. package/dist/boards/semantics.js.map +1 -1
  13. package/dist/boards/transition-policy.d.ts +4 -0
  14. package/dist/boards/transition-policy.d.ts.map +1 -1
  15. package/dist/boards/transition-policy.js.map +1 -1
  16. package/dist/boards/work-transition-policy.d.ts.map +1 -1
  17. package/dist/boards/work-transition-policy.js +6 -1
  18. package/dist/boards/work-transition-policy.js.map +1 -1
  19. package/dist/boards/work.d.ts.map +1 -1
  20. package/dist/boards/work.js +37 -3
  21. package/dist/boards/work.js.map +1 -1
  22. package/dist/capabilities/intake.d.ts +4 -0
  23. package/dist/capabilities/intake.d.ts.map +1 -1
  24. package/dist/factory.d.ts +6 -3
  25. package/dist/factory.d.ts.map +1 -1
  26. package/dist/factory.js +66 -18
  27. package/dist/factory.js.map +1 -1
  28. package/dist/integrations/base.d.ts +6 -2
  29. package/dist/integrations/base.d.ts.map +1 -1
  30. package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
  31. package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
  32. package/dist/integrations/incidentio/agent-tools.js +90 -0
  33. package/dist/integrations/incidentio/agent-tools.js.map +1 -0
  34. package/dist/integrations/incidentio/claim.d.ts +8 -0
  35. package/dist/integrations/incidentio/claim.d.ts.map +1 -0
  36. package/dist/integrations/incidentio/claim.js +14 -0
  37. package/dist/integrations/incidentio/claim.js.map +1 -0
  38. package/dist/integrations/incidentio/default-rules.d.ts +46 -0
  39. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
  40. package/dist/integrations/incidentio/default-rules.js +66 -0
  41. package/dist/integrations/incidentio/default-rules.js.map +1 -0
  42. package/dist/integrations/incidentio/integration.d.ts +48 -2
  43. package/dist/integrations/incidentio/integration.d.ts.map +1 -1
  44. package/dist/integrations/incidentio/integration.js +88 -2
  45. package/dist/integrations/incidentio/integration.js.map +1 -1
  46. package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
  47. package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
  48. package/dist/integrations/incidentio/issue-reconciler.js +38 -2
  49. package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
  50. package/dist/integrations/incidentio/routes.d.ts +71 -0
  51. package/dist/integrations/incidentio/routes.d.ts.map +1 -0
  52. package/dist/integrations/incidentio/routes.js +245 -0
  53. package/dist/integrations/incidentio/routes.js.map +1 -0
  54. package/dist/integrations/incidentio/rules.d.ts +55 -0
  55. package/dist/integrations/incidentio/rules.d.ts.map +1 -0
  56. package/dist/integrations/incidentio/rules.js +175 -0
  57. package/dist/integrations/incidentio/rules.js.map +1 -0
  58. package/dist/integrations/jira/adf.d.ts +26 -0
  59. package/dist/integrations/jira/adf.d.ts.map +1 -0
  60. package/dist/integrations/jira/adf.js +73 -0
  61. package/dist/integrations/jira/adf.js.map +1 -0
  62. package/dist/integrations/jira/agent-tools.d.ts +45 -0
  63. package/dist/integrations/jira/agent-tools.d.ts.map +1 -0
  64. package/dist/integrations/jira/agent-tools.js +87 -0
  65. package/dist/integrations/jira/agent-tools.js.map +1 -0
  66. package/dist/integrations/jira/api.d.ts +157 -0
  67. package/dist/integrations/jira/api.d.ts.map +1 -0
  68. package/dist/integrations/jira/api.js +141 -0
  69. package/dist/integrations/jira/api.js.map +1 -0
  70. package/dist/integrations/jira/claim.d.ts +9 -0
  71. package/dist/integrations/jira/claim.d.ts.map +1 -0
  72. package/dist/integrations/jira/claim.js +15 -0
  73. package/dist/integrations/jira/claim.js.map +1 -0
  74. package/dist/integrations/jira/default-rules.d.ts +47 -0
  75. package/dist/integrations/jira/default-rules.d.ts.map +1 -0
  76. package/dist/integrations/jira/default-rules.js +67 -0
  77. package/dist/integrations/jira/default-rules.js.map +1 -0
  78. package/dist/integrations/jira/integration.d.ts +114 -0
  79. package/dist/integrations/jira/integration.d.ts.map +1 -0
  80. package/dist/integrations/jira/integration.js +401 -0
  81. package/dist/integrations/jira/integration.js.map +1 -0
  82. package/dist/integrations/jira/issue-reconciler.d.ts +10 -0
  83. package/dist/integrations/jira/issue-reconciler.d.ts.map +1 -0
  84. package/dist/integrations/jira/issue-reconciler.js +70 -0
  85. package/dist/integrations/jira/issue-reconciler.js.map +1 -0
  86. package/dist/integrations/jira/reconciliation-config.d.ts +3 -0
  87. package/dist/integrations/jira/reconciliation-config.d.ts.map +1 -0
  88. package/dist/integrations/jira/reconciliation-config.js +17 -0
  89. package/dist/integrations/jira/reconciliation-config.js.map +1 -0
  90. package/dist/integrations/jira/routes.d.ts +54 -0
  91. package/dist/integrations/jira/routes.d.ts.map +1 -0
  92. package/dist/integrations/jira/routes.js +260 -0
  93. package/dist/integrations/jira/routes.js.map +1 -0
  94. package/dist/integrations/jira/rules.d.ts +57 -0
  95. package/dist/integrations/jira/rules.d.ts.map +1 -0
  96. package/dist/integrations/jira/rules.js +175 -0
  97. package/dist/integrations/jira/rules.js.map +1 -0
  98. package/dist/integrations/linear/claim.d.ts +8 -0
  99. package/dist/integrations/linear/claim.d.ts.map +1 -0
  100. package/dist/integrations/linear/claim.js +14 -0
  101. package/dist/integrations/linear/claim.js.map +1 -0
  102. package/dist/integrations/linear/default-rules.d.ts +1 -0
  103. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  104. package/dist/integrations/linear/default-rules.js +2 -0
  105. package/dist/integrations/linear/default-rules.js.map +1 -1
  106. package/dist/integrations/linear/integration.d.ts +24 -6
  107. package/dist/integrations/linear/integration.d.ts.map +1 -1
  108. package/dist/integrations/linear/integration.js +257 -37
  109. package/dist/integrations/linear/integration.js.map +1 -1
  110. package/dist/integrations/linear/routes.d.ts +10 -0
  111. package/dist/integrations/linear/routes.d.ts.map +1 -1
  112. package/dist/integrations/linear/routes.js +74 -12
  113. package/dist/integrations/linear/routes.js.map +1 -1
  114. package/dist/integrations/linear/rules.d.ts +1 -1
  115. package/dist/integrations/linear/rules.d.ts.map +1 -1
  116. package/dist/integrations/linear/rules.js +27 -2
  117. package/dist/integrations/linear/rules.js.map +1 -1
  118. package/dist/integrations/platform/connect/routes.d.ts +39 -0
  119. package/dist/integrations/platform/connect/routes.d.ts.map +1 -0
  120. package/dist/integrations/platform/connect/routes.js +129 -0
  121. package/dist/integrations/platform/connect/routes.js.map +1 -0
  122. package/dist/integrations/platform/github/event-worker.js +1 -1
  123. package/dist/integrations/platform/github/integration.js +1 -1
  124. package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
  125. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  126. package/dist/integrations/platform/incidentio/integration.js +371 -22
  127. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  128. package/dist/integrations/platform/jira/agent-tools.d.ts +36 -0
  129. package/dist/integrations/platform/jira/agent-tools.d.ts.map +1 -0
  130. package/dist/integrations/platform/jira/agent-tools.js +85 -0
  131. package/dist/integrations/platform/jira/agent-tools.js.map +1 -0
  132. package/dist/integrations/platform/jira/integration.d.ts +65 -0
  133. package/dist/integrations/platform/jira/integration.d.ts.map +1 -0
  134. package/dist/integrations/platform/jira/integration.js +540 -0
  135. package/dist/integrations/platform/jira/integration.js.map +1 -0
  136. package/dist/integrations/platform/jira/routes.d.ts +55 -0
  137. package/dist/integrations/platform/jira/routes.d.ts.map +1 -0
  138. package/dist/integrations/platform/jira/routes.js +274 -0
  139. package/dist/integrations/platform/jira/routes.js.map +1 -0
  140. package/dist/integrations/platform/linear/integration.d.ts +34 -1
  141. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  142. package/dist/integrations/platform/linear/integration.js +271 -48
  143. package/dist/integrations/platform/linear/integration.js.map +1 -1
  144. package/dist/routes/intake.d.ts.map +1 -1
  145. package/dist/routes/intake.js +15 -5
  146. package/dist/routes/intake.js.map +1 -1
  147. package/dist/routes/oauth.js +2 -5
  148. package/dist/routes/oauth.js.map +1 -1
  149. package/dist/routes/surface.d.ts.map +1 -1
  150. package/dist/routes/surface.js +24 -4
  151. package/dist/routes/surface.js.map +1 -1
  152. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  153. package/dist/rules/dispatch-errors.js +8 -0
  154. package/dist/rules/dispatch-errors.js.map +1 -1
  155. package/dist/rules/dispatcher.d.ts +1 -0
  156. package/dist/rules/dispatcher.d.ts.map +1 -1
  157. package/dist/rules/dispatcher.js +180 -106
  158. package/dist/rules/dispatcher.js.map +1 -1
  159. package/dist/rules/index.d.ts +2 -2
  160. package/dist/rules/index.d.ts.map +1 -1
  161. package/dist/rules/index.js +2 -2
  162. package/dist/rules/start-coordinator.d.ts.map +1 -1
  163. package/dist/rules/start-coordinator.js +4 -1
  164. package/dist/rules/start-coordinator.js.map +1 -1
  165. package/dist/rules/terminal-cleanup.d.ts +14 -1
  166. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  167. package/dist/rules/terminal-cleanup.js +20 -0
  168. package/dist/rules/terminal-cleanup.js.map +1 -1
  169. package/dist/rules/tools.d.ts +2 -0
  170. package/dist/rules/tools.d.ts.map +1 -1
  171. package/dist/rules/tools.js +30 -2
  172. package/dist/rules/tools.js.map +1 -1
  173. package/dist/rules/transition-service.d.ts +13 -0
  174. package/dist/rules/transition-service.d.ts.map +1 -1
  175. package/dist/rules/transition-service.js +11 -2
  176. package/dist/rules/transition-service.js.map +1 -1
  177. package/dist/rules/types.d.ts +72 -3
  178. package/dist/rules/types.d.ts.map +1 -1
  179. package/dist/rules/types.js +9 -1
  180. package/dist/rules/types.js.map +1 -1
  181. package/dist/rules/validation.d.ts.map +1 -1
  182. package/dist/rules/validation.js +13 -2
  183. package/dist/rules/validation.js.map +1 -1
  184. package/dist/session/run-audit.d.ts +9 -0
  185. package/dist/session/run-audit.d.ts.map +1 -1
  186. package/dist/session/run-audit.js +37 -6
  187. package/dist/session/run-audit.js.map +1 -1
  188. package/dist/skills/service.d.ts +12 -0
  189. package/dist/skills/service.d.ts.map +1 -1
  190. package/dist/skills/service.js +27 -1
  191. package/dist/skills/service.js.map +1 -1
  192. package/dist/storage/domains/intake/base.d.ts +11 -8
  193. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  194. package/dist/storage/domains/intake/base.js +69 -24
  195. package/dist/storage/domains/intake/base.js.map +1 -1
  196. package/dist/storage/domains/work-items/base.d.ts +41 -2
  197. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  198. package/dist/storage/domains/work-items/base.js +159 -12
  199. package/dist/storage/domains/work-items/base.js.map +1 -1
  200. package/dist/supervisor/session-messaging.d.ts +23 -0
  201. package/dist/supervisor/session-messaging.d.ts.map +1 -0
  202. package/dist/supervisor/session-messaging.js +15 -0
  203. package/dist/supervisor/session-messaging.js.map +1 -0
  204. package/dist/supervisor/write-tools.d.ts +2 -1
  205. package/dist/supervisor/write-tools.d.ts.map +1 -1
  206. package/dist/supervisor/write-tools.js +11 -7
  207. package/dist/supervisor/write-tools.js.map +1 -1
  208. package/dist/work-item-branch.d.ts +1 -1
  209. package/dist/work-item-branch.d.ts.map +1 -1
  210. package/dist/work-item-branch.js +4 -2
  211. package/dist/work-item-branch.js.map +1 -1
  212. package/package.json +11 -11
@@ -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":";;;;;;;;;;;;;;;;AAsDA,MAAM,0CAA0C;AAChD,MAAM,UAAU;AAChB,MAAM,0BAA0B;AAChC,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB,CAAC,gCAAgC,+BAA+B;AA0BxF,SAAgB,qBAAqB,cAAsB,UAA0B;CACnF,OAAO,GAAG,uBAAuB,OAAO,KAAK,KAAK,UAAU;EAAE;EAAc;CAAS,CAAC,CAAC,CAAC,CAAC,SAAS,WAAW;AAC/G;AAEA,SAAgB,qBAAqB,OAAsC;CACzE,IAAI,CAAC,MAAM,WAAW,oBAAoB,GAAG,OAAO;CACpD,IAAI;EACF,MAAM,SAAS,KAAK,MAClB,OAAO,KAAK,MAAM,MAAM,EAA2B,GAAG,WAAW,CAAC,CAAC,SAAS,MAAM,CACpF;EACA,OAAO,OAAO,OAAO,iBAAiB,YAAY,OAAO,OAAO,aAAa,WACzE;GAAE,cAAc,OAAO;GAAc,UAAU,OAAO;EAAS,IAC/D;CACN,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,qBAAqB,cAA6C;CACzE,OAAO;EAAE,MAAM;EAAS,aAAa,GAAG,0BAA0B;CAAe;AACnF;AAEA,SAAS,2BAA2B,YAAkD;CACpF,IAAI,WAAW,SAAS,WAAW,CAAC,WAAW,YAAY,WAAW,uBAAuB,GAAG,OAAO;CACvG,OAAO,WAAW,YAAY,MAAM,EAA8B,KAAK;AACzE;;;;;;AAOA,SAAS,iBAAiB,cAAsB,WAA+B;CAC7E,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,YAAY,WAAW;EAChC,MAAM,SAAS,qBAAqB,QAAQ;EAC5C,IAAI,QAAQ;GACV,IAAI,OAAO,iBAAiB,cAAc,SAAS,IAAI,OAAO,QAAQ;GACtE;EACF;EACA,IAAK,gBAAsC,SAAS,QAAQ,GAAG,SAAS,IAAI,QAAQ;CACtF;CACA,OAAO,CAAC,GAAG,QAAQ;AACrB;AAEA,SAAS,iBAAiB,QAAqC;CAC7D,OAAO,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW;AACjE;AAEA,SAAS,iBAAiB,QAAwD;CAChF,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EAC3E,IAAI,OAAO,OAAO,iBAAiB,YAAY,CAAC,OAAO,cAAc,OAAO;EAC5E,OAAO;GACL,cAAc,OAAO;GACrB,GAAI,OAAO,OAAO,UAAU,YAAY,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EACpF;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAOA,IAAa,gCAAb,MAAyE;CACvE,KAAc;CACd;CACA;CACA;CACA,wCAAiC,IAAI,IAAoB;CACzD;;CAGA;CACA;CACA;CACA,qCAA8B,IAAI,IAA2B;CAE7D,YAAY,SAA8C,CAAC,GAAG;EAC5D,KAAKA,gBAAgB,OAAO,gBAAgB,+BAA+B;EAC3E,KAAKC,kBAAkB,IAAI,kBAAkB,KAAKD,aAAa;EAC/D,KAAKE,gBAAgB,IAAI,IAAI,KAAKF,cAAc,OAAO,CAAC,CAAC;EACzD,KAAKI,SAAS,uBAAuB,OAAO,KAAK;CACnD;;CAGA,IAAI,QAA8B;EAChC,OAAO,KAAKA;CACd;CAEA,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,gGAAgG;EAElH,OAAO,KAAKA;CACd;;CAGA,IAAI,WAAmC;EACrC,IAAI,CAAC,KAAKF,WACR,MAAM,IAAI,MAAM,gGAAgG;EAElH,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,QAAQ,KAAK,UAAU,GAAG;GAC7B,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;;;;;;CAOA,MAAM,WAAW,MAAqE;EACpF,IAAI,CAAE,MAAM,KAAK,qBAAqB,GAAI,OAAO,CAAC;EAClD,OAAO,0BAA0B;GAAE,gBAAgB,KAAK;GAAgB,YAAY;EAAK,CAAC;CAC5F;CAEA,MAAM,kBAA4D;EAChE,MAAM,cAAc,mBAAmB,uCAAuC;EAK9E,QAAO,MAJc,KAAKJ,gBAAgB,QACxC,OACA,+BAA+B,aACjC,EAAA,CACc;CAChB;CAEA,MAAM,uBAAyC;EAC7C,QAAQ,MAAM,KAAKQ,mBAAmB,EAAA,CAAG,SAAS;CACpD;CAEA,cAAoB;EAClB,KAAKN,sBAAsB,MAAM;EACjC,KAAKE,mBAAmB,MAAM;CAChC;CAEA,MAAMI,qBAA+D;EAGnE,QAFqB,MAAM,KAAK,gBAAgB,EAAA,CAAG,QAAO,eAAc,WAAW,WAAW,QAE7E,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,MAAM,UAAU,KAAK,GAAG,cAAc,MAAM,EAAE,CAAC;CAClF;CAEA,kBAAkB,cAA8B;EAC9C,MAAM,SAAS,KAAKN,sBAAsB,IAAI,YAAY;EAC1D,IAAI,QAAQ,OAAO;EAInB,MAAM,SAAS,uBAAuB;GACpC,KAAK,IAAI,oBAAoB;IAC3B,SAAS,GALW,KAAKH,cAAc,QAAQ,QAAQ,QAAQ,EAAE,EAAE,kBAAkB,mBACvF,YACF,EAAE;IAIE,aAAa,KAAKA,cAAc;IAChC,GAAI,KAAKA,cAAc,YAAY,EAAE,WAAW,KAAKA,cAAc,UAAU,IAAI,CAAC;GACpF,CAAC;GACD,YAAY,qBAAqB,YAAY;EAC/C,CAAC;EACD,KAAKG,sBAAsB,IAAI,cAAc,MAAM;EACnD,OAAO;CACT;CAEA,MAAMO,sBAA8D;EAClE,QAAQ,MAAM,KAAKD,mBAAmB,EAAA,CAAG,KAAI,gBAAe;GAC1D;GACA,QAAQ,KAAKE,kBAAkB,WAAW,EAAE;EAC9C,EAAE;CACJ;;;;;;;;;CAUA,MAAMC,qBAAqB,YAAsE;EAC/F,MAAM,eAAe,2BAA2B,UAAU;EAC1D,MAAM,SAAS,MAAM,KAAKH,mBAAmB;EAC7C,IAAI,cAAc;GAChB,IAAI,CAAC,OAAO,MAAK,cAAa,UAAU,OAAO,YAAY,GACzD,MAAM,IAAI,mBAAmB,uEAAuE,GAAG;GAEzG,OAAO;IAAE,QAAQ,KAAKE,kBAAkB,YAAY;IAAG,YAAY,qBAAqB,YAAY;GAAE;EACxG;EACA,IAAI,OAAO,WAAW,GAAG;GACvB,MAAM,SAAS,OAAO,EAAE,CAAE;GAC1B,OAAO;IAAE,QAAQ,KAAKA,kBAAkB,MAAM;IAAG,YAAY,qBAAqB,MAAM;GAAE;EAC5F;EACA,MAAM,IAAI,mBACR,OAAO,WAAW,IACd,wEACA,wFACJ,OAAO,WAAW,IAAI,MAAM,GAC9B;CACF;CAEA,SAA0B;EACxB,wBAAuB,UAAS,KAAKE,uBAAuB,KAAK;EACjE,mBAAmB,KAAKC,aAAa;EACrC,YAAW,UAAS,KAAKC,WAAW,KAAK;EACzC,aAAY,UAAS,KAAKC,YAAY,KAAK;EAC3C,WAAU,UAAS,KAAKC,UAAU,KAAK;EACvC,gBAAe,UAAS,KAAKC,eAAe,KAAK;EACjD,cAAa,UAAS,KAAKC,aAAa,KAAK;CAC/C;CAEA,MAAMN,uBAAuB,OAA2E;EACtG,IAAI,MAAM,eAAe,SAAS,SAAS,OAAO;EAClD,MAAM,aAAa,MAAM,eAAe;EACxC,MAAM,WAAW,MAAM,KAAKH,oBAAoB;EAChD,IAAI,SAAS,WAAW,GAAG,OAAO;EAClC,IAAI,SAAS,WAAW,GAAG;GACzB,MAAM,WAAW,MAAM,SAAS,EAAE,CAAE,OAAO,wBAAwB,KAAK;GACxE,OAAO,WAAW,KAAKU,uBAAuB,SAAS,EAAE,CAAE,WAAW,IAAI,QAAQ,IAAI;EACxF;EAKA,IAAI;EACJ,KAAK,MAAM,WAAW,UACpB,IAAI;GAKF,IAAI,CAAC,MAJe,QAAQ,OAAO,SAAS;IAC1C,YAAY,qBAAqB,QAAQ,WAAW,EAAE;IACtD,SAAS;GACX,CAAC,GACW;GACZ,MAAM,WAAW,MAAM,QAAQ,OAAO,wBAAwB,KAAK;GACnE,OAAO,WAAW,KAAKA,uBAAuB,QAAQ,WAAW,IAAI,QAAQ,IAAI;EACnF,SAAS,OAAO;GACd,IAAI,iBAAiB,sBAAsB,MAAM,WAAW,KAAK;GACjE,eAAe;EACjB;EAEF,IAAI,eAAe,KAAA,GAAW,MAAM;EACpC,OAAO;CACT;CAEA,uBAAuB,cAAsB,UAA0D;EACrG,OAAO;GACL,GAAG;GACH,YAAY,qBAAqB,YAAY;GAC7C,GAAI,SAAS,WAAW,EAAE,UAAU,qBAAqB,cAAc,SAAS,QAAQ,EAAE,IAAI,CAAC;EACjG;CACF;CAEA,MAAMN,eAAwC;EAC5C,MAAM,WAAW,MAAM,KAAKJ,oBAAoB;EAChD,MAAM,UAA0B,CAAC;EACjC,KAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,UAAU,QAAQ,WAAW;GACnC,MAAM,QAAQ,MAAM,QAAQ,OAAO,YAAY;IAAE,OAAO;IAAI,QAAQ;GAAG,CAAC;GACxE,KAAK,MAAM,UAAU,OACnB,QAAQ,KAAK;IACX,GAAG;IACH,IAAI,qBAAqB,QAAQ,WAAW,IAAI,OAAO,EAAE;IACzD,MAAM,SAAS,SAAS,KAAK,UAAU,GAAG,OAAO,KAAK,IAAI,QAAQ,KAAK,OAAO;IAC9E,UAAU;KACR,GAAI,OAAO,YAAY,CAAC;KACxB,cAAc,QAAQ,WAAW;KACjC,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;IAC/B;GACF,CAAC;EAEL;EACA,OAAO;CACT;CAEA,MAAMK,WAAW,OAAsD;EACrE,IAAI,MAAM,UAAU,WAAW,GAAG,OAAO;GAAE,OAAO,CAAC;GAAG,YAAY;EAAK;EACvE,MAAM,WAAW,MAAM,KAAKL,oBAAoB;EAChD,MAAM,SAAS,iBAAiB,MAAM,MAAM;EAK5C,IAAI,aAAa;EACjB,IAAI,aAAa;EACjB,IAAI,QAAQ;GACV,MAAM,QAAQ,SAAS,WAAU,YAAW,QAAQ,WAAW,OAAO,OAAO,YAAY;GACzF,aAAa,UAAU;GACvB,aAAa,aACT,QACA,SAAS,WAAU,YAAW,QAAQ,WAAW,GAAG,cAAc,OAAO,YAAY,IAAI,CAAC;GAC9F,IAAI,eAAe,IAAI,OAAO;IAAE,OAAO,CAAC;IAAG,YAAY;GAAK;EAC9D;EAEA,KAAK,IAAI,QAAQ,YAAY,QAAQ,SAAS,QAAQ,SAAS;GAC7D,MAAM,UAAU,SAAS;GACzB,MAAM,gBAAgB,iBAAiB,QAAQ,WAAW,IAAI,MAAM,SAAS;GAC7E,IAAI,cAAc,WAAW,GAAG;GAChC,MAAM,OAAO,MAAM,QAAQ,OAAO,UAAU;IAC1C,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,WAAW;IACX,GAAI,UAAU,cAAc,cAAc,QAAQ,QAAQ,EAAE,QAAQ,OAAO,MAAM,IAAI,CAAC;GACxF,CAAC;GACD,MAAM,aAAa,KAAK,aACpB,iBAAiB;IAAE,cAAc,QAAQ,WAAW;IAAI,OAAO,KAAK;GAAW,CAAC,IAChF,QAAQ,IAAI,SAAS,SACnB,iBAAiB,EAAE,cAAc,SAAS,QAAQ,EAAE,CAAE,WAAW,GAAG,CAAC,IACrE;GACN,OAAO;IACL,OAAO,KAAK,MAAM,KAAI,UAAS;KAC7B,GAAG;KACH,UAAU,qBAAqB,QAAQ,WAAW,IAAI,KAAK,QAAQ;KACnE,UAAU;MAAE,GAAI,KAAK,YAAY,CAAC;MAAI,cAAc,QAAQ,WAAW;KAAG;IAC5E,EAAE;IACF;GACF;EACF;EACA,OAAO;GAAE,OAAO,CAAC;GAAG,YAAY;EAAK;CACvC;CAEA,MAAMM,YAAY,OAA6F;EAC7G,MAAM,WAAW,MAAM,KAAKJ,qBAAqB,MAAM,UAAU;EAIjE,MAAM,eAAe,2BAA2B,SAAS,UAAU;EACnE,MAAM,gBAAgB,eAClB,iBAAiB,cAAc,MAAM,SAAS,IAC9C,MAAM,UAAU,KAAI,aAAY,qBAAqB,QAAQ,CAAC,EAAE,YAAY,QAAQ;EACxF,OAAO,SAAS,OAAO,WAAW;GAChC,GAAG;GACH,YAAY,SAAS;GACrB,WAAW;EACb,CAAC;CACH;CAEA,MAAMK,UAAU,OAA+D;EAC7E,MAAM,WAAW,MAAM,KAAKL,qBAAqB,MAAM,UAAU;EACjE,OAAO,SAAS,OAAO,SAAS;GAC9B,GAAG,KAAKS,gBAAgB,KAAK;GAC7B,YAAY,SAAS;EACvB,CAAC;CACH;CAEA,MAAMH,eAAe,OAAuE;EAC1F,MAAM,WAAW,MAAM,KAAKN,qBAAqB,MAAM,UAAU;EACjE,OAAO,SAAS,OAAO,cAAc;GACnC,GAAG,KAAKS,gBAAgB,KAAK;GAC7B,YAAY,SAAS;EACvB,CAAC;CACH;CAEA,MAAMF,aAAa,OAA4D;EAC7E,MAAM,WAAW,MAAM,KAAKP,qBAAqB,MAAM,UAAU;EACjE,OAAO,SAAS,OAAO,YAAY;GACjC,GAAG,KAAKS,gBAAgB,KAAK;GAC7B,YAAY,SAAS;EACvB,CAAC;CACH;CAEA,gBAAiD,OAAa;EAC5D,IAAI,CAAC,MAAM,UAAU,OAAO;EAC5B,MAAM,SAAS,qBAAqB,MAAM,QAAQ;EAClD,OAAO,SAAS;GAAE,GAAG;GAAO,UAAU,OAAO;EAAS,IAAI;CAC5D;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;;;;;;;CAQA,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,YAAY;GAAM,MAAM;GAAY,cAAc,KAAKnB;EAAc;CAChF;AACF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Jira tools exposed to the coding agent — `jira_get_issue` for reads and
3
+ * `jira_create_comment` for posting findings back to the issue, matching the
4
+ * Linear tool surface.
5
+ *
6
+ * Wired into the agent through the SDK's async `extraTools` provider: on each
7
+ * tool-set resolution we map the session's resourceId (the factory project
8
+ * id) to its owning org and only expose the Jira tools for real factory
9
+ * projects with an active Platform-managed Jira connection.
10
+ *
11
+ * Tenancy mirrors the Jira API routes: nothing is exposed without the host
12
+ * auth seam, and the session must resolve to an org-owned project.
13
+ */
14
+ import type { RequestContext } from '@mastra/core/request-context';
15
+ import type { PlatformJiraIntegration } from './integration.js';
16
+ declare function createJiraGetIssueTool(jira: PlatformJiraIntegration): import("@mastra/core/tools").Tool<{
17
+ issue: string;
18
+ }, unknown, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "jira_get_issue", unknown>;
19
+ declare function createJiraCommentTool(jira: PlatformJiraIntegration): import("@mastra/core/tools").Tool<{
20
+ issue: string;
21
+ body: string;
22
+ }, unknown, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, "jira_create_comment", unknown>;
23
+ /**
24
+ * Async `extraTools` provider: expose the Jira tools only when the host runs
25
+ * with web auth and the session's resource is an org-owned factory project.
26
+ *
27
+ * Intake source bindings scope the board feed; both tools resolve the issue
28
+ * through one of the organization's active Platform Jira connections.
29
+ */
30
+ export declare function buildPlatformJiraAgentTools({ requestContext, jira, }: {
31
+ requestContext: RequestContext;
32
+ /** The integration instance providing the Jira API client. */
33
+ jira: PlatformJiraIntegration;
34
+ }): Promise<Record<string, ReturnType<typeof createJiraGetIssueTool> | ReturnType<typeof createJiraCommentTool>>>;
35
+ export {};
36
+ //# sourceMappingURL=agent-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/jira/agent-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAOnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAYhE,iBAAS,sBAAsB,CAAC,IAAI,EAAE,uBAAuB;;uIAuB5D;AAED,iBAAS,qBAAqB,CAAC,IAAI,EAAE,uBAAuB;;;4IAyB3D;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAAC,EAChD,cAAc,EACd,IAAI,GACL,EAAE;IACD,cAAc,EAAE,cAAc,CAAC;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,uBAAuB,CAAC;CAC/B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC,CAmBhH"}
@@ -0,0 +1,85 @@
1
+ import { JiraApiError } from "../../jira/api.js";
2
+ import { JIRA_UNTRUSTED_CONTENT_NOTICE } from "../../jira/agent-tools.js";
3
+ import { createTool } from "@mastra/core/tools";
4
+ import { z } from "zod";
5
+ //#region src/integrations/platform/jira/agent-tools.ts
6
+ /** The `Intake` contract requires a connection argument; the Platform adapter resolves the real connection. */
7
+ const PLATFORM_CONNECTION = {
8
+ type: "oauth",
9
+ accessToken: "platform-managed"
10
+ };
11
+ function toolError(action, err) {
12
+ if (err instanceof JiraApiError && err.code === "jira_auth_failed") return { error: "Jira rejected the connected account. Reconnect it in Mastra Platform." };
13
+ return { error: `${action}: ${err instanceof Error ? err.message : String(err)}` };
14
+ }
15
+ function createJiraGetIssueTool(jira) {
16
+ return createTool({
17
+ id: "jira_get_issue",
18
+ description: "Fetch a Jira issue's full details — summary, description, status, assignee, labels, priority, and discussion comments. Use this whenever you're working on a Jira issue (e.g. ENG-123) to get its complete context.",
19
+ inputSchema: z.object({ issue: z.string().trim().min(1).describe("The Jira issue key (e.g. \"ENG-123\") or numeric issue id.") }),
20
+ execute: async ({ issue }) => {
21
+ try {
22
+ const detail = await jira.intake.getIssue({
23
+ connection: PLATFORM_CONNECTION,
24
+ issueId: issue
25
+ });
26
+ if (!detail) return { error: `Jira issue "${issue}" was not found on this site.` };
27
+ return {
28
+ notice: JIRA_UNTRUSTED_CONTENT_NOTICE,
29
+ ...detail
30
+ };
31
+ } catch (err) {
32
+ return toolError("Failed to fetch Jira issue", err);
33
+ }
34
+ }
35
+ });
36
+ }
37
+ function createJiraCommentTool(jira) {
38
+ return createTool({
39
+ id: "jira_create_comment",
40
+ description: "Post a comment on a Jira issue (e.g. to report investigation findings, link a PR, or ask a clarifying question). The comment is posted as the connected Jira account, so make clear it comes from the agent.",
41
+ inputSchema: z.object({
42
+ issue: z.string().trim().min(1).describe("The Jira issue key (e.g. \"ENG-123\") or numeric issue id."),
43
+ body: z.string().min(1).describe("The comment body, as plain text.")
44
+ }),
45
+ execute: async ({ issue, body }) => {
46
+ try {
47
+ const comment = await jira.intake.createComment({
48
+ connection: PLATFORM_CONNECTION,
49
+ issueId: issue,
50
+ body
51
+ });
52
+ if (!comment) return { error: `Jira issue "${issue}" was not found on this site.` };
53
+ return {
54
+ posted: true,
55
+ url: comment.url
56
+ };
57
+ } catch (err) {
58
+ return toolError("Failed to post Jira comment", err);
59
+ }
60
+ }
61
+ });
62
+ }
63
+ /**
64
+ * Async `extraTools` provider: expose the Jira tools only when the host runs
65
+ * with web auth and the session's resource is an org-owned factory project.
66
+ *
67
+ * Intake source bindings scope the board feed; both tools resolve the issue
68
+ * through one of the organization's active Platform Jira connections.
69
+ */
70
+ async function buildPlatformJiraAgentTools({ requestContext, jira }) {
71
+ if (!jira.authEnabled) return {};
72
+ const ctx = requestContext.get("controller");
73
+ if (!ctx) return {};
74
+ const projectId = ctx.getState().factoryProjectId ?? ctx.resourceId;
75
+ if (!projectId) return {};
76
+ if (!await jira.resolveOrgId(projectId) || !await jira.hasActiveConnections()) return {};
77
+ return {
78
+ jira_get_issue: createJiraGetIssueTool(jira),
79
+ jira_create_comment: createJiraCommentTool(jira)
80
+ };
81
+ }
82
+ //#endregion
83
+ export { buildPlatformJiraAgentTools };
84
+
85
+ //# sourceMappingURL=agent-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tools.js","names":[],"sources":["../../../../src/integrations/platform/jira/agent-tools.ts"],"sourcesContent":["/**\n * Jira tools exposed to the coding agent — `jira_get_issue` for reads and\n * `jira_create_comment` for posting findings back to the issue, matching the\n * Linear tool surface.\n *\n * Wired into the agent through the SDK's async `extraTools` provider: on each\n * tool-set resolution we map the session's resourceId (the factory project\n * id) to its owning org and only expose the Jira tools for real factory\n * projects with an active Platform-managed Jira connection.\n *\n * Tenancy mirrors the Jira API routes: nothing is exposed without the host\n * auth seam, and the session must resolve to an org-owned project.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport { JIRA_UNTRUSTED_CONTENT_NOTICE } from '../../jira/agent-tools.js';\nimport { JiraApiError } from '../../jira/api.js';\nimport type { PlatformJiraIntegration } from './integration.js';\n\n/** The `Intake` contract requires a connection argument; the Platform adapter resolves the real connection. */\nconst PLATFORM_CONNECTION: IntegrationConnection = { type: 'oauth', accessToken: 'platform-managed' };\n\nfunction toolError(action: string, err: unknown): { error: string } {\n if (err instanceof JiraApiError && err.code === 'jira_auth_failed') {\n return { error: 'Jira rejected the connected account. Reconnect it in Mastra Platform.' };\n }\n return { error: `${action}: ${err instanceof Error ? err.message : String(err)}` };\n}\n\nfunction createJiraGetIssueTool(jira: PlatformJiraIntegration) {\n return createTool({\n id: 'jira_get_issue',\n description:\n \"Fetch a Jira issue's full details — summary, description, status, assignee, labels, priority, and discussion comments. Use this whenever you're working on a Jira issue (e.g. ENG-123) to get its complete context.\",\n inputSchema: z.object({\n issue: z.string().trim().min(1).describe('The Jira issue key (e.g. \"ENG-123\") or numeric issue id.'),\n }),\n execute: async ({ issue }: { issue: string }) => {\n try {\n const detail = await jira.intake.getIssue({\n connection: PLATFORM_CONNECTION,\n issueId: issue,\n });\n if (!detail) {\n return { error: `Jira issue \"${issue}\" was not found on this site.` };\n }\n return { notice: JIRA_UNTRUSTED_CONTENT_NOTICE, ...detail };\n } catch (err) {\n return toolError('Failed to fetch Jira issue', err);\n }\n },\n });\n}\n\nfunction createJiraCommentTool(jira: PlatformJiraIntegration) {\n return createTool({\n id: 'jira_create_comment',\n description:\n 'Post a comment on a Jira issue (e.g. to report investigation findings, link a PR, or ask a clarifying question). The comment is posted as the connected Jira account, so make clear it comes from the agent.',\n inputSchema: z.object({\n issue: z.string().trim().min(1).describe('The Jira issue key (e.g. \"ENG-123\") or numeric issue id.'),\n body: z.string().min(1).describe('The comment body, as plain text.'),\n }),\n execute: async ({ issue, body }: { issue: string; body: string }) => {\n try {\n const comment = await jira.intake.createComment({\n connection: PLATFORM_CONNECTION,\n issueId: issue,\n body,\n });\n if (!comment) {\n return { error: `Jira issue \"${issue}\" was not found on this site.` };\n }\n return { posted: true, url: comment.url };\n } catch (err) {\n return toolError('Failed to post Jira comment', err);\n }\n },\n });\n}\n\n/**\n * Async `extraTools` provider: expose the Jira tools only when the host runs\n * with web auth and the session's resource is an org-owned factory project.\n *\n * Intake source bindings scope the board feed; both tools resolve the issue\n * through one of the organization's active Platform Jira connections.\n */\nexport async function buildPlatformJiraAgentTools({\n requestContext,\n jira,\n}: {\n requestContext: RequestContext;\n /** The integration instance providing the Jira API client. */\n jira: PlatformJiraIntegration;\n}): Promise<Record<string, ReturnType<typeof createJiraGetIssueTool> | ReturnType<typeof createJiraCommentTool>>> {\n if (!jira.authEnabled) return {};\n\n const ctx = requestContext.get('controller') as\n AgentControllerRequestContext<{ factoryProjectId?: string }> | undefined;\n if (!ctx) return {};\n\n // Board-run resourceId is the work-item session id, not the project id stored\n // in factory_projects. Project-scoped sessions may not carry factoryProjectId.\n const projectId = ctx.getState().factoryProjectId ?? ctx.resourceId;\n if (!projectId) return {};\n\n const orgId = await jira.resolveOrgId(projectId);\n if (!orgId || !(await jira.hasActiveConnections())) return {};\n\n return {\n jira_get_issue: createJiraGetIssueTool(jira),\n jira_create_comment: createJiraCommentTool(jira),\n };\n}\n"],"mappings":";;;;;;AAyBA,MAAM,sBAA6C;CAAE,MAAM;CAAS,aAAa;AAAmB;AAEpG,SAAS,UAAU,QAAgB,KAAiC;CAClE,IAAI,eAAe,gBAAgB,IAAI,SAAS,oBAC9C,OAAO,EAAE,OAAO,wEAAwE;CAE1F,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,IAAI;AACnF;AAEA,SAAS,uBAAuB,MAA+B;CAC7D,OAAO,WAAW;EAChB,IAAI;EACJ,aACE;EACF,aAAa,EAAE,OAAO,EACpB,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,4DAA0D,EACrG,CAAC;EACD,SAAS,OAAO,EAAE,YAA+B;GAC/C,IAAI;IACF,MAAM,SAAS,MAAM,KAAK,OAAO,SAAS;KACxC,YAAY;KACZ,SAAS;IACX,CAAC;IACD,IAAI,CAAC,QACH,OAAO,EAAE,OAAO,eAAe,MAAM,+BAA+B;IAEtE,OAAO;KAAE,QAAQ;KAA+B,GAAG;IAAO;GAC5D,SAAS,KAAK;IACZ,OAAO,UAAU,8BAA8B,GAAG;GACpD;EACF;CACF,CAAC;AACH;AAEA,SAAS,sBAAsB,MAA+B;CAC5D,OAAO,WAAW;EAChB,IAAI;EACJ,aACE;EACF,aAAa,EAAE,OAAO;GACpB,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,4DAA0D;GACnG,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,kCAAkC;EACrE,CAAC;EACD,SAAS,OAAO,EAAE,OAAO,WAA4C;GACnE,IAAI;IACF,MAAM,UAAU,MAAM,KAAK,OAAO,cAAc;KAC9C,YAAY;KACZ,SAAS;KACT;IACF,CAAC;IACD,IAAI,CAAC,SACH,OAAO,EAAE,OAAO,eAAe,MAAM,+BAA+B;IAEtE,OAAO;KAAE,QAAQ;KAAM,KAAK,QAAQ;IAAI;GAC1C,SAAS,KAAK;IACZ,OAAO,UAAU,+BAA+B,GAAG;GACrD;EACF;CACF,CAAC;AACH;;;;;;;;AASA,eAAsB,4BAA4B,EAChD,gBACA,QAKgH;CAChH,IAAI,CAAC,KAAK,aAAa,OAAO,CAAC;CAE/B,MAAM,MAAM,eAAe,IAAI,YAAY;CAE3C,IAAI,CAAC,KAAK,OAAO,CAAC;CAIlB,MAAM,YAAY,IAAI,SAAS,CAAC,CAAC,oBAAoB,IAAI;CACzD,IAAI,CAAC,WAAW,OAAO,CAAC;CAGxB,IAAI,CAAC,MADe,KAAK,aAAa,SAAS,KACjC,CAAE,MAAM,KAAK,qBAAqB,GAAI,OAAO,CAAC;CAE5D,OAAO;EACL,gBAAgB,uBAAuB,IAAI;EAC3C,qBAAqB,sBAAsB,IAAI;CACjD;AACF"}