@mastra/factory 0.17.3-alpha.5 → 0.18.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 (178) hide show
  1. package/dist/auth.d.ts.map +1 -1
  2. package/dist/auth.js +15 -4
  3. package/dist/auth.js.map +1 -1
  4. package/dist/boards/review.d.ts +2 -0
  5. package/dist/boards/review.d.ts.map +1 -1
  6. package/dist/boards/review.js +16 -6
  7. package/dist/boards/review.js.map +1 -1
  8. package/dist/boards/transition-policy.d.ts +3 -3
  9. package/dist/boards/transition-policy.d.ts.map +1 -1
  10. package/dist/boards/transition-policy.js.map +1 -1
  11. package/dist/boards/work-transition-policy.d.ts.map +1 -1
  12. package/dist/boards/work-transition-policy.js +13 -3
  13. package/dist/boards/work-transition-policy.js.map +1 -1
  14. package/dist/capabilities/intake.d.ts +2 -0
  15. package/dist/capabilities/intake.d.ts.map +1 -1
  16. package/dist/factory.d.ts.map +1 -1
  17. package/dist/factory.js +14 -3
  18. package/dist/factory.js.map +1 -1
  19. package/dist/integrations/github/default-rules.d.ts +17 -1
  20. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  21. package/dist/integrations/github/default-rules.js +30 -6
  22. package/dist/integrations/github/default-rules.js.map +1 -1
  23. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  24. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  25. package/dist/integrations/github/routes.d.ts.map +1 -1
  26. package/dist/integrations/github/routes.js +1 -0
  27. package/dist/integrations/github/routes.js.map +1 -1
  28. package/dist/integrations/github/rules.d.ts.map +1 -1
  29. package/dist/integrations/github/rules.js +48 -2
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  32. package/dist/integrations/github/sandbox.js.map +1 -1
  33. package/dist/integrations/gitlab/api.d.ts.map +1 -1
  34. package/dist/integrations/gitlab/api.js.map +1 -1
  35. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  36. package/dist/integrations/gitlab/default-rules.js +1 -0
  37. package/dist/integrations/gitlab/default-rules.js.map +1 -1
  38. package/dist/integrations/gitlab/reconciler.d.ts.map +1 -1
  39. package/dist/integrations/gitlab/reconciler.js.map +1 -1
  40. package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -1
  41. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  42. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/routes.js +1 -0
  44. package/dist/integrations/gitlab/routes.js.map +1 -1
  45. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  46. package/dist/integrations/gitlab/rules.js +66 -0
  47. package/dist/integrations/gitlab/rules.js.map +1 -1
  48. package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
  49. package/dist/integrations/gitlab/version-control.js.map +1 -1
  50. package/dist/integrations/incidentio/api.d.ts.map +1 -1
  51. package/dist/integrations/incidentio/api.js.map +1 -1
  52. package/dist/integrations/incidentio/intake.d.ts.map +1 -1
  53. package/dist/integrations/incidentio/intake.js.map +1 -1
  54. package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -1
  55. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  56. package/dist/integrations/linear/default-rules.d.ts +2 -1
  57. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  58. package/dist/integrations/linear/default-rules.js +1 -0
  59. package/dist/integrations/linear/default-rules.js.map +1 -1
  60. package/dist/integrations/linear/integration.d.ts.map +1 -1
  61. package/dist/integrations/linear/integration.js +2 -0
  62. package/dist/integrations/linear/integration.js.map +1 -1
  63. package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
  64. package/dist/integrations/linear/issue-reconciler.js +3 -0
  65. package/dist/integrations/linear/issue-reconciler.js.map +1 -1
  66. package/dist/integrations/linear/routes.d.ts.map +1 -1
  67. package/dist/integrations/linear/routes.js +2 -1
  68. package/dist/integrations/linear/routes.js.map +1 -1
  69. package/dist/integrations/linear/rules.d.ts +1 -0
  70. package/dist/integrations/linear/rules.d.ts.map +1 -1
  71. package/dist/integrations/linear/rules.js.map +1 -1
  72. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  73. package/dist/integrations/slack/emoji-shortcodes.generated.js.map +1 -1
  74. package/dist/integrations/slack/feed-publisher.d.ts.map +1 -1
  75. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  76. package/dist/integrations/slack/slack.d.ts +7 -7
  77. package/dist/integrations/slack/slack.d.ts.map +1 -1
  78. package/dist/integrations/slack/slack.js +157 -23
  79. package/dist/integrations/slack/slack.js.map +1 -1
  80. package/dist/integrations/subscription-session.d.ts +3 -1
  81. package/dist/integrations/subscription-session.d.ts.map +1 -1
  82. package/dist/integrations/subscription-session.js +4 -2
  83. package/dist/integrations/subscription-session.js.map +1 -1
  84. package/dist/platform-auth-host.d.ts +4 -0
  85. package/dist/platform-auth-host.d.ts.map +1 -0
  86. package/dist/platform-auth-host.js +18 -0
  87. package/dist/platform-auth-host.js.map +1 -0
  88. package/dist/routes/contracts.d.ts +23 -0
  89. package/dist/routes/contracts.d.ts.map +1 -1
  90. package/dist/routes/contracts.js +21 -0
  91. package/dist/routes/contracts.js.map +1 -1
  92. package/dist/routes/intake.d.ts.map +1 -1
  93. package/dist/routes/intake.js +15 -1
  94. package/dist/routes/intake.js.map +1 -1
  95. package/dist/routes/projects.d.ts +22 -2
  96. package/dist/routes/projects.d.ts.map +1 -1
  97. package/dist/routes/projects.js +101 -0
  98. package/dist/routes/projects.js.map +1 -1
  99. package/dist/routes/surface.d.ts +1 -1
  100. package/dist/routes/surface.d.ts.map +1 -1
  101. package/dist/routes/surface.js +47 -6
  102. package/dist/routes/surface.js.map +1 -1
  103. package/dist/routes/work-items.d.ts.map +1 -1
  104. package/dist/routes/work-items.js +6 -1
  105. package/dist/routes/work-items.js.map +1 -1
  106. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  107. package/dist/rules/dispatch-errors.js +4 -0
  108. package/dist/rules/dispatch-errors.js.map +1 -1
  109. package/dist/rules/dispatcher.d.ts.map +1 -1
  110. package/dist/rules/dispatcher.js +14 -6
  111. package/dist/rules/dispatcher.js.map +1 -1
  112. package/dist/rules/processor.d.ts.map +1 -1
  113. package/dist/rules/processor.js +9 -1
  114. package/dist/rules/processor.js.map +1 -1
  115. package/dist/rules/review-source-tool.d.ts +103 -0
  116. package/dist/rules/review-source-tool.d.ts.map +1 -0
  117. package/dist/rules/review-source-tool.js +109 -0
  118. package/dist/rules/review-source-tool.js.map +1 -0
  119. package/dist/rules/start-coordinator.d.ts +7 -1
  120. package/dist/rules/start-coordinator.d.ts.map +1 -1
  121. package/dist/rules/start-coordinator.js +56 -2
  122. package/dist/rules/start-coordinator.js.map +1 -1
  123. package/dist/rules/tools.d.ts +1 -0
  124. package/dist/rules/tools.d.ts.map +1 -1
  125. package/dist/rules/tools.js +107 -32
  126. package/dist/rules/tools.js.map +1 -1
  127. package/dist/rules/transition-service.d.ts +4 -4
  128. package/dist/rules/transition-service.d.ts.map +1 -1
  129. package/dist/rules/transition-service.js +3 -2
  130. package/dist/rules/transition-service.js.map +1 -1
  131. package/dist/rules/types.d.ts +2 -0
  132. package/dist/rules/types.d.ts.map +1 -1
  133. package/dist/rules/types.js.map +1 -1
  134. package/dist/sandbox/materialization.d.ts +5 -1
  135. package/dist/sandbox/materialization.d.ts.map +1 -1
  136. package/dist/sandbox/materialization.js.map +1 -1
  137. package/dist/sandbox/session-sandbox.d.ts +13 -1
  138. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  139. package/dist/sandbox/session-sandbox.js.map +1 -1
  140. package/dist/session/factory-session.d.ts +14 -4
  141. package/dist/session/factory-session.d.ts.map +1 -1
  142. package/dist/session/factory-session.js +32 -11
  143. package/dist/session/factory-session.js.map +1 -1
  144. package/dist/session/work-item-repository.d.ts +21 -0
  145. package/dist/session/work-item-repository.d.ts.map +1 -0
  146. package/dist/session/work-item-repository.js +91 -0
  147. package/dist/session/work-item-repository.js.map +1 -0
  148. package/dist/skills/service.d.ts.map +1 -1
  149. package/dist/skills/service.js +16 -6
  150. package/dist/skills/service.js.map +1 -1
  151. package/dist/storage/domains/intake/base.d.ts +1 -0
  152. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  153. package/dist/storage/domains/intake/base.js.map +1 -1
  154. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  155. package/dist/storage/domains/integrations/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/base.d.ts +15 -2
  157. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  158. package/dist/storage/domains/work-items/base.js +31 -1
  159. package/dist/storage/domains/work-items/base.js.map +1 -1
  160. package/dist/supervisor/session-messaging.d.ts +4 -1
  161. package/dist/supervisor/session-messaging.d.ts.map +1 -1
  162. package/dist/supervisor/session-messaging.js +8 -2
  163. package/dist/supervisor/session-messaging.js.map +1 -1
  164. package/dist/supervisor/write-tools.d.ts +2 -1
  165. package/dist/supervisor/write-tools.d.ts.map +1 -1
  166. package/dist/supervisor/write-tools.js +9 -2
  167. package/dist/supervisor/write-tools.js.map +1 -1
  168. package/dist/workspace.d.ts +7 -1
  169. package/dist/workspace.d.ts.map +1 -1
  170. package/dist/workspace.js +53 -14
  171. package/dist/workspace.js.map +1 -1
  172. package/factory-skills/factory-complete-issue/SKILL.md +4 -2
  173. package/factory-skills/factory-gitlab-rereview/SKILL.md +3 -2
  174. package/factory-skills/factory-gitlab-review/SKILL.md +19 -3
  175. package/factory-skills/factory-plan/SKILL.md +11 -0
  176. package/factory-skills/factory-rereview/SKILL.md +37 -12
  177. package/factory-skills/factory-review/SKILL.md +37 -12
  178. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconcile-worker.js","names":["#integrationId","#reconcile","#intervalMs","#leaseTtlMs","#leaseKey","#leaseOwner","#leaseProvider","#running","#schedule","#timer","#inFlight","#tick"],"sources":["../../src/integrations/issue-reconcile-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IssueReconciler } from './issue-reconciler.js';\n\nexport const DEFAULT_ISSUE_RECONCILE_INTERVAL_MS = 5 * 60_000;\nconst MIN_LEASE_TTL_MS = 30_000;\n\nexport interface IssueReconcileWorkerConfig {\n integrationId: string;\n reconcile: IssueReconciler;\n intervalMs?: number;\n}\n\nexport class IssueReconcileWorker extends MastraWorker {\n readonly name: string;\n\n readonly #integrationId: string;\n readonly #reconcile: IssueReconciler;\n readonly #intervalMs: number;\n readonly #leaseTtlMs: number;\n readonly #leaseKey: string;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n\n constructor(config: IssueReconcileWorkerConfig) {\n super();\n this.#integrationId = config.integrationId;\n this.name = `${config.integrationId}-issue-reconcile`;\n this.#leaseKey = `${config.integrationId}:issue-reconcile`;\n this.#reconcile = config.reconcile;\n this.#intervalMs = config.intervalMs ?? DEFAULT_ISSUE_RECONCILE_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error(`${config.integrationId} issue reconcile interval must be a positive number.`);\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);\n }\n\n async init(deps: WorkerDeps): Promise<void> {\n await super.init(deps);\n this.#leaseProvider = getLeaseProvider(deps.pubsub);\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('IssueReconcileWorker: call init() before start()');\n this.#running = true;\n this.deps.logger.info(`${this.#integrationId} issue reconcile worker started`, { intervalMs: this.#intervalMs });\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n if (!this.#running) return;\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n await this.#inFlight;\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const run = this.#tick().finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#intervalMs);\n });\n this.#inFlight = run;\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n const lease = await this.#leaseProvider\n .acquireLease(this.#leaseKey, this.#leaseOwner, this.#leaseTtlMs)\n .catch(() => ({ acquired: false }));\n if (!lease.acquired) return;\n\n // A sweep that traverses many projects/orgs can outrun the lease TTL;\n // renew periodically so a replica can't grab the expired lease and start\n // an overlapping sweep partway through this one.\n const renewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(this.#leaseKey, this.#leaseOwner, this.#leaseTtlMs)\n .catch(error => {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile lease renewal failed`, {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.max(1_000, Math.floor(this.#leaseTtlMs / 3)),\n );\n renewalTimer.unref?.();\n\n try {\n const startedAt = Date.now();\n const { errors, ...counts } = await this.#reconcile();\n const context = { ...counts, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile sweep completed with failures`, {\n ...context,\n errors,\n });\n } else {\n this.deps?.logger.debug(`${this.#integrationId} issue reconcile sweep completed`, context);\n }\n } catch (error) {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile sweep failed`, {\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n clearInterval(renewalTimer);\n await this.#leaseProvider.releaseLease(this.#leaseKey, this.#leaseOwner).catch(() => undefined);\n }\n }\n}\n\nfunction getLeaseProvider(pubsub: PubSub): LeaseProvider {\n const getProvider = (pubsub as PubSub & { getLeaseProvider?: () => LeaseProvider | undefined }).getLeaseProvider;\n if (typeof getProvider === 'function') return getProvider.call(pubsub) ?? NoopLeaseProvider;\n return isLeaseProvider(pubsub)\n ? pubsub\n : ((pubsub as PubSub & { lease?: LeaseProvider }).lease ?? NoopLeaseProvider);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issue-reconcile-worker.js","names":["#integrationId","#reconcile","#intervalMs","#leaseTtlMs","#leaseKey","#leaseOwner","#leaseProvider","#running","#schedule","#timer","#inFlight","#tick"],"sources":["../../src/integrations/issue-reconcile-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IssueReconciler } from './issue-reconciler.js';\n\nexport const DEFAULT_ISSUE_RECONCILE_INTERVAL_MS = 5 * 60_000;\nconst MIN_LEASE_TTL_MS = 30_000;\n\nexport interface IssueReconcileWorkerConfig {\n integrationId: string;\n reconcile: IssueReconciler;\n intervalMs?: number;\n}\n\nexport class IssueReconcileWorker extends MastraWorker {\n readonly name: string;\n\n readonly #integrationId: string;\n readonly #reconcile: IssueReconciler;\n readonly #intervalMs: number;\n readonly #leaseTtlMs: number;\n readonly #leaseKey: string;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n\n constructor(config: IssueReconcileWorkerConfig) {\n super();\n this.#integrationId = config.integrationId;\n this.name = `${config.integrationId}-issue-reconcile`;\n this.#leaseKey = `${config.integrationId}:issue-reconcile`;\n this.#reconcile = config.reconcile;\n this.#intervalMs = config.intervalMs ?? DEFAULT_ISSUE_RECONCILE_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error(`${config.integrationId} issue reconcile interval must be a positive number.`);\n }\n this.#leaseTtlMs = Math.max(MIN_LEASE_TTL_MS, this.#intervalMs * 3);\n }\n\n async init(deps: WorkerDeps): Promise<void> {\n await super.init(deps);\n this.#leaseProvider = getLeaseProvider(deps.pubsub);\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('IssueReconcileWorker: call init() before start()');\n this.#running = true;\n this.deps.logger.info(`${this.#integrationId} issue reconcile worker started`, { intervalMs: this.#intervalMs });\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n if (!this.#running) return;\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n await this.#inFlight;\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const run = this.#tick().finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#intervalMs);\n });\n this.#inFlight = run;\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n const lease = await this.#leaseProvider\n .acquireLease(this.#leaseKey, this.#leaseOwner, this.#leaseTtlMs)\n .catch(() => ({ acquired: false }));\n if (!lease.acquired) return;\n\n // A sweep that traverses many projects/orgs can outrun the lease TTL;\n // renew periodically so a replica can't grab the expired lease and start\n // an overlapping sweep partway through this one.\n const renewalTimer = setInterval(\n () => {\n void this.#leaseProvider.renewLease(this.#leaseKey, this.#leaseOwner, this.#leaseTtlMs).catch(error => {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile lease renewal failed`, {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.max(1_000, Math.floor(this.#leaseTtlMs / 3)),\n );\n renewalTimer.unref?.();\n\n try {\n const startedAt = Date.now();\n const { errors, ...counts } = await this.#reconcile();\n const context = { ...counts, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile sweep completed with failures`, {\n ...context,\n errors,\n });\n } else {\n this.deps?.logger.debug(`${this.#integrationId} issue reconcile sweep completed`, context);\n }\n } catch (error) {\n this.deps?.logger.warn(`${this.#integrationId} issue reconcile sweep failed`, {\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n clearInterval(renewalTimer);\n await this.#leaseProvider.releaseLease(this.#leaseKey, this.#leaseOwner).catch(() => undefined);\n }\n }\n}\n\nfunction getLeaseProvider(pubsub: PubSub): LeaseProvider {\n const getProvider = (pubsub as PubSub & { getLeaseProvider?: () => LeaseProvider | undefined }).getLeaseProvider;\n if (typeof getProvider === 'function') return getProvider.call(pubsub) ?? NoopLeaseProvider;\n return isLeaseProvider(pubsub) ? pubsub : ((pubsub as PubSub & { lease?: LeaseProvider }).lease ?? NoopLeaseProvider);\n}\n"],"mappings":"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"}
@@ -15,7 +15,6 @@ declare function linearIssueObserved(context: FactoryLinearRuleContext): {
15
15
  readonly creator?: string | undefined;
16
16
  readonly author?: string | undefined;
17
17
  readonly assignee?: string | undefined;
18
- readonly linearIssueId: string;
19
18
  readonly identifier: string;
20
19
  readonly sourceCreatedAt: string;
21
20
  readonly linearState: string;
@@ -25,6 +24,8 @@ declare function linearIssueObserved(context: FactoryLinearRuleContext): {
25
24
  readonly linearCreator: string | null;
26
25
  readonly linearTeam: string | null;
27
26
  readonly labels: string[];
27
+ readonly linearProjectId?: string | undefined;
28
+ readonly linearIssueId: string;
28
29
  };
29
30
  } | undefined;
30
31
  declare function linearIssueClosed(context: FactoryLinearRuleContext): {
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/default-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGjH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,MAAM,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,wBAAwB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAChG,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CACrC,MAAM,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,wBAAwB,CAAC,GAAG,IAAI,CAAC,CACpF,CAAC;AAEF,iBAAS,mBAAmB,CAAC,OAAO,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;yBAwBnB,MAAM,EAAE;;cAKlD;AAED,iBAAS,iBAAiB,CAAC,OAAO,EAAE,wBAAwB;;;;;;;;cAkB3D;AAED,eAAO,MAAM,kBAAkB;;;EAGF,CAAC;AAE9B,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAsBpF"}
1
+ {"version":3,"file":"default-rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/default-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGjH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,MAAM,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,wBAAwB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAChG,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CACrC,MAAM,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,wBAAwB,CAAC,GAAG,IAAI,CAAC,CACpF,CAAC;AAEF,iBAAS,mBAAmB,CAAC,OAAO,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;yBAyBnB,MAAM,EAAE;;;;cAKlD;AAED,iBAAS,iBAAiB,CAAC,OAAO,EAAE,wBAAwB;;;;;;;;cAkB3D;AAED,eAAO,MAAM,kBAAkB;;;EAGF,CAAC;AAE9B,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,CAsBpF"}
@@ -14,6 +14,7 @@ function linearIssueObserved(context) {
14
14
  stage: context.intake?.initialPhase ?? "intake",
15
15
  metadata: {
16
16
  linearIssueId: context.issue.id,
17
+ ...context.issue.projectId ? { linearProjectId: context.issue.projectId } : {},
17
18
  identifier: context.issue.identifier,
18
19
  sourceCreatedAt: context.issue.createdAt,
19
20
  linearState: context.issue.state,
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/linear/default-rules.ts"],"sourcesContent":["import type { FactoryLinearEventName, FactoryLinearRuleContext, FactoryRuleHandler } from '../../rules/types.js';\nimport { linearClaimKey } from './claim.js';\n\nexport type LinearRuleOverrides = Partial<\n Record<FactoryLinearEventName, FactoryRuleHandler<FactoryLinearRuleContext> | null | undefined>\n>;\nexport type LinearEventRules = Readonly<\n Record<FactoryLinearEventName, FactoryRuleHandler<FactoryLinearRuleContext> | null>\n>;\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n // A source bound to a custom board lands on that board's initial phase;\n // otherwise the issue waits in Work Intake.\n board: context.intake?.board ?? 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n claimKey: linearClaimKey(context.issue.id),\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n sourceCreatedAt: context.issue.createdAt,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nexport const defaultLinearRules = Object.freeze({\n issueObserved: linearIssueObserved,\n issueClosed: linearIssueClosed,\n} satisfies LinearEventRules);\n\nexport function resolveLinearRules(overrides?: LinearRuleOverrides): LinearEventRules {\n if (\n overrides !== undefined &&\n (overrides === null ||\n typeof overrides !== 'object' ||\n Array.isArray(overrides) ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))\n ) {\n throw new Error('Linear rules must be a plain object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryLinearRuleContext> | null> = { ...defaultLinearRules };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultLinearRules, key)) {\n throw new Error(`Unknown Linear rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryLinearEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`Linear rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return Object.freeze(rules) as LinearEventRules;\n}\n"],"mappings":";;AAUA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EAGtC,OAAO,QAAQ,QAAQ,SAAS;EAChC,QAAQ;EACR,WAAW,UAAU,QAAQ,MAAM;EACnC,UAAU,eAAe,QAAQ,MAAM,EAAE;EACzC,OAAO,GAAG,QAAQ,MAAM,WAAW,IAAI,QAAQ,MAAM;EACrD,KAAK,QAAQ,MAAM;EACnB,OAAO,QAAQ,QAAQ,gBAAgB;EACvC,UAAU;GACR,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,iBAAiB,QAAQ,MAAM;GAC/B,aAAa,QAAQ,MAAM;GAC3B,iBAAiB,QAAQ,MAAM;GAC/B,gBAAgB,QAAQ,MAAM;GAC9B,gBAAgB,QAAQ,MAAM;GAC9B,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,GAAI,QAAQ,MAAM,WAAW,EAAE,UAAU,QAAQ,MAAM,SAAS,IAAI,CAAC;GACrE,GAAI,QAAQ,MAAM,UAAU;IAAE,SAAS,QAAQ,MAAM;IAAS,QAAQ,QAAQ,MAAM;GAAQ,IAAI,CAAC;EACnG;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,gBAAgB;CAC7D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAEjF,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MAAM,gBAAgB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GAC3J;CACF;AACF;AAEA,MAAa,qBAAqB,OAAO,OAAO;CAC9C,eAAe;CACf,aAAa;AACf,CAA4B;AAE5B,SAAgB,mBAAmB,WAAmD;CACpF,IACE,cAAc,KAAA,MACb,cAAc,QACb,OAAO,cAAc,YACrB,MAAM,QAAQ,SAAS,KACvB,CAAC,CAAC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS,OAAO,eAAe,SAAS,CAAC,IAErE,MAAM,IAAI,MAAM,sCAAsC;CAExD,MAAM,QAA6E,EAAE,GAAG,mBAAmB;CAC3G,KAAK,MAAM,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;EAClD,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,OAAO,oBAAoB,GAAG,GACnE,MAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG,EAAE,EAAE;EAE9D,MAAM,UAAU,YAAY;EAC5B,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,OAAO,YAAY,YAClE,MAAM,IAAI,MAAM,eAAe,IAAI,yCAAyC;EAE9E,IAAI,YAAY,KAAA,GAAW,MAAM,OAAO;CAC1C;CACA,OAAO,OAAO,OAAO,KAAK;AAC5B"}
1
+ {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/linear/default-rules.ts"],"sourcesContent":["import type { FactoryLinearEventName, FactoryLinearRuleContext, FactoryRuleHandler } from '../../rules/types.js';\nimport { linearClaimKey } from './claim.js';\n\nexport type LinearRuleOverrides = Partial<\n Record<FactoryLinearEventName, FactoryRuleHandler<FactoryLinearRuleContext> | null | undefined>\n>;\nexport type LinearEventRules = Readonly<\n Record<FactoryLinearEventName, FactoryRuleHandler<FactoryLinearRuleContext> | null>\n>;\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n // A source bound to a custom board lands on that board's initial phase;\n // otherwise the issue waits in Work Intake.\n board: context.intake?.board ?? 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n claimKey: linearClaimKey(context.issue.id),\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'intake',\n metadata: {\n linearIssueId: context.issue.id,\n ...(context.issue.projectId ? { linearProjectId: context.issue.projectId } : {}),\n identifier: context.issue.identifier,\n sourceCreatedAt: context.issue.createdAt,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nexport const defaultLinearRules = Object.freeze({\n issueObserved: linearIssueObserved,\n issueClosed: linearIssueClosed,\n} satisfies LinearEventRules);\n\nexport function resolveLinearRules(overrides?: LinearRuleOverrides): LinearEventRules {\n if (\n overrides !== undefined &&\n (overrides === null ||\n typeof overrides !== 'object' ||\n Array.isArray(overrides) ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))\n ) {\n throw new Error('Linear rules must be a plain object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryLinearRuleContext> | null> = { ...defaultLinearRules };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultLinearRules, key)) {\n throw new Error(`Unknown Linear rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryLinearEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`Linear rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return Object.freeze(rules) as LinearEventRules;\n}\n"],"mappings":";;AAUA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EAGtC,OAAO,QAAQ,QAAQ,SAAS;EAChC,QAAQ;EACR,WAAW,UAAU,QAAQ,MAAM;EACnC,UAAU,eAAe,QAAQ,MAAM,EAAE;EACzC,OAAO,GAAG,QAAQ,MAAM,WAAW,IAAI,QAAQ,MAAM;EACrD,KAAK,QAAQ,MAAM;EACnB,OAAO,QAAQ,QAAQ,gBAAgB;EACvC,UAAU;GACR,eAAe,QAAQ,MAAM;GAC7B,GAAI,QAAQ,MAAM,YAAY,EAAE,iBAAiB,QAAQ,MAAM,UAAU,IAAI,CAAC;GAC9E,YAAY,QAAQ,MAAM;GAC1B,iBAAiB,QAAQ,MAAM;GAC/B,aAAa,QAAQ,MAAM;GAC3B,iBAAiB,QAAQ,MAAM;GAC/B,gBAAgB,QAAQ,MAAM;GAC9B,gBAAgB,QAAQ,MAAM;GAC9B,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,GAAI,QAAQ,MAAM,WAAW,EAAE,UAAU,QAAQ,MAAM,SAAS,IAAI,CAAC;GACrE,GAAI,QAAQ,MAAM,UAAU;IAAE,SAAS,QAAQ,MAAM;IAAS,QAAQ,QAAQ,MAAM;GAAQ,IAAI,CAAC;EACnG;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,gBAAgB;CAC7D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAEjF,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MAAM,gBAAgB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GAC3J;CACF;AACF;AAEA,MAAa,qBAAqB,OAAO,OAAO;CAC9C,eAAe;CACf,aAAa;AACf,CAA4B;AAE5B,SAAgB,mBAAmB,WAAmD;CACpF,IACE,cAAc,KAAA,MACb,cAAc,QACb,OAAO,cAAc,YACrB,MAAM,QAAQ,SAAS,KACvB,CAAC,CAAC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS,OAAO,eAAe,SAAS,CAAC,IAErE,MAAM,IAAI,MAAM,sCAAsC;CAExD,MAAM,QAA6E,EAAE,GAAG,mBAAmB;CAC3G,KAAK,MAAM,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;EAClD,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,OAAO,oBAAoB,GAAG,GACnE,MAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG,EAAE,EAAE;EAE9D,MAAM,UAAU,YAAY;EAC5B,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,OAAO,YAAY,YAClE,MAAM,IAAI,MAAM,eAAe,IAAI,yCAAyC;EAE9E,IAAI,YAAY,KAAA,GAAW,MAAM,OAAO;CAC1C;CACA,OAAO,OAAO,OAAO,KAAK;AAC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAGV,MAAM,EAKP,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAC3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAMhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAM1G,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sFAAsF;IACtF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,mEAAmE;IACnE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,+GAA+G;AAC/G,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,wDAAwD;IACxD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAiBD,2EAA2E;AAC3E,qBAAa,yBAA0B,SAAQ,KAAK;;CAInD;AAED,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAqGD,qBAAa,iBAAkB,YAAW,kBAAkB;;IAC1D,wDAAwD;IACxD,QAAQ,CAAC,EAAE,YAAY;IAMvB,2GAA2G;IAC3G,UAAU,CAAC,EACT,OAAO,EACP,QAAQ,EACR,IAAI,GACL,EAAE;QACD,OAAO,EAAE,wBAAwB,CAAC;QAClC,QAAQ,EAAE,sBAAsB,CAAC;QACjC,IAAI,EAAE,SAAS,CAAC;KACjB,GAAG,IAAI;IAMR,IAAI,OAAO,IAAI,mBAAmB,CAKjC;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED;;;OAGG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAID,sEAAsE;IAChE,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAmBxE,4DAA4D;IACtD,gBAAgB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BzE;;;;OAIG;IACH,eAAe,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO;IAYzD;;;;;OAKG;IACG,mBAAmB,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAmD3E;;;;OAIG;IACG,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAapE;;;;OAIG;IACH,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAY9C,uFAAuF;IACjF,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuB9D,gEAAgE;IAChE,WAAW,IAAI,IAAI;IAKnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CA6DrB;IAmBF;;;OAGG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;IAO1C,IAAI,KAAK,IAAI,gBAAgB,CAE5B;gBAEW,MAAM,EAAE,uBAAuB;IAY3C;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAa7D,iEAAiE;IAC3D,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAInF;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAwCvE,wEAAwE;IAClE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAuInE,6EAA6E;IACvE,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAsBjE,0EAA0E;IACpE,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAiC3D,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO;IAMrG;;;;;;;;OAQG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,MAAM,CAAC,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,eAAe,CAAC;IAgG3B;;;;;OAKG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAC1B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAiEpC;;;;;OAKG;IACG,kBAAkB,CACtB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA6BvC,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrF,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKvC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/integration.ts"],"names":[],"mappings":"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"}
@@ -309,6 +309,7 @@ var LinearIntegration = class {
309
309
  updatedAt: issue.updatedAt,
310
310
  metadata: {
311
311
  identifier: issue.identifier,
312
+ ...issue.projectId ? { linearProjectId: issue.projectId } : {},
312
313
  stateType: issue.stateType,
313
314
  priority: issue.priority,
314
315
  team: issue.source
@@ -897,6 +898,7 @@ function linearIssueToIntakeIssue(issue) {
897
898
  return {
898
899
  id: issue.id,
899
900
  sourceId: issue.projectId ?? null,
901
+ projectId: issue.projectId ?? null,
900
902
  identifier: issue.identifier,
901
903
  title: issue.title,
902
904
  url: issue.url,
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":["#storage","#projects","#auth","#updateTokens","#inflightRefreshes","#connectionChecks","#orgIdByResourceId","#resolveIntakeDispatch","#listIntakeIssues","#getIntakeIssue","#createIntakeComment","#updateIntakeIssue","#clientId","#clientSecret","#rules","#requestTokens","#listTeamWorkflowStates","#fetchRemainingIssueComments"],"sources":["../../../src/integrations/linear/integration.ts"],"sourcesContent":["/**\n * `LinearIntegration` — the self-contained Linear integration.\n *\n * Implements the system-wide `FactoryIntegration` contract\n * (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env\n * vars ONCE, constructs an instance with explicit credentials, and passes it\n * to `MastraFactory`. Everything Linear-flavored the system does — the OAuth\n * connect/callback flow, workspace/project/issue reads for Intake, and the\n * agent's issue tools — flows through this instance. No other module reads\n * `LINEAR_*` env vars.\n *\n * The class owns:\n * - OAuth: the user-facing authorize URL, code exchange, and refresh-token\n * rotation against Linear's `/oauth/token` endpoint.\n * - GraphQL reads/writes: viewer workspace, projects, active issues for\n * Intake, full issue detail (description + discussion), issue comments.\n * - The HTTP surface (`routes()`) and per-request agent tools\n * (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with\n * `this` as the API client.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n ListIntakeIssuesInput,\n UpdateIntakeIssueInput,\n} from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';\nimport { IssueReconcileWorker } from '../issue-reconcile-worker.js';\nimport { buildLinearAgentTools } from './agent-tools.js';\nimport type { LinearEventRules, LinearRuleOverrides } from './default-rules.js';\nimport { resolveLinearRules } from './default-rules.js';\nimport { attachLinearIssueReconciler } from './issue-reconciler.js';\nimport { linearIssueReconciliationEnabled, linearIssueReconciliationInterval } from './reconciliation-config.js';\nimport { buildLinearRoutes } from './routes.js';\nimport { attachLinearRules } from './rules.js';\nimport type { LinearConnectionRow, LinearStorageHandle, UpsertLinearConnectionInput } from './storage.js';\n\nconst LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';\nconst LINEAR_TOKEN_URL = 'https://api.linear.app/oauth/token';\nconst LINEAR_AUTHORIZE_URL = 'https://linear.app/oauth/authorize';\n\n/** Credentials and optional event rules for the Linear OAuth application. */\nexport interface LinearIntegrationConfig {\n /** Per-event replacements; omitted events retain defaults, null disables. */\n rules?: LinearRuleOverrides;\n /** OAuth client id of the Linear application. */\n clientId: string;\n /** OAuth client secret of the Linear application. */\n clientSecret: string;\n}\n\n/**\n * Tokens minted by Linear's `/oauth/token` endpoint. Linear access tokens\n * expire (24h) and refresh tokens rotate: every refresh invalidates the old\n * pair, so callers must persist the whole set after each exchange.\n */\nexport interface LinearTokenSet {\n accessToken: string;\n /** Null when Linear issued no refresh token (legacy non-expiring apps). */\n refreshToken: string | null;\n /** Null when Linear reported no `expires_in`. */\n expiresAt: Date | null;\n /** Scopes granted to the token as reported by Linear; null when omitted. */\n scope: string | null;\n}\n\nexport interface LinearWorkspace {\n name: string;\n urlKey: string;\n}\n\nexport interface LinearIssue {\n id: string;\n projectId: string | null;\n /** Backend workspace identity when source ids span multiple workspaces. */\n workspaceId?: string;\n /** Raw Linear team id for exact team-source attribution. */\n teamId: string | null;\n /** Human key like `ENG-123`. */\n identifier: string;\n title: string;\n url: string;\n /** Workflow state name, e.g. `In Progress`. */\n state: string;\n /** Workflow state type, e.g. `backlog` / `unstarted` / `started` / `triage`. */\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n /** Display name of the Linear user who created the issue, when Linear returns one. */\n creator: string | null;\n team: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LinearIssuePage {\n issues: LinearIssue[];\n /** Opaque cursor for the next page, or `null` on the last page. */\n nextCursor: string | null;\n}\n\nexport interface LinearProjectTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n}\n\nexport interface LinearProject {\n id: string;\n name: string;\n /** Project state, e.g. `planned` / `started` / `paused` / `completed`. */\n state: string;\n /** Teams the project belongs to (the Settings picker groups by these). */\n teams: LinearProjectTeam[];\n}\n\n/** A Linear team, selectable as an intake source in its own right. */\nexport interface LinearTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n /** Opaque intake source identifier. Optional on the wire for compatibility. */\n sourceId?: string;\n}\n\nexport interface LinearIssueComment {\n author: string | null;\n body: string;\n createdAt: string;\n}\n\n/** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */\nexport interface LinearIssueDetail extends LinearIssue {\n /** Markdown body of the issue, or `null` when empty. */\n description: string | null;\n /** Discussion comments, oldest first. */\n comments: LinearIssueComment[];\n}\n\n/** The comment created by {@link LinearIntegration.createIssueComment}. */\nexport interface LinearCreatedComment {\n id: string;\n url: string;\n}\n\nconst LINEAR_ISSUES_PAGE_SIZE = 30;\nconst ISSUE_COMMENTS_PAGE_SIZE = 50;\nconst LINEAR_TEAMS_PAGE_SIZE = 100;\nconst LINEAR_TEAMS_MAX_PAGES = 20;\n/** Hard stop for comment pagination so a misbehaving cursor can't loop forever. */\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Refresh this many ms before the recorded expiry to absorb clock skew. */\nconst TOKEN_REFRESH_SKEW_MS = 60_000;\n\n/** Re-check an org's Linear connection (and its scopes) at most this often. */\nconst CONNECTION_TTL_MS = 60_000;\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Thrown when the org's Linear authorization can no longer be renewed. */\nexport class LinearReauthRequiredError extends Error {\n constructor() {\n super('Linear authorization expired. Reconnect Linear to keep syncing intake issues.');\n }\n}\n\n/** Cached result of {@link LinearIntegration.checkConnection}. */\nexport interface LinearConnectionCheck {\n connected: boolean;\n /** Whether the granted OAuth scope allows posting issue comments. */\n canComment: boolean;\n checkedAt: number;\n}\n\ninterface IssuesQueryData {\n issues: {\n nodes: Array<{\n id: string;\n identifier: string;\n title: string;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string } | null;\n assignee: { name: string } | null;\n creator: { name: string } | null;\n team: { id: string; key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\ninterface IssueCommentNode {\n body: string;\n createdAt: string;\n user: { name: string } | null;\n}\n\ninterface IssueCommentsPage {\n nodes: IssueCommentNode[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n}\n\ninterface IssueDetailQueryData {\n issue: {\n id: string;\n identifier: string;\n title: string;\n description: string | null;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string } | null;\n assignee: { name: string } | null;\n creator: { name: string } | null;\n team: { id: string; key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n comments: IssueCommentsPage;\n } | null;\n}\n\ninterface IssueCommentsQueryData {\n issue: { comments: IssueCommentsPage } | null;\n}\n\ninterface IssueIdQueryData {\n issue: { id: string } | null;\n}\n\ninterface CommentCreateMutationData {\n commentCreate: { success: boolean; comment: { id: string; url: string } | null };\n}\n\n/** POST a GraphQL query to Linear with the given OAuth access token. */\nasync function linearGraphql<T>(accessToken: string, query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(LINEAR_GRAPHQL_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n // Linear returns GraphQL errors (validation, missing scopes, …) with a\n // 400 status — surface the actual message instead of just the code.\n let detail: string | null = null;\n try {\n const errBody = (await res.json()) as { errors?: Array<{ message?: string }> };\n detail = errBody.errors?.[0]?.message ?? null;\n } catch {\n // Non-JSON error body; fall back to the status code alone.\n }\n const err = new Error(`Linear API request failed (${res.status})${detail ? `: ${detail}` : ''}`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as { data?: T; errors?: Array<{ message?: string }> };\n if (body.errors?.length) {\n throw new Error(`Linear API error: ${body.errors[0]?.message ?? 'unknown error'}`);\n }\n if (!body.data) {\n throw new Error('Linear API returned no data.');\n }\n return body.data;\n}\n\nexport class LinearIntegration implements FactoryIntegration {\n /** Stable integration identifier (see `../base.ts`). */\n readonly id = 'linear';\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #storage: LinearStorageHandle | undefined;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n\n /** Bind Linear's slice of the generic integration storage, the projects domain, and the host auth seam. */\n initialize({\n storage,\n projects,\n auth,\n }: {\n storage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n }): void {\n this.#storage = storage as unknown as LinearStorageHandle;\n this.#projects = projects;\n this.#auth = auth;\n }\n\n get storage(): LinearStorageHandle {\n if (!this.#storage) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#storage;\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('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /**\n * Whether the host runs with web auth enabled. Linear connections are\n * org-owned, so every Linear surface is inert without a tenant auth seam.\n */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n // ── Connection + OAuth token lifecycle ───────────────────────────────────\n\n /** Load the org's Linear connection, or `null` when not connected. */\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const connection = await this.storage.connections.get(orgId);\n if (!connection) return null;\n const { data } = connection;\n return {\n id: connection.id,\n orgId: connection.orgId,\n userId: connection.userId,\n accessToken: data.accessToken,\n scope: data.scope ?? null,\n refreshToken: data.refreshToken ?? null,\n expiresAt: data.expiresAtMs === null || data.expiresAtMs === undefined ? null : new Date(data.expiresAtMs),\n workspaceName: data.workspaceName ?? null,\n workspaceUrlKey: data.workspaceUrlKey ?? null,\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n };\n }\n\n /** Insert or replace the org's connection (one per org). */\n async upsertConnection(input: UpsertLinearConnectionInput): Promise<void> {\n await this.storage.connections.upsert(input.orgId, {\n userId: input.userId,\n data: {\n accessToken: input.accessToken,\n refreshToken: input.refreshToken,\n expiresAtMs: input.expiresAt?.getTime() ?? null,\n scope: input.scope,\n workspaceName: input.workspaceName,\n workspaceUrlKey: input.workspaceUrlKey,\n },\n });\n // Let the agent tools see the new connection immediately.\n this.invalidateConnectionCache(input.orgId);\n }\n\n /** Persist a rotated token set on the org's existing connection row. */\n async #updateTokens(orgId: string, tokens: LinearTokenSet): Promise<void> {\n await this.storage.connections.update(orgId, data => ({\n ...data,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAtMs: tokens.expiresAt?.getTime() ?? null,\n // Refresh responses may omit scope; keep the recorded grant.\n scope: tokens.scope ?? data.scope,\n }));\n }\n\n /**\n * Whether the connection's token can post issue comments. Legacy rows\n * without a recorded scope were minted with `read` only, so they count as\n * read-only until the org reconnects Linear.\n */\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n /**\n * In-flight refreshes keyed by org. Linear rotates refresh tokens, so two\n * concurrent refreshes with the same token would invalidate each other —\n * single-flight ensures one exchange per org and shares the result.\n */\n readonly #inflightRefreshes = new Map<string, Promise<string>>();\n\n /**\n * Return a usable access token for the connection, proactively refreshing\n * it when the recorded expiry is past (or imminent). Throws\n * `LinearReauthRequiredError` when the token is expired and cannot be\n * refreshed — the org has to go through the OAuth flow again.\n */\n async getFreshAccessToken(connection: LinearConnectionRow): Promise<string> {\n const expired =\n connection.expiresAt !== null && connection.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!expired) return connection.accessToken;\n\n if (!connection.refreshToken) {\n // Legacy row from before refresh-token support: nothing to renew with.\n throw new LinearReauthRequiredError();\n }\n\n const existing = this.#inflightRefreshes.get(connection.orgId);\n if (existing) return existing;\n\n // The caller may hold a stale row: another request could have refreshed\n // and rotated the refresh token since this row was loaded. Reload before\n // refreshing so we don't burn the rotated token and force a false reauth.\n const latest = await this.loadConnection(connection.orgId);\n if (!latest) throw new LinearReauthRequiredError();\n\n const concurrent = this.#inflightRefreshes.get(connection.orgId);\n if (concurrent) return concurrent;\n\n const latestExpired = latest.expiresAt !== null && latest.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!latestExpired) return latest.accessToken;\n if (!latest.refreshToken) throw new LinearReauthRequiredError();\n\n const refreshToken = latest.refreshToken;\n const refresh = (async () => {\n try {\n const tokens = await this.refreshAccessToken(refreshToken);\n await this.#updateTokens(connection.orgId, tokens);\n return tokens.accessToken;\n } catch (err) {\n const status = (err as { status?: number }).status;\n // invalid_grant surfaces as 400/401: the refresh token was revoked or\n // already rotated away. Terminal for this connection.\n if (status === 400 || status === 401) throw new LinearReauthRequiredError();\n throw err;\n } finally {\n this.#inflightRefreshes.delete(connection.orgId);\n }\n })();\n this.#inflightRefreshes.set(connection.orgId, refresh);\n return refresh;\n }\n\n // ── Connection checks for agent tools ────────────────────────────────────\n\n /** Re-check an org's Linear connection (and its scopes) at most this often. */\n readonly #connectionChecks = new Map<string, LinearConnectionCheck>();\n\n /**\n * Whether the org has an active connection and whether its granted scope\n * allows posting comments. Cached per org with a short TTL — tool-set\n * resolution runs on every request.\n */\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const cached = this.#connectionChecks.get(orgId);\n if (cached && Date.now() - cached.checkedAt < CONNECTION_TTL_MS) return cached;\n const connection = await this.loadConnection(orgId);\n const check: LinearConnectionCheck = {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n this.#connectionChecks.set(orgId, check);\n return check;\n }\n\n /**\n * Drop the cached connection check for an org. Called after a connection is\n * persisted so the tools show up on the very next run instead of after the\n * TTL lapses.\n */\n invalidateConnectionCache(orgId: string): void {\n this.#connectionChecks.delete(orgId);\n }\n\n /**\n * A project's org never changes, so the resourceId → orgId mapping is\n * cached forever. `null` marks resource ids that aren't factory projects\n * (e.g. local default resources) so we don't re-query them on every\n * tool-set resolution.\n */\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n /** Map a session's resourceId to its owning org, or `null` when it isn't a project. */\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n // Non-UUID resource ids (local/dev resources) would make the uuid column\n // comparison throw — they're definitively \"not a project\", so cache that.\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org/connection caches between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#connectionChecks.clear();\n }\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async ({ orgId }) => {\n const connection = await this.loadConnection(orgId);\n if (!connection) return [];\n const accessToken = await this.getFreshAccessToken(connection);\n const [projects, teams] = await Promise.all([this.listProjects(accessToken), this.listTeams(accessToken)]);\n return [\n ...projects.map(project => ({\n id: project.id,\n name: project.name,\n type: 'project' as const,\n })),\n ...teams.map(team => ({\n id: encodeSelfManagedTeamSourceId(team.id),\n name: team.name,\n type: 'team' as const,\n metadata: { teamKey: team.key },\n })),\n ];\n },\n listItems: async ({ orgId, sourceIds, attributionSourceIds, cursor }) => {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const connection = await this.loadConnection(orgId);\n if (!connection) return { items: [], nextCursor: null };\n const accessToken = await this.getFreshAccessToken(connection);\n const page = await this.#listIntakeIssues({\n connection: { type: 'oauth', accessToken },\n sourceIds,\n attributionSourceIds,\n cursor,\n });\n return {\n items: page.issues.flatMap(issue => {\n if (!issue.sourceId) return [];\n return [\n {\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: issue.sourceId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state ?? '',\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n stateType: issue.stateType,\n priority: issue.priority,\n team: issue.source,\n },\n },\n ];\n }),\n nextCursor: page.nextCursor,\n };\n },\n listIssues: input => this.#listIntakeIssues(input),\n getIssue: input => this.#getIntakeIssue(input),\n createComment: input => this.#createIntakeComment(input),\n updateIssue: input => this.#updateIntakeIssue(input),\n };\n\n /**\n * Background-dispatch context: the org's OAuth connection with a fresh\n * token. Linear work items store the issue UUID directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n const accessToken = await this.getFreshAccessToken(connection);\n return { connection: { type: 'oauth', accessToken }, issueId: externalSource.externalId };\n }\n /**\n * The OAuth connect/callback flow round-trips a signed `state` through\n * Linear, so a multi-replica deploy needs a deployment-stable state secret.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #clientId: string;\n readonly #clientSecret: string;\n\n readonly #rules: LinearEventRules;\n\n get rules(): LinearEventRules {\n return this.#rules;\n }\n\n constructor(config: LinearIntegrationConfig) {\n this.#rules = resolveLinearRules(config.rules);\n const missing = (['clientId', 'clientSecret'] as const).filter(key => !config[key]);\n if (missing.length > 0) {\n throw new Error(`LinearIntegration is missing required config: ${missing.join(', ')}.`);\n }\n this.#clientId = config.clientId;\n this.#clientSecret = config.clientSecret;\n }\n\n // ── OAuth ────────────────────────────────────────────────────────────────\n\n /**\n * Build the OAuth authorize URL. `prompt=consent` forces the workspace\n * picker even for an already-authorized user, so \"reconnect\" can switch\n * workspaces.\n */\n buildAuthorizeUrl(state: string, redirectUri: string): string {\n const url = new URL(LINEAR_AUTHORIZE_URL);\n url.searchParams.set('client_id', this.#clientId);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('response_type', 'code');\n // `comments:create` lets the agent's linear_create_comment tool post\n // comments; everything else the integration does is read-only.\n url.searchParams.set('scope', 'read,comments:create');\n url.searchParams.set('state', state);\n url.searchParams.set('prompt', 'consent');\n return url.toString();\n }\n\n /** Exchange an OAuth `code` for a workspace-scoped token set. */\n async exchangeOAuthCode(code: string, redirectUri: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'authorization_code', code, redirect_uri: redirectUri }, 'token exchange');\n }\n\n /**\n * Exchange a refresh token for a new token set. Linear rotates refresh\n * tokens, so the returned set replaces the stored one entirely. A 400/401\n * here means the refresh token is invalid/revoked and the org must\n * re-authorize.\n */\n async refreshAccessToken(refreshToken: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'refresh_token', refresh_token: refreshToken }, 'token refresh');\n }\n\n /** POST to Linear's token endpoint and normalize the response. */\n async #requestTokens(params: Record<string, string>, label: string): Promise<LinearTokenSet> {\n const res = await fetch(LINEAR_TOKEN_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(10_000),\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n ...params,\n client_id: this.#clientId,\n client_secret: this.#clientSecret,\n }),\n });\n if (!res.ok) {\n const err = new Error(`Linear ${label} failed (${res.status})`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n if (!body.access_token) {\n throw new Error(`Linear ${label} returned no access token.`);\n }\n return {\n accessToken: body.access_token,\n refreshToken: body.refresh_token ?? null,\n expiresAt: typeof body.expires_in === 'number' ? new Date(Date.now() + body.expires_in * 1000) : null,\n scope: body.scope ?? null,\n };\n }\n\n // ── GraphQL reads/writes ─────────────────────────────────────────────────\n\n /** Fetch the workspace (organization) the access token is scoped to. */\n async fetchWorkspace(accessToken: string): Promise<LinearWorkspace> {\n const data = await linearGraphql<{ organization: { name: string; urlKey: string } }>(\n accessToken,\n `query { organization { name urlKey } }`,\n );\n return { name: data.organization.name, urlKey: data.organization.urlKey };\n }\n\n async #listIntakeIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const accessToken = getLinearAccessToken(input.connection);\n if (input.sourceIds.length === 0) return { issues: [], nextCursor: null };\n const attributionSourceIds = input.attributionSourceIds ?? input.sourceIds;\n const { projectIds, teamIds } = splitSelfManagedSourceIds(input.sourceIds);\n const cursors = decodeSelfManagedListCursor(input.cursor, input.sourceIds, attributionSourceIds);\n\n // Project-only intake still uses one Linear query, but its provider cursor\n // is wrapped so a later source-selection change cannot reinterpret it.\n if (teamIds.length === 0) {\n if (cursors.projects === null) return { issues: [], nextCursor: null };\n const result = await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels);\n return {\n issues: result.issues.map(issue => linearIssueToIntakeIssue(issue)),\n nextCursor: encodeSelfManagedListCursor(\n { projects: result.nextCursor, teams: null },\n input.sourceIds,\n attributionSourceIds,\n ),\n };\n }\n\n // A team is selected. List projects and teams separately (the two Linear\n // filters would AND together in one query) and stamp each issue with the\n // selected source that surfaced it. Each source kind paginates on its own\n // cursor, carried in a compound cursor when both are present.\n const projectResult =\n projectIds.length > 0 && cursors.projects !== null\n ? await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels)\n : { issues: [], nextCursor: null };\n const teamResult =\n cursors.teams !== null\n ? await this.listActiveIssues(accessToken, cursors.teams, undefined, input.labels, teamIds)\n : { issues: [], nextCursor: null };\n\n // Stamp project issues with their (selected) project id; stamp team issues\n // with the team source id. A team issue whose project was also selected is\n // reconciled by the dedupe step below (project wins).\n const projectIssues = projectResult.issues.map(issue => linearIssueToIntakeIssue(issue));\n const selectedTeamSourceById = new Map(teamIds.map(teamId => [teamId, encodeSelfManagedTeamSourceId(teamId)]));\n const selectedProjectIds = new Set(splitSelfManagedSourceIds(attributionSourceIds).projectIds);\n const teamIssues = teamResult.issues\n .filter(issue => issue.projectId === null || !selectedProjectIds.has(issue.projectId))\n .map(issue => {\n const teamSourceId = issue.teamId ? selectedTeamSourceById.get(issue.teamId) : undefined;\n if (!teamSourceId) throw new Error('Linear returned an issue outside the selected teams.');\n return { ...linearIssueToIntakeIssue(issue), sourceId: teamSourceId };\n });\n\n const deduped = dedupeSelfManagedIssues([...projectIssues, ...teamIssues]);\n return {\n issues: deduped,\n nextCursor: encodeSelfManagedListCursor(\n { projects: projectResult.nextCursor, teams: teamResult.nextCursor },\n input.sourceIds,\n attributionSourceIds,\n ),\n };\n }\n\n async #getIntakeIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n return {\n ...linearIssueToIntakeIssue(issue),\n description: issue.description,\n commentCount: issue.comments.length,\n comments: issue.comments,\n };\n }\n\n async #createIntakeComment(input: CreateIntakeCommentInput): Promise<{ id: string; url: string } | null> {\n const accessToken = getLinearAccessToken(input.connection);\n return this.createIssueComment(accessToken, input.issueId, input.body);\n }\n\n async #updateIntakeIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n const teamKey = issue.team;\n if (!teamKey) return null;\n const states = await this.#listTeamWorkflowStates(accessToken, teamKey);\n let targetState: { id: string; name: string; type: string } | null = null;\n if (input.state.kind === 'byType') {\n const wantedType = input.state.stateType;\n targetState = states.find(state => state.type === wantedType) ?? null;\n } else {\n const wanted = input.state.name.toLowerCase();\n targetState = states.find(state => state.name.toLowerCase() === wanted) ?? null;\n }\n if (!targetState) return null;\n if (targetState.name === issue.state) {\n return linearIssueToIntakeIssue(issue);\n }\n const data = await linearGraphql<{ issueUpdate: { success: boolean } }>(\n accessToken,\n `mutation UpdateIssueState($id: String!, $stateId: String!) {\n issueUpdate(id: $id, input: { stateId: $stateId }) { success }\n }`,\n { id: issue.id, stateId: targetState.id },\n );\n if (!data.issueUpdate.success) {\n throw new Error('Linear did not accept the issue update.');\n }\n const fresh = await this.fetchIssueDetail(accessToken, issue.id);\n if (!fresh) return null;\n return linearIssueToIntakeIssue(fresh);\n }\n\n async #listTeamWorkflowStates(\n accessToken: string,\n teamKey: string,\n ): Promise<Array<{ id: string; name: string; type: string }>> {\n const data = await linearGraphql<{\n team: { states: { nodes: Array<{ id: string; name: string; type: string }> } } | null;\n }>(\n accessToken,\n `query TeamStates($key: String!) {\n team(id: $key) { states(first: 100) { nodes { id name type } } }\n }`,\n { key: teamKey },\n );\n return data.team?.states.nodes ?? [];\n }\n\n /** List the workspace's projects (for the Settings intake-source picker). */\n async listProjects(accessToken: string): Promise<LinearProject[]> {\n const data = await linearGraphql<{\n projects: {\n nodes: Array<{\n id: string;\n name: string;\n state: string;\n teams: { nodes: Array<{ id: string; key: string; name: string }> };\n }>;\n };\n }>(\n accessToken,\n `query { projects(first: 100) { nodes { id name state teams(first: 10) { nodes { id key name } } } } }`,\n );\n return data.projects.nodes.map(node => ({\n id: node.id,\n name: node.name,\n state: node.state,\n teams: node.teams.nodes.map(team => ({ id: team.id, key: team.key, name: team.name })),\n }));\n }\n\n /** List the workspace's teams (for the Settings intake-source picker). */\n async listTeams(accessToken: string): Promise<LinearTeam[]> {\n const teams: Array<{ id: string; key: string; name: string }> = [];\n let after: string | undefined;\n for (let page = 0; page < LINEAR_TEAMS_MAX_PAGES; page += 1) {\n const data = await linearGraphql<{\n teams: {\n nodes: Array<{ id: string; key: string; name: string }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n }>(\n accessToken,\n `query Teams($first: Int!, $after: String) {\n teams(first: $first, after: $after) {\n nodes { id key name }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n { first: LINEAR_TEAMS_PAGE_SIZE, ...(after ? { after } : {}) },\n );\n teams.push(...data.teams.nodes);\n if (!data.teams.pageInfo.hasNextPage || !data.teams.pageInfo.endCursor) break;\n // A page that hands back the cursor it was asked for would replay forever.\n if (data.teams.pageInfo.endCursor === after) throw invalidLinearCursor();\n after = data.teams.pageInfo.endCursor;\n }\n return teams.map(team => ({\n id: team.id,\n key: team.key,\n name: team.name,\n sourceId: encodeSelfManagedTeamSourceId(team.id),\n }));\n }\n\n sourceMatchesIssue(sourceId: string, issue: Pick<LinearIssueDetail, 'projectId' | 'teamId'>): boolean {\n return isSelfManagedTeamSourceId(sourceId)\n ? issue.teamId === decodeSelfManagedTeamSourceId(sourceId)\n : issue.projectId === sourceId;\n }\n\n /**\n * List one page of the workspace's active issues (triage/backlog/unstarted/\n * started — completed and canceled are excluded), most recently updated\n * first. When `projectIds` is provided, only issues from those projects are\n * returned. When `teamIds` is provided, only issues in those teams are\n * returned, including issues that belong to no project. Callers list team\n * sources and project sources in separate calls so the two filters are not\n * AND-ed together.\n */\n async listActiveIssues(\n accessToken: string,\n after?: string,\n projectIds?: string[],\n labels?: string[],\n teamIds?: string[],\n ): Promise<LinearIssuePage> {\n const normalizedLabels = [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n const projectFilter = projectIds?.length ? ', project: { id: { in: $projectIds } }' : '';\n const projectVar = projectIds?.length ? ', $projectIds: [ID!]' : '';\n const teamFilter = teamIds?.length ? ', team: { id: { in: $teamIds } }' : '';\n const teamVar = teamIds?.length ? ', $teamIds: [ID!]' : '';\n const labelFilter = normalizedLabels.length > 0 ? ', labels: { name: { in: $labels } }' : '';\n const labelVar = normalizedLabels.length > 0 ? ', $labels: [String!]' : '';\n const data = await linearGraphql<IssuesQueryData>(\n accessToken,\n `query Intake($first: Int!, $after: String${projectVar}${teamVar}${labelVar}) {\n issues(\n first: $first\n after: $after\n orderBy: updatedAt\n filter: { state: { type: { in: [\"triage\", \"backlog\", \"unstarted\", \"started\"] } }${projectFilter}${teamFilter}${labelFilter} }\n ) {\n nodes {\n id\n identifier\n title\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n creator { name }\n team { id key }\n labels { nodes { name } }\n }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n {\n first: LINEAR_ISSUES_PAGE_SIZE,\n after: after ?? null,\n ...(projectIds?.length ? { projectIds } : {}),\n ...(teamIds?.length ? { teamIds } : {}),\n ...(normalizedLabels.length > 0 ? { labels: normalizedLabels } : {}),\n },\n );\n const { nodes, pageInfo } = data.issues;\n return {\n issues: nodes.map(node => ({\n id: node.id,\n projectId: node.project?.id ?? null,\n identifier: node.identifier,\n title: node.title,\n url: node.url,\n state: node.state.name,\n stateType: node.state.type,\n priorityLabel: node.priorityLabel,\n assignee: node.assignee?.name ?? null,\n creator: node.creator?.name ?? null,\n team: node.team?.key ?? null,\n labels: node.labels.nodes.map(label => label.name),\n createdAt: node.createdAt,\n updatedAt: node.updatedAt,\n teamId: node.team?.id ?? null,\n })),\n nextCursor: pageInfo.hasNextPage ? pageInfo.endCursor : null,\n };\n }\n\n /** Follow `comments.pageInfo` until exhausted so long discussions aren't truncated. */\n async #fetchRemainingIssueComments(\n accessToken: string,\n issueId: string,\n firstPage: IssueCommentsPage,\n ): Promise<IssueCommentNode[]> {\n const nodes = [...firstPage.nodes];\n let { hasNextPage, endCursor } = firstPage.pageInfo;\n for (let page = 1; hasNextPage && endCursor && page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const data = await linearGraphql<IssueCommentsQueryData>(\n accessToken,\n `query IssueComments($id: String!, $first: Int!, $after: String!) {\n issue(id: $id) {\n comments(first: $first, after: $after) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: issueId, first: ISSUE_COMMENTS_PAGE_SIZE, after: endCursor },\n );\n const comments = data.issue?.comments;\n if (!comments) break;\n nodes.push(...comments.nodes);\n if (comments.pageInfo.hasNextPage && comments.pageInfo.endCursor === endCursor) throw invalidLinearCursor();\n ({ hasNextPage, endCursor } = comments.pageInfo);\n }\n return nodes;\n }\n\n /**\n * Fetch one issue with its description and comments. `idOrIdentifier`\n * accepts both the Linear UUID and the human key (`ENG-123`). Returns\n * `null` when the issue doesn't exist (Linear reports it as an \"Entity not\n * found\" error).\n */\n async fetchIssueDetail(\n accessToken: string,\n idOrIdentifier: string,\n _sourceIds?: string[],\n _routedSourceIds?: string[],\n ): Promise<LinearIssueDetail | null> {\n let data: IssueDetailQueryData;\n try {\n data = await linearGraphql<IssueDetailQueryData>(\n accessToken,\n `query IssueDetail($id: String!, $commentsFirst: Int!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n creator { name }\n team { id key }\n labels { nodes { name } }\n comments(first: $commentsFirst) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: idOrIdentifier, commentsFirst: ISSUE_COMMENTS_PAGE_SIZE },\n );\n } catch (err) {\n // Linear surfaces unknown ids/identifiers as a GraphQL \"Entity not\n // found\" error rather than a null node — map that to \"issue doesn't\n // exist\".\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const issue = data.issue;\n if (!issue) return null;\n const allComments = await this.#fetchRemainingIssueComments(accessToken, issue.id, issue.comments);\n const comments = allComments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return {\n id: issue.id,\n projectId: issue.project?.id ?? null,\n teamId: issue.team?.id ?? null,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description?.trim() ? issue.description : null,\n url: issue.url,\n state: issue.state.name,\n stateType: issue.state.type,\n priorityLabel: issue.priorityLabel,\n assignee: issue.assignee?.name ?? null,\n creator: issue.creator?.name ?? null,\n team: issue.team?.key ?? null,\n labels: issue.labels.nodes.map(label => label.name),\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n comments: comments.map(comment => ({\n author: comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n }\n\n /**\n * Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID\n * and the human key (`ENG-123`) — the identifier is resolved to a UUID\n * first because `commentCreate` only accepts UUIDs. Returns `null` when the\n * issue doesn't exist.\n */\n async createIssueComment(\n accessToken: string,\n idOrIdentifier: string,\n body: string,\n ): Promise<LinearCreatedComment | null> {\n let issueId: string;\n try {\n const data = await linearGraphql<IssueIdQueryData>(\n accessToken,\n `query IssueId($id: String!) { issue(id: $id) { id } }`,\n { id: idOrIdentifier },\n );\n if (!data.issue) return null;\n issueId = data.issue.id;\n } catch (err) {\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const data = await linearGraphql<CommentCreateMutationData>(\n accessToken,\n `mutation CommentCreate($input: CommentCreateInput!) {\n commentCreate(input: $input) { success comment { id url } }\n }`,\n { input: { issueId, body } },\n );\n if (!data.commentCreate.success || !data.commentCreate.comment) {\n throw new Error('Linear did not accept the comment.');\n }\n return data.commentCreate.comment;\n }\n\n // ── FactoryIntegration surface ───────────────────────────────────────────\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!linearIssueReconciliationEnabled()) return [];\n const reconcile = attachLinearIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = linearIssueReconciliationInterval();\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/linear/*` + `/auth/linear/*` Mastra\n * `apiRoutes` (status, OAuth connect/callback, projects + issues for\n * Intake). Handlers operate on this instance.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildLinearRoutes({\n linear: this,\n auth: ctx.auth,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(this, ctx),\n workItems: ctx.runtime?.workItems,\n boards: ctx.runtime?.boards,\n });\n }\n\n /**\n * Org-scoped agent tools: issue detail + comment tools for sessions whose\n * project belongs to an org with an active Linear connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext: args.requestContext, linear: this });\n }\n\n /** Non-secret config snapshot for system diagnostics/startup logs. */\n diagnostics(): Record<string, unknown> {\n return {\n oauthAppConfigured: true,\n };\n }\n}\n\nfunction getLinearAccessToken(connection: IntegrationConnection): string {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n return connection.accessToken;\n}\n\n/**\n * Self-managed source ids. Projects use their raw Linear project id (no prefix,\n * preserving existing intake bindings). Teams are prefixed so the two never\n * collide and callers can tell them apart.\n */\nconst SELF_MANAGED_TEAM_SOURCE_PREFIX = 'linear-team:';\n\nfunction encodeSelfManagedTeamSourceId(teamId: string): string {\n return `${SELF_MANAGED_TEAM_SOURCE_PREFIX}${teamId}`;\n}\n\nfunction isSelfManagedTeamSourceId(sourceId: string): boolean {\n return sourceId.startsWith(SELF_MANAGED_TEAM_SOURCE_PREFIX);\n}\n\nfunction decodeSelfManagedTeamSourceId(sourceId: string): string {\n return sourceId.slice(SELF_MANAGED_TEAM_SOURCE_PREFIX.length);\n}\n\n/**\n * Split selected source ids into raw project ids and raw team ids. A team\n * source id carries the `linear-team:` prefix; everything else is a project id.\n */\nfunction splitSelfManagedSourceIds(sourceIds: string[]): { projectIds: string[]; teamIds: string[] } {\n const projectIds: string[] = [];\n const teamIds: string[] = [];\n for (const sourceId of sourceIds) {\n if (isSelfManagedTeamSourceId(sourceId)) teamIds.push(decodeSelfManagedTeamSourceId(sourceId));\n else projectIds.push(sourceId);\n }\n return { projectIds, teamIds };\n}\n\ntype SelfManagedListCursor = {\n v: 1;\n sourceSet: string;\n projects: string | null;\n teams: string | null;\n};\n\nfunction linearSourceSetFingerprint(sourceIds: string[], attributionSourceIds: string[]): string {\n const scope = {\n sourceIds: [...new Set(sourceIds)].sort(),\n attributionSourceIds: [...new Set(attributionSourceIds)].sort(),\n };\n return createHash('sha256').update(JSON.stringify(scope)).digest('base64url');\n}\n\nfunction invalidLinearCursor(): Error {\n return Object.assign(new Error('Linear cursor is invalid or stale.'), { code: 'invalid_cursor' as const });\n}\n\nfunction isCursorValue(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\nfunction decodeSelfManagedListCursor(\n cursor: string | undefined,\n sourceIds: string[],\n attributionSourceIds: string[],\n): { projects: string | null | undefined; teams: string | null | undefined } {\n const { projectIds, teamIds } = splitSelfManagedSourceIds(sourceIds);\n if (!cursor) {\n return {\n projects: projectIds.length > 0 ? undefined : null,\n teams: teamIds.length > 0 ? undefined : null,\n };\n }\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Partial<SelfManagedListCursor>;\n if (\n parsed.v !== 1 ||\n parsed.sourceSet !== linearSourceSetFingerprint(sourceIds, attributionSourceIds) ||\n !isCursorValue(parsed.projects) ||\n !isCursorValue(parsed.teams)\n ) {\n throw invalidLinearCursor();\n }\n return {\n projects: parsed.projects,\n teams: parsed.teams,\n };\n } catch {\n throw invalidLinearCursor();\n }\n}\n\nfunction encodeSelfManagedListCursor(\n state: Pick<SelfManagedListCursor, 'projects' | 'teams'>,\n sourceIds: string[],\n attributionSourceIds: string[],\n): string | null {\n if (state.projects === null && state.teams === null) return null;\n return Buffer.from(\n JSON.stringify({\n v: 1,\n sourceSet: linearSourceSetFingerprint(sourceIds, attributionSourceIds),\n ...state,\n } satisfies SelfManagedListCursor),\n ).toString('base64url');\n}\n\n/**\n * Deduplicate issues that surfaced from both a selected team and one of its\n * selected projects. Keeps one entry per Linear issue UUID.\n *\n * Tie-breaker — MOST-SPECIFIC-WINS: an issue in a selected project is\n * attributed to that project source (routes to the project's board); the team\n * source covers only the remainder (projectless issues and issues in projects\n * that were not separately selected). Change the comparison here to change the\n * routing policy — this is the single precedence point for the self-managed\n * integration, mirroring the platform-backed one.\n */\nfunction dedupeSelfManagedIssues(\n issues: Array<IntakeIssue & { sourceId?: string | null }>,\n): Array<IntakeIssue & { sourceId?: string | null }> {\n const isProjectSource = (sourceId: string | null | undefined): boolean =>\n typeof sourceId === 'string' && sourceId.length > 0 && !isSelfManagedTeamSourceId(sourceId);\n const winners = new Map<string, IntakeIssue & { sourceId?: string | null }>();\n for (const issue of issues) {\n const existing = winners.get(issue.id);\n if (!existing) {\n winners.set(issue.id, issue);\n continue;\n }\n // Project source beats team source; otherwise keep the first seen.\n if (isProjectSource(issue.sourceId) && !isProjectSource(existing.sourceId)) {\n winners.set(issue.id, issue);\n }\n }\n const seen = new Set<string>();\n const ordered: Array<IntakeIssue & { sourceId?: string | null }> = [];\n for (const issue of issues) {\n if (seen.has(issue.id)) continue;\n seen.add(issue.id);\n ordered.push(winners.get(issue.id)!);\n }\n return ordered;\n}\n\nfunction linearIssueToIntakeIssue(issue: Omit<LinearIssue, 'projectId'> & { projectId?: string | null }): IntakeIssue {\n return {\n id: issue.id,\n sourceId: issue.projectId ?? null,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n assignee: issue.assignee,\n source: issue.team,\n labels: issue.labels,\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integration.js","names":["#storage","#projects","#auth","#updateTokens","#inflightRefreshes","#connectionChecks","#orgIdByResourceId","#resolveIntakeDispatch","#listIntakeIssues","#getIntakeIssue","#createIntakeComment","#updateIntakeIssue","#clientId","#clientSecret","#rules","#requestTokens","#listTeamWorkflowStates","#fetchRemainingIssueComments"],"sources":["../../../src/integrations/linear/integration.ts"],"sourcesContent":["/**\n * `LinearIntegration` — the self-contained Linear integration.\n *\n * Implements the system-wide `FactoryIntegration` contract\n * (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env\n * vars ONCE, constructs an instance with explicit credentials, and passes it\n * to `MastraFactory`. Everything Linear-flavored the system does — the OAuth\n * connect/callback flow, workspace/project/issue reads for Intake, and the\n * agent's issue tools — flows through this instance. No other module reads\n * `LINEAR_*` env vars.\n *\n * The class owns:\n * - OAuth: the user-facing authorize URL, code exchange, and refresh-token\n * rotation against Linear's `/oauth/token` endpoint.\n * - GraphQL reads/writes: viewer workspace, projects, active issues for\n * Intake, full issue detail (description + discussion), issue comments.\n * - The HTTP surface (`routes()`) and per-request agent tools\n * (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with\n * `this` as the API client.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n ListIntakeIssuesInput,\n UpdateIntakeIssueInput,\n} from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';\nimport { IssueReconcileWorker } from '../issue-reconcile-worker.js';\nimport { buildLinearAgentTools } from './agent-tools.js';\nimport type { LinearEventRules, LinearRuleOverrides } from './default-rules.js';\nimport { resolveLinearRules } from './default-rules.js';\nimport { attachLinearIssueReconciler } from './issue-reconciler.js';\nimport { linearIssueReconciliationEnabled, linearIssueReconciliationInterval } from './reconciliation-config.js';\nimport { buildLinearRoutes } from './routes.js';\nimport { attachLinearRules } from './rules.js';\nimport type { LinearConnectionRow, LinearStorageHandle, UpsertLinearConnectionInput } from './storage.js';\n\nconst LINEAR_GRAPHQL_URL = 'https://api.linear.app/graphql';\nconst LINEAR_TOKEN_URL = 'https://api.linear.app/oauth/token';\nconst LINEAR_AUTHORIZE_URL = 'https://linear.app/oauth/authorize';\n\n/** Credentials and optional event rules for the Linear OAuth application. */\nexport interface LinearIntegrationConfig {\n /** Per-event replacements; omitted events retain defaults, null disables. */\n rules?: LinearRuleOverrides;\n /** OAuth client id of the Linear application. */\n clientId: string;\n /** OAuth client secret of the Linear application. */\n clientSecret: string;\n}\n\n/**\n * Tokens minted by Linear's `/oauth/token` endpoint. Linear access tokens\n * expire (24h) and refresh tokens rotate: every refresh invalidates the old\n * pair, so callers must persist the whole set after each exchange.\n */\nexport interface LinearTokenSet {\n accessToken: string;\n /** Null when Linear issued no refresh token (legacy non-expiring apps). */\n refreshToken: string | null;\n /** Null when Linear reported no `expires_in`. */\n expiresAt: Date | null;\n /** Scopes granted to the token as reported by Linear; null when omitted. */\n scope: string | null;\n}\n\nexport interface LinearWorkspace {\n name: string;\n urlKey: string;\n}\n\nexport interface LinearIssue {\n id: string;\n projectId: string | null;\n /** Backend workspace identity when source ids span multiple workspaces. */\n workspaceId?: string;\n /** Raw Linear team id for exact team-source attribution. */\n teamId: string | null;\n /** Human key like `ENG-123`. */\n identifier: string;\n title: string;\n url: string;\n /** Workflow state name, e.g. `In Progress`. */\n state: string;\n /** Workflow state type, e.g. `backlog` / `unstarted` / `started` / `triage`. */\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n /** Display name of the Linear user who created the issue, when Linear returns one. */\n creator: string | null;\n team: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LinearIssuePage {\n issues: LinearIssue[];\n /** Opaque cursor for the next page, or `null` on the last page. */\n nextCursor: string | null;\n}\n\nexport interface LinearProjectTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n}\n\nexport interface LinearProject {\n id: string;\n name: string;\n /** Project state, e.g. `planned` / `started` / `paused` / `completed`. */\n state: string;\n /** Teams the project belongs to (the Settings picker groups by these). */\n teams: LinearProjectTeam[];\n}\n\n/** A Linear team, selectable as an intake source in its own right. */\nexport interface LinearTeam {\n id: string;\n /** Short team key, e.g. `ENG`. */\n key: string;\n name: string;\n /** Opaque intake source identifier. Optional on the wire for compatibility. */\n sourceId?: string;\n}\n\nexport interface LinearIssueComment {\n author: string | null;\n body: string;\n createdAt: string;\n}\n\n/** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */\nexport interface LinearIssueDetail extends LinearIssue {\n /** Markdown body of the issue, or `null` when empty. */\n description: string | null;\n /** Discussion comments, oldest first. */\n comments: LinearIssueComment[];\n}\n\n/** The comment created by {@link LinearIntegration.createIssueComment}. */\nexport interface LinearCreatedComment {\n id: string;\n url: string;\n}\n\nconst LINEAR_ISSUES_PAGE_SIZE = 30;\nconst ISSUE_COMMENTS_PAGE_SIZE = 50;\nconst LINEAR_TEAMS_PAGE_SIZE = 100;\nconst LINEAR_TEAMS_MAX_PAGES = 20;\n/** Hard stop for comment pagination so a misbehaving cursor can't loop forever. */\nconst ISSUE_COMMENTS_MAX_PAGES = 20;\n\n/** Refresh this many ms before the recorded expiry to absorb clock skew. */\nconst TOKEN_REFRESH_SKEW_MS = 60_000;\n\n/** Re-check an org's Linear connection (and its scopes) at most this often. */\nconst CONNECTION_TTL_MS = 60_000;\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Thrown when the org's Linear authorization can no longer be renewed. */\nexport class LinearReauthRequiredError extends Error {\n constructor() {\n super('Linear authorization expired. Reconnect Linear to keep syncing intake issues.');\n }\n}\n\n/** Cached result of {@link LinearIntegration.checkConnection}. */\nexport interface LinearConnectionCheck {\n connected: boolean;\n /** Whether the granted OAuth scope allows posting issue comments. */\n canComment: boolean;\n checkedAt: number;\n}\n\ninterface IssuesQueryData {\n issues: {\n nodes: Array<{\n id: string;\n identifier: string;\n title: string;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string } | null;\n assignee: { name: string } | null;\n creator: { name: string } | null;\n team: { id: string; key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\ninterface IssueCommentNode {\n body: string;\n createdAt: string;\n user: { name: string } | null;\n}\n\ninterface IssueCommentsPage {\n nodes: IssueCommentNode[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n}\n\ninterface IssueDetailQueryData {\n issue: {\n id: string;\n identifier: string;\n title: string;\n description: string | null;\n url: string;\n priorityLabel: string;\n createdAt: string;\n updatedAt: string;\n state: { name: string; type: string };\n project: { id: string } | null;\n assignee: { name: string } | null;\n creator: { name: string } | null;\n team: { id: string; key: string } | null;\n labels: { nodes: Array<{ name: string }> };\n comments: IssueCommentsPage;\n } | null;\n}\n\ninterface IssueCommentsQueryData {\n issue: { comments: IssueCommentsPage } | null;\n}\n\ninterface IssueIdQueryData {\n issue: { id: string } | null;\n}\n\ninterface CommentCreateMutationData {\n commentCreate: { success: boolean; comment: { id: string; url: string } | null };\n}\n\n/** POST a GraphQL query to Linear with the given OAuth access token. */\nasync function linearGraphql<T>(accessToken: string, query: string, variables?: Record<string, unknown>): Promise<T> {\n const res = await fetch(LINEAR_GRAPHQL_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(15_000),\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n // Linear returns GraphQL errors (validation, missing scopes, …) with a\n // 400 status — surface the actual message instead of just the code.\n let detail: string | null = null;\n try {\n const errBody = (await res.json()) as { errors?: Array<{ message?: string }> };\n detail = errBody.errors?.[0]?.message ?? null;\n } catch {\n // Non-JSON error body; fall back to the status code alone.\n }\n const err = new Error(`Linear API request failed (${res.status})${detail ? `: ${detail}` : ''}`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as { data?: T; errors?: Array<{ message?: string }> };\n if (body.errors?.length) {\n throw new Error(`Linear API error: ${body.errors[0]?.message ?? 'unknown error'}`);\n }\n if (!body.data) {\n throw new Error('Linear API returned no data.');\n }\n return body.data;\n}\n\nexport class LinearIntegration implements FactoryIntegration {\n /** Stable integration identifier (see `../base.ts`). */\n readonly id = 'linear';\n /** Bound once by the factory via `initialize()` before any surface is used. */\n #storage: LinearStorageHandle | undefined;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n\n /** Bind Linear's slice of the generic integration storage, the projects domain, and the host auth seam. */\n initialize({\n storage,\n projects,\n auth,\n }: {\n storage: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n }): void {\n this.#storage = storage as unknown as LinearStorageHandle;\n this.#projects = projects;\n this.#auth = auth;\n }\n\n get storage(): LinearStorageHandle {\n if (!this.#storage) {\n throw new Error('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#storage;\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('LinearIntegration is not initialized — the factory binds storage during prepare().');\n }\n return this.#projects;\n }\n\n /**\n * Whether the host runs with web auth enabled. Linear connections are\n * org-owned, so every Linear surface is inert without a tenant auth seam.\n */\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n // ── Connection + OAuth token lifecycle ───────────────────────────────────\n\n /** Load the org's Linear connection, or `null` when not connected. */\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const connection = await this.storage.connections.get(orgId);\n if (!connection) return null;\n const { data } = connection;\n return {\n id: connection.id,\n orgId: connection.orgId,\n userId: connection.userId,\n accessToken: data.accessToken,\n scope: data.scope ?? null,\n refreshToken: data.refreshToken ?? null,\n expiresAt: data.expiresAtMs === null || data.expiresAtMs === undefined ? null : new Date(data.expiresAtMs),\n workspaceName: data.workspaceName ?? null,\n workspaceUrlKey: data.workspaceUrlKey ?? null,\n createdAt: connection.createdAt,\n updatedAt: connection.updatedAt,\n };\n }\n\n /** Insert or replace the org's connection (one per org). */\n async upsertConnection(input: UpsertLinearConnectionInput): Promise<void> {\n await this.storage.connections.upsert(input.orgId, {\n userId: input.userId,\n data: {\n accessToken: input.accessToken,\n refreshToken: input.refreshToken,\n expiresAtMs: input.expiresAt?.getTime() ?? null,\n scope: input.scope,\n workspaceName: input.workspaceName,\n workspaceUrlKey: input.workspaceUrlKey,\n },\n });\n // Let the agent tools see the new connection immediately.\n this.invalidateConnectionCache(input.orgId);\n }\n\n /** Persist a rotated token set on the org's existing connection row. */\n async #updateTokens(orgId: string, tokens: LinearTokenSet): Promise<void> {\n await this.storage.connections.update(orgId, data => ({\n ...data,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAtMs: tokens.expiresAt?.getTime() ?? null,\n // Refresh responses may omit scope; keep the recorded grant.\n scope: tokens.scope ?? data.scope,\n }));\n }\n\n /**\n * Whether the connection's token can post issue comments. Legacy rows\n * without a recorded scope were minted with `read` only, so they count as\n * read-only until the org reconnects Linear.\n */\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n /**\n * In-flight refreshes keyed by org. Linear rotates refresh tokens, so two\n * concurrent refreshes with the same token would invalidate each other —\n * single-flight ensures one exchange per org and shares the result.\n */\n readonly #inflightRefreshes = new Map<string, Promise<string>>();\n\n /**\n * Return a usable access token for the connection, proactively refreshing\n * it when the recorded expiry is past (or imminent). Throws\n * `LinearReauthRequiredError` when the token is expired and cannot be\n * refreshed — the org has to go through the OAuth flow again.\n */\n async getFreshAccessToken(connection: LinearConnectionRow): Promise<string> {\n const expired =\n connection.expiresAt !== null && connection.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!expired) return connection.accessToken;\n\n if (!connection.refreshToken) {\n // Legacy row from before refresh-token support: nothing to renew with.\n throw new LinearReauthRequiredError();\n }\n\n const existing = this.#inflightRefreshes.get(connection.orgId);\n if (existing) return existing;\n\n // The caller may hold a stale row: another request could have refreshed\n // and rotated the refresh token since this row was loaded. Reload before\n // refreshing so we don't burn the rotated token and force a false reauth.\n const latest = await this.loadConnection(connection.orgId);\n if (!latest) throw new LinearReauthRequiredError();\n\n const concurrent = this.#inflightRefreshes.get(connection.orgId);\n if (concurrent) return concurrent;\n\n const latestExpired = latest.expiresAt !== null && latest.expiresAt.getTime() - TOKEN_REFRESH_SKEW_MS <= Date.now();\n if (!latestExpired) return latest.accessToken;\n if (!latest.refreshToken) throw new LinearReauthRequiredError();\n\n const refreshToken = latest.refreshToken;\n const refresh = (async () => {\n try {\n const tokens = await this.refreshAccessToken(refreshToken);\n await this.#updateTokens(connection.orgId, tokens);\n return tokens.accessToken;\n } catch (err) {\n const status = (err as { status?: number }).status;\n // invalid_grant surfaces as 400/401: the refresh token was revoked or\n // already rotated away. Terminal for this connection.\n if (status === 400 || status === 401) throw new LinearReauthRequiredError();\n throw err;\n } finally {\n this.#inflightRefreshes.delete(connection.orgId);\n }\n })();\n this.#inflightRefreshes.set(connection.orgId, refresh);\n return refresh;\n }\n\n // ── Connection checks for agent tools ────────────────────────────────────\n\n /** Re-check an org's Linear connection (and its scopes) at most this often. */\n readonly #connectionChecks = new Map<string, LinearConnectionCheck>();\n\n /**\n * Whether the org has an active connection and whether its granted scope\n * allows posting comments. Cached per org with a short TTL — tool-set\n * resolution runs on every request.\n */\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const cached = this.#connectionChecks.get(orgId);\n if (cached && Date.now() - cached.checkedAt < CONNECTION_TTL_MS) return cached;\n const connection = await this.loadConnection(orgId);\n const check: LinearConnectionCheck = {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n this.#connectionChecks.set(orgId, check);\n return check;\n }\n\n /**\n * Drop the cached connection check for an org. Called after a connection is\n * persisted so the tools show up on the very next run instead of after the\n * TTL lapses.\n */\n invalidateConnectionCache(orgId: string): void {\n this.#connectionChecks.delete(orgId);\n }\n\n /**\n * A project's org never changes, so the resourceId → orgId mapping is\n * cached forever. `null` marks resource ids that aren't factory projects\n * (e.g. local default resources) so we don't re-query them on every\n * tool-set resolution.\n */\n readonly #orgIdByResourceId = new Map<string, string | null>();\n\n /** Map a session's resourceId to its owning org, or `null` when it isn't a project. */\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n // Non-UUID resource ids (local/dev resources) would make the uuid column\n // comparison throw — they're definitively \"not a project\", so cache that.\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n let orgId: string | null;\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n orgId = project?.orgId ?? null;\n } catch {\n // Transient database failure: skip the tools for this request but don't\n // cache the miss, so the next request retries the lookup.\n return null;\n }\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n }\n\n /** Test hook: clear the org/connection caches between specs. */\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n this.#connectionChecks.clear();\n }\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async ({ orgId }) => {\n const connection = await this.loadConnection(orgId);\n if (!connection) return [];\n const accessToken = await this.getFreshAccessToken(connection);\n const [projects, teams] = await Promise.all([this.listProjects(accessToken), this.listTeams(accessToken)]);\n return [\n ...projects.map(project => ({\n id: project.id,\n name: project.name,\n type: 'project' as const,\n })),\n ...teams.map(team => ({\n id: encodeSelfManagedTeamSourceId(team.id),\n name: team.name,\n type: 'team' as const,\n metadata: { teamKey: team.key },\n })),\n ];\n },\n listItems: async ({ orgId, sourceIds, attributionSourceIds, cursor }) => {\n if (sourceIds.length === 0) return { items: [], nextCursor: null };\n const connection = await this.loadConnection(orgId);\n if (!connection) return { items: [], nextCursor: null };\n const accessToken = await this.getFreshAccessToken(connection);\n const page = await this.#listIntakeIssues({\n connection: { type: 'oauth', accessToken },\n sourceIds,\n attributionSourceIds,\n cursor,\n });\n return {\n items: page.issues.flatMap(issue => {\n if (!issue.sourceId) return [];\n return [\n {\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: issue.sourceId,\n title: `${issue.identifier}: ${issue.title}`,\n status: issue.state ?? '',\n labels: issue.labels,\n assignee: issue.assignee,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n ...(issue.projectId ? { linearProjectId: issue.projectId } : {}),\n stateType: issue.stateType,\n priority: issue.priority,\n team: issue.source,\n },\n },\n ];\n }),\n nextCursor: page.nextCursor,\n };\n },\n listIssues: input => this.#listIntakeIssues(input),\n getIssue: input => this.#getIntakeIssue(input),\n createComment: input => this.#createIntakeComment(input),\n updateIssue: input => this.#updateIntakeIssue(input),\n };\n\n /**\n * Background-dispatch context: the org's OAuth connection with a fresh\n * token. Linear work items store the issue UUID directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n const accessToken = await this.getFreshAccessToken(connection);\n return { connection: { type: 'oauth', accessToken }, issueId: externalSource.externalId };\n }\n /**\n * The OAuth connect/callback flow round-trips a signed `state` through\n * Linear, so a multi-replica deploy needs a deployment-stable state secret.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #clientId: string;\n readonly #clientSecret: string;\n\n readonly #rules: LinearEventRules;\n\n get rules(): LinearEventRules {\n return this.#rules;\n }\n\n constructor(config: LinearIntegrationConfig) {\n this.#rules = resolveLinearRules(config.rules);\n const missing = (['clientId', 'clientSecret'] as const).filter(key => !config[key]);\n if (missing.length > 0) {\n throw new Error(`LinearIntegration is missing required config: ${missing.join(', ')}.`);\n }\n this.#clientId = config.clientId;\n this.#clientSecret = config.clientSecret;\n }\n\n // ── OAuth ────────────────────────────────────────────────────────────────\n\n /**\n * Build the OAuth authorize URL. `prompt=consent` forces the workspace\n * picker even for an already-authorized user, so \"reconnect\" can switch\n * workspaces.\n */\n buildAuthorizeUrl(state: string, redirectUri: string): string {\n const url = new URL(LINEAR_AUTHORIZE_URL);\n url.searchParams.set('client_id', this.#clientId);\n url.searchParams.set('redirect_uri', redirectUri);\n url.searchParams.set('response_type', 'code');\n // `comments:create` lets the agent's linear_create_comment tool post\n // comments; everything else the integration does is read-only.\n url.searchParams.set('scope', 'read,comments:create');\n url.searchParams.set('state', state);\n url.searchParams.set('prompt', 'consent');\n return url.toString();\n }\n\n /** Exchange an OAuth `code` for a workspace-scoped token set. */\n async exchangeOAuthCode(code: string, redirectUri: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'authorization_code', code, redirect_uri: redirectUri }, 'token exchange');\n }\n\n /**\n * Exchange a refresh token for a new token set. Linear rotates refresh\n * tokens, so the returned set replaces the stored one entirely. A 400/401\n * here means the refresh token is invalid/revoked and the org must\n * re-authorize.\n */\n async refreshAccessToken(refreshToken: string): Promise<LinearTokenSet> {\n return this.#requestTokens({ grant_type: 'refresh_token', refresh_token: refreshToken }, 'token refresh');\n }\n\n /** POST to Linear's token endpoint and normalize the response. */\n async #requestTokens(params: Record<string, string>, label: string): Promise<LinearTokenSet> {\n const res = await fetch(LINEAR_TOKEN_URL, {\n method: 'POST',\n signal: AbortSignal.timeout(10_000),\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n ...params,\n client_id: this.#clientId,\n client_secret: this.#clientSecret,\n }),\n });\n if (!res.ok) {\n const err = new Error(`Linear ${label} failed (${res.status})`);\n (err as { status?: number }).status = res.status;\n throw err;\n }\n const body = (await res.json()) as {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n if (!body.access_token) {\n throw new Error(`Linear ${label} returned no access token.`);\n }\n return {\n accessToken: body.access_token,\n refreshToken: body.refresh_token ?? null,\n expiresAt: typeof body.expires_in === 'number' ? new Date(Date.now() + body.expires_in * 1000) : null,\n scope: body.scope ?? null,\n };\n }\n\n // ── GraphQL reads/writes ─────────────────────────────────────────────────\n\n /** Fetch the workspace (organization) the access token is scoped to. */\n async fetchWorkspace(accessToken: string): Promise<LinearWorkspace> {\n const data = await linearGraphql<{ organization: { name: string; urlKey: string } }>(\n accessToken,\n `query { organization { name urlKey } }`,\n );\n return { name: data.organization.name, urlKey: data.organization.urlKey };\n }\n\n async #listIntakeIssues(input: ListIntakeIssuesInput): Promise<{ issues: IntakeIssue[]; nextCursor: string | null }> {\n const accessToken = getLinearAccessToken(input.connection);\n if (input.sourceIds.length === 0) return { issues: [], nextCursor: null };\n const attributionSourceIds = input.attributionSourceIds ?? input.sourceIds;\n const { projectIds, teamIds } = splitSelfManagedSourceIds(input.sourceIds);\n const cursors = decodeSelfManagedListCursor(input.cursor, input.sourceIds, attributionSourceIds);\n\n // Project-only intake still uses one Linear query, but its provider cursor\n // is wrapped so a later source-selection change cannot reinterpret it.\n if (teamIds.length === 0) {\n if (cursors.projects === null) return { issues: [], nextCursor: null };\n const result = await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels);\n return {\n issues: result.issues.map(issue => linearIssueToIntakeIssue(issue)),\n nextCursor: encodeSelfManagedListCursor(\n { projects: result.nextCursor, teams: null },\n input.sourceIds,\n attributionSourceIds,\n ),\n };\n }\n\n // A team is selected. List projects and teams separately (the two Linear\n // filters would AND together in one query) and stamp each issue with the\n // selected source that surfaced it. Each source kind paginates on its own\n // cursor, carried in a compound cursor when both are present.\n const projectResult =\n projectIds.length > 0 && cursors.projects !== null\n ? await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels)\n : { issues: [], nextCursor: null };\n const teamResult =\n cursors.teams !== null\n ? await this.listActiveIssues(accessToken, cursors.teams, undefined, input.labels, teamIds)\n : { issues: [], nextCursor: null };\n\n // Stamp project issues with their (selected) project id; stamp team issues\n // with the team source id. A team issue whose project was also selected is\n // reconciled by the dedupe step below (project wins).\n const projectIssues = projectResult.issues.map(issue => linearIssueToIntakeIssue(issue));\n const selectedTeamSourceById = new Map(teamIds.map(teamId => [teamId, encodeSelfManagedTeamSourceId(teamId)]));\n const selectedProjectIds = new Set(splitSelfManagedSourceIds(attributionSourceIds).projectIds);\n const teamIssues = teamResult.issues\n .filter(issue => issue.projectId === null || !selectedProjectIds.has(issue.projectId))\n .map(issue => {\n const teamSourceId = issue.teamId ? selectedTeamSourceById.get(issue.teamId) : undefined;\n if (!teamSourceId) throw new Error('Linear returned an issue outside the selected teams.');\n return { ...linearIssueToIntakeIssue(issue), sourceId: teamSourceId };\n });\n\n const deduped = dedupeSelfManagedIssues([...projectIssues, ...teamIssues]);\n return {\n issues: deduped,\n nextCursor: encodeSelfManagedListCursor(\n { projects: projectResult.nextCursor, teams: teamResult.nextCursor },\n input.sourceIds,\n attributionSourceIds,\n ),\n };\n }\n\n async #getIntakeIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n return {\n ...linearIssueToIntakeIssue(issue),\n description: issue.description,\n commentCount: issue.comments.length,\n comments: issue.comments,\n };\n }\n\n async #createIntakeComment(input: CreateIntakeCommentInput): Promise<{ id: string; url: string } | null> {\n const accessToken = getLinearAccessToken(input.connection);\n return this.createIssueComment(accessToken, input.issueId, input.body);\n }\n\n async #updateIntakeIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const accessToken = getLinearAccessToken(input.connection);\n const issue = await this.fetchIssueDetail(accessToken, input.issueId);\n if (!issue) return null;\n const teamKey = issue.team;\n if (!teamKey) return null;\n const states = await this.#listTeamWorkflowStates(accessToken, teamKey);\n let targetState: { id: string; name: string; type: string } | null = null;\n if (input.state.kind === 'byType') {\n const wantedType = input.state.stateType;\n targetState = states.find(state => state.type === wantedType) ?? null;\n } else {\n const wanted = input.state.name.toLowerCase();\n targetState = states.find(state => state.name.toLowerCase() === wanted) ?? null;\n }\n if (!targetState) return null;\n if (targetState.name === issue.state) {\n return linearIssueToIntakeIssue(issue);\n }\n const data = await linearGraphql<{ issueUpdate: { success: boolean } }>(\n accessToken,\n `mutation UpdateIssueState($id: String!, $stateId: String!) {\n issueUpdate(id: $id, input: { stateId: $stateId }) { success }\n }`,\n { id: issue.id, stateId: targetState.id },\n );\n if (!data.issueUpdate.success) {\n throw new Error('Linear did not accept the issue update.');\n }\n const fresh = await this.fetchIssueDetail(accessToken, issue.id);\n if (!fresh) return null;\n return linearIssueToIntakeIssue(fresh);\n }\n\n async #listTeamWorkflowStates(\n accessToken: string,\n teamKey: string,\n ): Promise<Array<{ id: string; name: string; type: string }>> {\n const data = await linearGraphql<{\n team: { states: { nodes: Array<{ id: string; name: string; type: string }> } } | null;\n }>(\n accessToken,\n `query TeamStates($key: String!) {\n team(id: $key) { states(first: 100) { nodes { id name type } } }\n }`,\n { key: teamKey },\n );\n return data.team?.states.nodes ?? [];\n }\n\n /** List the workspace's projects (for the Settings intake-source picker). */\n async listProjects(accessToken: string): Promise<LinearProject[]> {\n const data = await linearGraphql<{\n projects: {\n nodes: Array<{\n id: string;\n name: string;\n state: string;\n teams: { nodes: Array<{ id: string; key: string; name: string }> };\n }>;\n };\n }>(\n accessToken,\n `query { projects(first: 100) { nodes { id name state teams(first: 10) { nodes { id key name } } } } }`,\n );\n return data.projects.nodes.map(node => ({\n id: node.id,\n name: node.name,\n state: node.state,\n teams: node.teams.nodes.map(team => ({ id: team.id, key: team.key, name: team.name })),\n }));\n }\n\n /** List the workspace's teams (for the Settings intake-source picker). */\n async listTeams(accessToken: string): Promise<LinearTeam[]> {\n const teams: Array<{ id: string; key: string; name: string }> = [];\n let after: string | undefined;\n for (let page = 0; page < LINEAR_TEAMS_MAX_PAGES; page += 1) {\n const data = await linearGraphql<{\n teams: {\n nodes: Array<{ id: string; key: string; name: string }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n }>(\n accessToken,\n `query Teams($first: Int!, $after: String) {\n teams(first: $first, after: $after) {\n nodes { id key name }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n { first: LINEAR_TEAMS_PAGE_SIZE, ...(after ? { after } : {}) },\n );\n teams.push(...data.teams.nodes);\n if (!data.teams.pageInfo.hasNextPage || !data.teams.pageInfo.endCursor) break;\n // A page that hands back the cursor it was asked for would replay forever.\n if (data.teams.pageInfo.endCursor === after) throw invalidLinearCursor();\n after = data.teams.pageInfo.endCursor;\n }\n return teams.map(team => ({\n id: team.id,\n key: team.key,\n name: team.name,\n sourceId: encodeSelfManagedTeamSourceId(team.id),\n }));\n }\n\n sourceMatchesIssue(sourceId: string, issue: Pick<LinearIssueDetail, 'projectId' | 'teamId'>): boolean {\n return isSelfManagedTeamSourceId(sourceId)\n ? issue.teamId === decodeSelfManagedTeamSourceId(sourceId)\n : issue.projectId === sourceId;\n }\n\n /**\n * List one page of the workspace's active issues (triage/backlog/unstarted/\n * started — completed and canceled are excluded), most recently updated\n * first. When `projectIds` is provided, only issues from those projects are\n * returned. When `teamIds` is provided, only issues in those teams are\n * returned, including issues that belong to no project. Callers list team\n * sources and project sources in separate calls so the two filters are not\n * AND-ed together.\n */\n async listActiveIssues(\n accessToken: string,\n after?: string,\n projectIds?: string[],\n labels?: string[],\n teamIds?: string[],\n ): Promise<LinearIssuePage> {\n const normalizedLabels = [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n const projectFilter = projectIds?.length ? ', project: { id: { in: $projectIds } }' : '';\n const projectVar = projectIds?.length ? ', $projectIds: [ID!]' : '';\n const teamFilter = teamIds?.length ? ', team: { id: { in: $teamIds } }' : '';\n const teamVar = teamIds?.length ? ', $teamIds: [ID!]' : '';\n const labelFilter = normalizedLabels.length > 0 ? ', labels: { name: { in: $labels } }' : '';\n const labelVar = normalizedLabels.length > 0 ? ', $labels: [String!]' : '';\n const data = await linearGraphql<IssuesQueryData>(\n accessToken,\n `query Intake($first: Int!, $after: String${projectVar}${teamVar}${labelVar}) {\n issues(\n first: $first\n after: $after\n orderBy: updatedAt\n filter: { state: { type: { in: [\"triage\", \"backlog\", \"unstarted\", \"started\"] } }${projectFilter}${teamFilter}${labelFilter} }\n ) {\n nodes {\n id\n identifier\n title\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n creator { name }\n team { id key }\n labels { nodes { name } }\n }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n {\n first: LINEAR_ISSUES_PAGE_SIZE,\n after: after ?? null,\n ...(projectIds?.length ? { projectIds } : {}),\n ...(teamIds?.length ? { teamIds } : {}),\n ...(normalizedLabels.length > 0 ? { labels: normalizedLabels } : {}),\n },\n );\n const { nodes, pageInfo } = data.issues;\n return {\n issues: nodes.map(node => ({\n id: node.id,\n projectId: node.project?.id ?? null,\n identifier: node.identifier,\n title: node.title,\n url: node.url,\n state: node.state.name,\n stateType: node.state.type,\n priorityLabel: node.priorityLabel,\n assignee: node.assignee?.name ?? null,\n creator: node.creator?.name ?? null,\n team: node.team?.key ?? null,\n labels: node.labels.nodes.map(label => label.name),\n createdAt: node.createdAt,\n updatedAt: node.updatedAt,\n teamId: node.team?.id ?? null,\n })),\n nextCursor: pageInfo.hasNextPage ? pageInfo.endCursor : null,\n };\n }\n\n /** Follow `comments.pageInfo` until exhausted so long discussions aren't truncated. */\n async #fetchRemainingIssueComments(\n accessToken: string,\n issueId: string,\n firstPage: IssueCommentsPage,\n ): Promise<IssueCommentNode[]> {\n const nodes = [...firstPage.nodes];\n let { hasNextPage, endCursor } = firstPage.pageInfo;\n for (let page = 1; hasNextPage && endCursor && page < ISSUE_COMMENTS_MAX_PAGES; page++) {\n const data = await linearGraphql<IssueCommentsQueryData>(\n accessToken,\n `query IssueComments($id: String!, $first: Int!, $after: String!) {\n issue(id: $id) {\n comments(first: $first, after: $after) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: issueId, first: ISSUE_COMMENTS_PAGE_SIZE, after: endCursor },\n );\n const comments = data.issue?.comments;\n if (!comments) break;\n nodes.push(...comments.nodes);\n if (comments.pageInfo.hasNextPage && comments.pageInfo.endCursor === endCursor) throw invalidLinearCursor();\n ({ hasNextPage, endCursor } = comments.pageInfo);\n }\n return nodes;\n }\n\n /**\n * Fetch one issue with its description and comments. `idOrIdentifier`\n * accepts both the Linear UUID and the human key (`ENG-123`). Returns\n * `null` when the issue doesn't exist (Linear reports it as an \"Entity not\n * found\" error).\n */\n async fetchIssueDetail(\n accessToken: string,\n idOrIdentifier: string,\n _sourceIds?: string[],\n _routedSourceIds?: string[],\n ): Promise<LinearIssueDetail | null> {\n let data: IssueDetailQueryData;\n try {\n data = await linearGraphql<IssueDetailQueryData>(\n accessToken,\n `query IssueDetail($id: String!, $commentsFirst: Int!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n url\n priorityLabel\n createdAt\n updatedAt\n state { name type }\n project { id }\n assignee { name }\n creator { name }\n team { id key }\n labels { nodes { name } }\n comments(first: $commentsFirst) {\n nodes { body createdAt user { name } }\n pageInfo { hasNextPage endCursor }\n }\n }\n }`,\n { id: idOrIdentifier, commentsFirst: ISSUE_COMMENTS_PAGE_SIZE },\n );\n } catch (err) {\n // Linear surfaces unknown ids/identifiers as a GraphQL \"Entity not\n // found\" error rather than a null node — map that to \"issue doesn't\n // exist\".\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const issue = data.issue;\n if (!issue) return null;\n const allComments = await this.#fetchRemainingIssueComments(accessToken, issue.id, issue.comments);\n const comments = allComments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return {\n id: issue.id,\n projectId: issue.project?.id ?? null,\n teamId: issue.team?.id ?? null,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description?.trim() ? issue.description : null,\n url: issue.url,\n state: issue.state.name,\n stateType: issue.state.type,\n priorityLabel: issue.priorityLabel,\n assignee: issue.assignee?.name ?? null,\n creator: issue.creator?.name ?? null,\n team: issue.team?.key ?? null,\n labels: issue.labels.nodes.map(label => label.name),\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n comments: comments.map(comment => ({\n author: comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n }\n\n /**\n * Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID\n * and the human key (`ENG-123`) — the identifier is resolved to a UUID\n * first because `commentCreate` only accepts UUIDs. Returns `null` when the\n * issue doesn't exist.\n */\n async createIssueComment(\n accessToken: string,\n idOrIdentifier: string,\n body: string,\n ): Promise<LinearCreatedComment | null> {\n let issueId: string;\n try {\n const data = await linearGraphql<IssueIdQueryData>(\n accessToken,\n `query IssueId($id: String!) { issue(id: $id) { id } }`,\n { id: idOrIdentifier },\n );\n if (!data.issue) return null;\n issueId = data.issue.id;\n } catch (err) {\n if (err instanceof Error && /entity not found/i.test(err.message)) return null;\n throw err;\n }\n const data = await linearGraphql<CommentCreateMutationData>(\n accessToken,\n `mutation CommentCreate($input: CommentCreateInput!) {\n commentCreate(input: $input) { success comment { id url } }\n }`,\n { input: { issueId, body } },\n );\n if (!data.commentCreate.success || !data.commentCreate.comment) {\n throw new Error('Linear did not accept the comment.');\n }\n return data.commentCreate.comment;\n }\n\n // ── FactoryIntegration surface ───────────────────────────────────────────\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!linearIssueReconciliationEnabled()) return [];\n const reconcile = attachLinearIssueReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = linearIssueReconciliationInterval();\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/linear/*` + `/auth/linear/*` Mastra\n * `apiRoutes` (status, OAuth connect/callback, projects + issues for\n * Intake). Handlers operate on this instance.\n */\n routes(ctx: IntegrationContext): ApiRoute[] {\n return buildLinearRoutes({\n linear: this,\n auth: ctx.auth,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(this, ctx),\n workItems: ctx.runtime?.workItems,\n boards: ctx.runtime?.boards,\n });\n }\n\n /**\n * Org-scoped agent tools: issue detail + comment tools for sessions whose\n * project belongs to an org with an active Linear connection.\n */\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext: args.requestContext, linear: this });\n }\n\n /** Non-secret config snapshot for system diagnostics/startup logs. */\n diagnostics(): Record<string, unknown> {\n return {\n oauthAppConfigured: true,\n };\n }\n}\n\nfunction getLinearAccessToken(connection: IntegrationConnection): string {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n return connection.accessToken;\n}\n\n/**\n * Self-managed source ids. Projects use their raw Linear project id (no prefix,\n * preserving existing intake bindings). Teams are prefixed so the two never\n * collide and callers can tell them apart.\n */\nconst SELF_MANAGED_TEAM_SOURCE_PREFIX = 'linear-team:';\n\nfunction encodeSelfManagedTeamSourceId(teamId: string): string {\n return `${SELF_MANAGED_TEAM_SOURCE_PREFIX}${teamId}`;\n}\n\nfunction isSelfManagedTeamSourceId(sourceId: string): boolean {\n return sourceId.startsWith(SELF_MANAGED_TEAM_SOURCE_PREFIX);\n}\n\nfunction decodeSelfManagedTeamSourceId(sourceId: string): string {\n return sourceId.slice(SELF_MANAGED_TEAM_SOURCE_PREFIX.length);\n}\n\n/**\n * Split selected source ids into raw project ids and raw team ids. A team\n * source id carries the `linear-team:` prefix; everything else is a project id.\n */\nfunction splitSelfManagedSourceIds(sourceIds: string[]): { projectIds: string[]; teamIds: string[] } {\n const projectIds: string[] = [];\n const teamIds: string[] = [];\n for (const sourceId of sourceIds) {\n if (isSelfManagedTeamSourceId(sourceId)) teamIds.push(decodeSelfManagedTeamSourceId(sourceId));\n else projectIds.push(sourceId);\n }\n return { projectIds, teamIds };\n}\n\ntype SelfManagedListCursor = {\n v: 1;\n sourceSet: string;\n projects: string | null;\n teams: string | null;\n};\n\nfunction linearSourceSetFingerprint(sourceIds: string[], attributionSourceIds: string[]): string {\n const scope = {\n sourceIds: [...new Set(sourceIds)].sort(),\n attributionSourceIds: [...new Set(attributionSourceIds)].sort(),\n };\n return createHash('sha256').update(JSON.stringify(scope)).digest('base64url');\n}\n\nfunction invalidLinearCursor(): Error {\n return Object.assign(new Error('Linear cursor is invalid or stale.'), { code: 'invalid_cursor' as const });\n}\n\nfunction isCursorValue(value: unknown): value is string | null {\n return value === null || typeof value === 'string';\n}\n\nfunction decodeSelfManagedListCursor(\n cursor: string | undefined,\n sourceIds: string[],\n attributionSourceIds: string[],\n): { projects: string | null | undefined; teams: string | null | undefined } {\n const { projectIds, teamIds } = splitSelfManagedSourceIds(sourceIds);\n if (!cursor) {\n return {\n projects: projectIds.length > 0 ? undefined : null,\n teams: teamIds.length > 0 ? undefined : null,\n };\n }\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Partial<SelfManagedListCursor>;\n if (\n parsed.v !== 1 ||\n parsed.sourceSet !== linearSourceSetFingerprint(sourceIds, attributionSourceIds) ||\n !isCursorValue(parsed.projects) ||\n !isCursorValue(parsed.teams)\n ) {\n throw invalidLinearCursor();\n }\n return {\n projects: parsed.projects,\n teams: parsed.teams,\n };\n } catch {\n throw invalidLinearCursor();\n }\n}\n\nfunction encodeSelfManagedListCursor(\n state: Pick<SelfManagedListCursor, 'projects' | 'teams'>,\n sourceIds: string[],\n attributionSourceIds: string[],\n): string | null {\n if (state.projects === null && state.teams === null) return null;\n return Buffer.from(\n JSON.stringify({\n v: 1,\n sourceSet: linearSourceSetFingerprint(sourceIds, attributionSourceIds),\n ...state,\n } satisfies SelfManagedListCursor),\n ).toString('base64url');\n}\n\n/**\n * Deduplicate issues that surfaced from both a selected team and one of its\n * selected projects. Keeps one entry per Linear issue UUID.\n *\n * Tie-breaker — MOST-SPECIFIC-WINS: an issue in a selected project is\n * attributed to that project source (routes to the project's board); the team\n * source covers only the remainder (projectless issues and issues in projects\n * that were not separately selected). Change the comparison here to change the\n * routing policy — this is the single precedence point for the self-managed\n * integration, mirroring the platform-backed one.\n */\nfunction dedupeSelfManagedIssues(\n issues: Array<IntakeIssue & { sourceId?: string | null }>,\n): Array<IntakeIssue & { sourceId?: string | null }> {\n const isProjectSource = (sourceId: string | null | undefined): boolean =>\n typeof sourceId === 'string' && sourceId.length > 0 && !isSelfManagedTeamSourceId(sourceId);\n const winners = new Map<string, IntakeIssue & { sourceId?: string | null }>();\n for (const issue of issues) {\n const existing = winners.get(issue.id);\n if (!existing) {\n winners.set(issue.id, issue);\n continue;\n }\n // Project source beats team source; otherwise keep the first seen.\n if (isProjectSource(issue.sourceId) && !isProjectSource(existing.sourceId)) {\n winners.set(issue.id, issue);\n }\n }\n const seen = new Set<string>();\n const ordered: Array<IntakeIssue & { sourceId?: string | null }> = [];\n for (const issue of issues) {\n if (seen.has(issue.id)) continue;\n seen.add(issue.id);\n ordered.push(winners.get(issue.id)!);\n }\n return ordered;\n}\n\nfunction linearIssueToIntakeIssue(issue: Omit<LinearIssue, 'projectId'> & { projectId?: string | null }): IntakeIssue {\n return {\n id: issue.id,\n sourceId: issue.projectId ?? null,\n projectId: issue.projectId ?? null,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priorityLabel,\n assignee: issue.assignee,\n source: issue.team,\n labels: issue.labels,\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/issue-reconciler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAoBpD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,OAAO,CAAC,EACnD,OAAO,EAAE,kBAAkB,GAC1B,qBAAqB,GAAG,SAAS,CA6CnC"}
1
+ {"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/issue-reconciler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAsBpD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,OAAO,CAAC,EACnD,OAAO,EAAE,kBAAkB,GAC1B,qBAAqB,GAAG,SAAS,CA8CnC"}
@@ -16,6 +16,8 @@ function issueToIngress(issue) {
16
16
  creator: issue.author ?? null,
17
17
  team: issue.source ?? null,
18
18
  labels: [...issue.labels ?? []],
19
+ sourceId: issue.sourceId,
20
+ projectId: issue.projectId,
19
21
  createdAt: issue.createdAt,
20
22
  updatedAt: issue.updatedAt
21
23
  };
@@ -42,6 +44,7 @@ function attachLinearIssueReconciler(linear, context) {
42
44
  },
43
45
  metadata: (_item, issue) => ({
44
46
  linearIssueId: issue.id,
47
+ ...issue.projectId ? { linearProjectId: issue.projectId } : {},
45
48
  identifier: issue.identifier,
46
49
  linearState: issue.state,
47
50
  linearStateType: issue.stateType,
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/linear/issue-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { IntegrationContext } from '../base.js';\nimport { createIssueReconciler } from '../issue-reconciler.js';\nimport type { IssueReconciler } from '../issue-reconciler.js';\nimport type { LinearIntegration } from './integration.js';\nimport { LinearRules } from './rules.js';\nimport type { LinearIssueIngress } from './rules.js';\n\nexport type LinearIssueReconciler = IssueReconciler;\n\nfunction issueToIngress(issue: import('../../capabilities/intake.js').IntakeIssue): LinearIssueIngress {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee ?? null,\n creator: issue.author ?? null,\n team: issue.source ?? null,\n labels: [...(issue.labels ?? [])],\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nexport function attachLinearIssueReconciler(\n linear: Pick<LinearIntegration, 'intake' | 'rules'>,\n context: IntegrationContext,\n): LinearIssueReconciler | undefined {\n if (!context.runtime || !linear.intake.resolveIntakeDispatch) return undefined;\n const boards = context.runtime.boards;\n\n const rules = new LinearRules({\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n configVersion: context.runtime.configVersion,\n boards,\n linearRules: linear.rules,\n });\n\n return createIssueReconciler({\n integrationId: 'linear',\n intake: linear.intake,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n isTerminal: item => workItemPhaseSemantics(boards, item)?.kind === 'terminal',\n issueId: item => {\n const issueId = item.metadata?.linearIssueId;\n return typeof issueId === 'string' && issueId.length > 0 ? issueId : undefined;\n },\n metadata: (_item, issue) => ({\n linearIssueId: issue.id,\n identifier: issue.identifier,\n linearState: issue.state,\n linearStateType: issue.stateType,\n linearPriority: issue.priority,\n linearAssignee: issue.assignee,\n linearCreator: issue.author,\n linearTeam: issue.source,\n assignee: issue.assignee,\n creator: issue.author,\n author: issue.author,\n labels: issue.labels ?? [],\n }),\n onClosed: async (item, issue, project) => {\n await rules.ingest({\n orgId: project.orgId,\n userId: 'factory-rule-dispatcher',\n factoryProjectId: project.id,\n issues: [issueToIngress(issue)],\n });\n },\n });\n}\n"],"mappings":";;;;;AAUA,SAAS,eAAe,OAA+E;CACrG,OAAO;EACL,IAAI,MAAM;EACV,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,KAAK,MAAM;EACX,OAAO,MAAM,SAAS;EACtB,WAAW,MAAM,aAAa;EAC9B,eAAe,MAAM,YAAY;EACjC,UAAU,MAAM,YAAY;EAC5B,SAAS,MAAM,UAAU;EACzB,MAAM,MAAM,UAAU;EACtB,QAAQ,CAAC,GAAI,MAAM,UAAU,CAAC,CAAE;EAChC,WAAW,MAAM;EACjB,WAAW,MAAM;CACnB;AACF;AAEA,SAAgB,4BACd,QACA,SACmC;CACnC,IAAI,CAAC,QAAQ,WAAW,CAAC,OAAO,OAAO,uBAAuB,OAAO,KAAA;CACrE,MAAM,SAAS,QAAQ,QAAQ;CAE/B,MAAM,QAAQ,IAAI,YAAY;EAC5B,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,eAAe,QAAQ,QAAQ;EAC/B;EACA,aAAa,OAAO;CACtB,CAAC;CAED,OAAO,sBAAsB;EAC3B,eAAe;EACf,QAAQ,OAAO;EACf,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,aAAY,SAAQ,uBAAuB,QAAQ,IAAI,CAAC,EAAE,SAAS;EACnE,UAAS,SAAQ;GACf,MAAM,UAAU,KAAK,UAAU;GAC/B,OAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU,KAAA;EACvE;EACA,WAAW,OAAO,WAAW;GAC3B,eAAe,MAAM;GACrB,YAAY,MAAM;GAClB,aAAa,MAAM;GACnB,iBAAiB,MAAM;GACvB,gBAAgB,MAAM;GACtB,gBAAgB,MAAM;GACtB,eAAe,MAAM;GACrB,YAAY,MAAM;GAClB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,QAAQ,MAAM,UAAU,CAAC;EAC3B;EACA,UAAU,OAAO,MAAM,OAAO,YAAY;GACxC,MAAM,MAAM,OAAO;IACjB,OAAO,QAAQ;IACf,QAAQ;IACR,kBAAkB,QAAQ;IAC1B,QAAQ,CAAC,eAAe,KAAK,CAAC;GAChC,CAAC;EACH;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/linear/issue-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { IntegrationContext } from '../base.js';\nimport { createIssueReconciler } from '../issue-reconciler.js';\nimport type { IssueReconciler } from '../issue-reconciler.js';\nimport type { LinearIntegration } from './integration.js';\nimport { LinearRules } from './rules.js';\nimport type { LinearIssueIngress } from './rules.js';\n\nexport type LinearIssueReconciler = IssueReconciler;\n\nfunction issueToIngress(issue: import('../../capabilities/intake.js').IntakeIssue): LinearIssueIngress {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee ?? null,\n creator: issue.author ?? null,\n team: issue.source ?? null,\n labels: [...(issue.labels ?? [])],\n sourceId: issue.sourceId,\n projectId: issue.projectId,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nexport function attachLinearIssueReconciler(\n linear: Pick<LinearIntegration, 'intake' | 'rules'>,\n context: IntegrationContext,\n): LinearIssueReconciler | undefined {\n if (!context.runtime || !linear.intake.resolveIntakeDispatch) return undefined;\n const boards = context.runtime.boards;\n\n const rules = new LinearRules({\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n configVersion: context.runtime.configVersion,\n boards,\n linearRules: linear.rules,\n });\n\n return createIssueReconciler({\n integrationId: 'linear',\n intake: linear.intake,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n isTerminal: item => workItemPhaseSemantics(boards, item)?.kind === 'terminal',\n issueId: item => {\n const issueId = item.metadata?.linearIssueId;\n return typeof issueId === 'string' && issueId.length > 0 ? issueId : undefined;\n },\n metadata: (_item, issue) => ({\n linearIssueId: issue.id,\n ...(issue.projectId ? { linearProjectId: issue.projectId } : {}),\n identifier: issue.identifier,\n linearState: issue.state,\n linearStateType: issue.stateType,\n linearPriority: issue.priority,\n linearAssignee: issue.assignee,\n linearCreator: issue.author,\n linearTeam: issue.source,\n assignee: issue.assignee,\n creator: issue.author,\n author: issue.author,\n labels: issue.labels ?? [],\n }),\n onClosed: async (item, issue, project) => {\n await rules.ingest({\n orgId: project.orgId,\n userId: 'factory-rule-dispatcher',\n factoryProjectId: project.id,\n issues: [issueToIngress(issue)],\n });\n },\n });\n}\n"],"mappings":";;;;;AAUA,SAAS,eAAe,OAA+E;CACrG,OAAO;EACL,IAAI,MAAM;EACV,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,KAAK,MAAM;EACX,OAAO,MAAM,SAAS;EACtB,WAAW,MAAM,aAAa;EAC9B,eAAe,MAAM,YAAY;EACjC,UAAU,MAAM,YAAY;EAC5B,SAAS,MAAM,UAAU;EACzB,MAAM,MAAM,UAAU;EACtB,QAAQ,CAAC,GAAI,MAAM,UAAU,CAAC,CAAE;EAChC,UAAU,MAAM;EAChB,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,WAAW,MAAM;CACnB;AACF;AAEA,SAAgB,4BACd,QACA,SACmC;CACnC,IAAI,CAAC,QAAQ,WAAW,CAAC,OAAO,OAAO,uBAAuB,OAAO,KAAA;CACrE,MAAM,SAAS,QAAQ,QAAQ;CAE/B,MAAM,QAAQ,IAAI,YAAY;EAC5B,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,eAAe,QAAQ,QAAQ;EAC/B;EACA,aAAa,OAAO;CACtB,CAAC;CAED,OAAO,sBAAsB;EAC3B,eAAe;EACf,QAAQ,OAAO;EACf,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,aAAY,SAAQ,uBAAuB,QAAQ,IAAI,CAAC,EAAE,SAAS;EACnE,UAAS,SAAQ;GACf,MAAM,UAAU,KAAK,UAAU;GAC/B,OAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU,KAAA;EACvE;EACA,WAAW,OAAO,WAAW;GAC3B,eAAe,MAAM;GACrB,GAAI,MAAM,YAAY,EAAE,iBAAiB,MAAM,UAAU,IAAI,CAAC;GAC9D,YAAY,MAAM;GAClB,aAAa,MAAM;GACnB,iBAAiB,MAAM;GACvB,gBAAgB,MAAM;GACtB,gBAAgB,MAAM;GACtB,eAAe,MAAM;GACrB,YAAY,MAAM;GAClB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,QAAQ,MAAM,UAAU,CAAC;EAC3B;EACA,UAAU,OAAO,MAAM,OAAO,YAAY;GACxC,MAAM,MAAM,OAAO;IACjB,OAAO,QAAQ;IACf,QAAQ;IACR,kBAAkB,QAAQ;IAC1B,QAAQ,CAAC,eAAe,KAAK,CAAC;GAChC,CAAC;EACH;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAWrD;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,4FAA4F;IAC5F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;IAClE,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE;;;;OAIG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,oBAAoB,GAAG,eAAe,CAAC,CAAC;IAC3E,gFAAgF;IAChF,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAoHD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CAsV/E"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAWrD;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,4FAA4F;IAC5F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC;IAClE,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE;;;;OAIG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,oBAAoB,GAAG,eAAe,CAAC,CAAC;IAC3E,gFAAgF;IAChF,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAoHD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CAuV/E"}
@@ -297,7 +297,8 @@ function buildLinearRoutes(options) {
297
297
  labels: issue.labels,
298
298
  createdAt: issue.createdAt,
299
299
  updatedAt: issue.updatedAt,
300
- sourceId: issue.sourceId ?? null
300
+ sourceId: issue.sourceId ?? null,
301
+ projectId: issue.projectId ?? null
301
302
  }));
302
303
  if (factoryProjectId && intakeBoards && options.ingestFactoryIssues) await options.ingestFactoryIssues({
303
304
  orgId: resolved.tenant.orgId,