@mastra/factory 0.15.0 → 0.16.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/boards/index.d.ts +1 -1
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -2
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +4 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/semantics.d.ts +7 -0
- package/dist/boards/semantics.d.ts.map +1 -1
- package/dist/boards/semantics.js +15 -1
- package/dist/boards/semantics.js.map +1 -1
- package/dist/boards/transition-policy.d.ts +4 -0
- package/dist/boards/transition-policy.d.ts.map +1 -1
- package/dist/boards/transition-policy.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +6 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +37 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +4 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts +6 -3
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +66 -18
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +6 -2
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
- package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
- package/dist/integrations/incidentio/agent-tools.js +90 -0
- package/dist/integrations/incidentio/agent-tools.js.map +1 -0
- package/dist/integrations/incidentio/claim.d.ts +8 -0
- package/dist/integrations/incidentio/claim.d.ts.map +1 -0
- package/dist/integrations/incidentio/claim.js +14 -0
- package/dist/integrations/incidentio/claim.js.map +1 -0
- package/dist/integrations/incidentio/default-rules.d.ts +46 -0
- package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
- package/dist/integrations/incidentio/default-rules.js +66 -0
- package/dist/integrations/incidentio/default-rules.js.map +1 -0
- package/dist/integrations/incidentio/integration.d.ts +48 -2
- package/dist/integrations/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/incidentio/integration.js +88 -2
- package/dist/integrations/incidentio/integration.js.map +1 -1
- package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
- package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/incidentio/issue-reconciler.js +38 -2
- package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
- package/dist/integrations/incidentio/routes.d.ts +71 -0
- package/dist/integrations/incidentio/routes.d.ts.map +1 -0
- package/dist/integrations/incidentio/routes.js +245 -0
- package/dist/integrations/incidentio/routes.js.map +1 -0
- package/dist/integrations/incidentio/rules.d.ts +55 -0
- package/dist/integrations/incidentio/rules.d.ts.map +1 -0
- package/dist/integrations/incidentio/rules.js +175 -0
- package/dist/integrations/incidentio/rules.js.map +1 -0
- package/dist/integrations/jira/adf.d.ts +26 -0
- package/dist/integrations/jira/adf.d.ts.map +1 -0
- package/dist/integrations/jira/adf.js +73 -0
- package/dist/integrations/jira/adf.js.map +1 -0
- package/dist/integrations/jira/agent-tools.d.ts +45 -0
- package/dist/integrations/jira/agent-tools.d.ts.map +1 -0
- package/dist/integrations/jira/agent-tools.js +87 -0
- package/dist/integrations/jira/agent-tools.js.map +1 -0
- package/dist/integrations/jira/api.d.ts +157 -0
- package/dist/integrations/jira/api.d.ts.map +1 -0
- package/dist/integrations/jira/api.js +141 -0
- package/dist/integrations/jira/api.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +9 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -0
- package/dist/integrations/jira/claim.js +15 -0
- package/dist/integrations/jira/claim.js.map +1 -0
- package/dist/integrations/jira/default-rules.d.ts +47 -0
- package/dist/integrations/jira/default-rules.d.ts.map +1 -0
- package/dist/integrations/jira/default-rules.js +67 -0
- package/dist/integrations/jira/default-rules.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +114 -0
- package/dist/integrations/jira/integration.d.ts.map +1 -0
- package/dist/integrations/jira/integration.js +401 -0
- package/dist/integrations/jira/integration.js.map +1 -0
- package/dist/integrations/jira/issue-reconciler.d.ts +10 -0
- package/dist/integrations/jira/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/jira/issue-reconciler.js +70 -0
- package/dist/integrations/jira/issue-reconciler.js.map +1 -0
- package/dist/integrations/jira/reconciliation-config.d.ts +3 -0
- package/dist/integrations/jira/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/jira/reconciliation-config.js +17 -0
- package/dist/integrations/jira/reconciliation-config.js.map +1 -0
- package/dist/integrations/jira/routes.d.ts +54 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -0
- package/dist/integrations/jira/routes.js +260 -0
- package/dist/integrations/jira/routes.js.map +1 -0
- package/dist/integrations/jira/rules.d.ts +57 -0
- package/dist/integrations/jira/rules.d.ts.map +1 -0
- package/dist/integrations/jira/rules.js +175 -0
- package/dist/integrations/jira/rules.js.map +1 -0
- package/dist/integrations/linear/claim.d.ts +8 -0
- package/dist/integrations/linear/claim.d.ts.map +1 -0
- package/dist/integrations/linear/claim.js +14 -0
- package/dist/integrations/linear/claim.js.map +1 -0
- package/dist/integrations/linear/default-rules.d.ts +1 -0
- package/dist/integrations/linear/default-rules.d.ts.map +1 -1
- package/dist/integrations/linear/default-rules.js +2 -0
- package/dist/integrations/linear/default-rules.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts +24 -6
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +257 -37
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts +10 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +74 -12
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +1 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +27 -2
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/connect/routes.d.ts +39 -0
- package/dist/integrations/platform/connect/routes.d.ts.map +1 -0
- package/dist/integrations/platform/connect/routes.js +129 -0
- package/dist/integrations/platform/connect/routes.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +1 -1
- package/dist/integrations/platform/github/integration.js +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
- package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js +371 -22
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/agent-tools.d.ts +36 -0
- package/dist/integrations/platform/jira/agent-tools.d.ts.map +1 -0
- package/dist/integrations/platform/jira/agent-tools.js +85 -0
- package/dist/integrations/platform/jira/agent-tools.js.map +1 -0
- package/dist/integrations/platform/jira/integration.d.ts +65 -0
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -0
- package/dist/integrations/platform/jira/integration.js +540 -0
- package/dist/integrations/platform/jira/integration.js.map +1 -0
- package/dist/integrations/platform/jira/routes.d.ts +55 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -0
- package/dist/integrations/platform/jira/routes.js +274 -0
- package/dist/integrations/platform/jira/routes.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +34 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +271 -48
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +15 -5
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +2 -5
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +24 -4
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +8 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +1 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +180 -106
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +4 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +14 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +20 -0
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -0
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +30 -2
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +13 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +11 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +72 -3
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +9 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +13 -2
- package/dist/rules/validation.js.map +1 -1
- package/dist/session/run-audit.d.ts +9 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +37 -6
- package/dist/session/run-audit.js.map +1 -1
- package/dist/skills/service.d.ts +12 -0
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +27 -1
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +11 -8
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +69 -24
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +41 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +159 -12
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/session-messaging.d.ts +23 -0
- package/dist/supervisor/session-messaging.d.ts.map +1 -0
- package/dist/supervisor/session-messaging.js +15 -0
- package/dist/supervisor/session-messaging.js.map +1 -0
- package/dist/supervisor/write-tools.d.ts +2 -1
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +11 -7
- package/dist/supervisor/write-tools.js.map +1 -1
- package/dist/work-item-branch.d.ts +1 -1
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +4 -2
- package/dist/work-item-branch.js.map +1 -1
- package/package.json +11 -11
|
@@ -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';\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 Work auto-triages the new issue.\n board: context.intake?.board ?? 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'triage',\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":"
|
|
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 Work auto-triages the new issue.\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 ?? 'triage',\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"}
|
|
@@ -57,7 +57,11 @@ export interface LinearWorkspace {
|
|
|
57
57
|
}
|
|
58
58
|
export interface LinearIssue {
|
|
59
59
|
id: string;
|
|
60
|
-
projectId: string;
|
|
60
|
+
projectId: string | null;
|
|
61
|
+
/** Backend workspace identity when source ids span multiple workspaces. */
|
|
62
|
+
workspaceId?: string;
|
|
63
|
+
/** Raw Linear team id for exact team-source attribution. */
|
|
64
|
+
teamId: string | null;
|
|
61
65
|
/** Human key like `ENG-123`. */
|
|
62
66
|
identifier: string;
|
|
63
67
|
title: string;
|
|
@@ -94,14 +98,22 @@ export interface LinearProject {
|
|
|
94
98
|
/** Teams the project belongs to (the Settings picker groups by these). */
|
|
95
99
|
teams: LinearProjectTeam[];
|
|
96
100
|
}
|
|
101
|
+
/** A Linear team, selectable as an intake source in its own right. */
|
|
102
|
+
export interface LinearTeam {
|
|
103
|
+
id: string;
|
|
104
|
+
/** Short team key, e.g. `ENG`. */
|
|
105
|
+
key: string;
|
|
106
|
+
name: string;
|
|
107
|
+
/** Opaque intake source identifier. Optional on the wire for compatibility. */
|
|
108
|
+
sourceId?: string;
|
|
109
|
+
}
|
|
97
110
|
export interface LinearIssueComment {
|
|
98
111
|
author: string | null;
|
|
99
112
|
body: string;
|
|
100
113
|
createdAt: string;
|
|
101
114
|
}
|
|
102
115
|
/** Full issue payload for agent context: everything in {@link LinearIssue} plus description and discussion. */
|
|
103
|
-
export interface LinearIssueDetail extends
|
|
104
|
-
projectId: string | null;
|
|
116
|
+
export interface LinearIssueDetail extends LinearIssue {
|
|
105
117
|
/** Markdown body of the issue, or `null` when empty. */
|
|
106
118
|
description: string | null;
|
|
107
119
|
/** Discussion comments, oldest first. */
|
|
@@ -201,20 +213,26 @@ export declare class LinearIntegration implements FactoryIntegration {
|
|
|
201
213
|
fetchWorkspace(accessToken: string): Promise<LinearWorkspace>;
|
|
202
214
|
/** List the workspace's projects (for the Settings intake-source picker). */
|
|
203
215
|
listProjects(accessToken: string): Promise<LinearProject[]>;
|
|
216
|
+
/** List the workspace's teams (for the Settings intake-source picker). */
|
|
217
|
+
listTeams(accessToken: string): Promise<LinearTeam[]>;
|
|
218
|
+
sourceMatchesIssue(sourceId: string, issue: Pick<LinearIssueDetail, 'projectId' | 'teamId'>): boolean;
|
|
204
219
|
/**
|
|
205
220
|
* List one page of the workspace's active issues (triage/backlog/unstarted/
|
|
206
221
|
* started — completed and canceled are excluded), most recently updated
|
|
207
222
|
* first. When `projectIds` is provided, only issues from those projects are
|
|
208
|
-
* returned.
|
|
223
|
+
* returned. When `teamIds` is provided, only issues in those teams are
|
|
224
|
+
* returned, including issues that belong to no project. Callers list team
|
|
225
|
+
* sources and project sources in separate calls so the two filters are not
|
|
226
|
+
* AND-ed together.
|
|
209
227
|
*/
|
|
210
|
-
listActiveIssues(accessToken: string, after?: string, projectIds?: string[], labels?: string[]): Promise<LinearIssuePage>;
|
|
228
|
+
listActiveIssues(accessToken: string, after?: string, projectIds?: string[], labels?: string[], teamIds?: string[]): Promise<LinearIssuePage>;
|
|
211
229
|
/**
|
|
212
230
|
* Fetch one issue with its description and comments. `idOrIdentifier`
|
|
213
231
|
* accepts both the Linear UUID and the human key (`ENG-123`). Returns
|
|
214
232
|
* `null` when the issue doesn't exist (Linear reports it as an "Entity not
|
|
215
233
|
* found" error).
|
|
216
234
|
*/
|
|
217
|
-
fetchIssueDetail(accessToken: string, idOrIdentifier: string): Promise<LinearIssueDetail | null>;
|
|
235
|
+
fetchIssueDetail(accessToken: string, idOrIdentifier: string, _sourceIds?: string[], _routedSourceIds?: string[]): Promise<LinearIssueDetail | null>;
|
|
218
236
|
/**
|
|
219
237
|
* Post a comment on an issue. `idOrIdentifier` accepts both the Linear UUID
|
|
220
238
|
* and the human key (`ENG-123`) — the identifier is resolved to a UUID
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/linear/integration.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,12 +5,35 @@ import { attachLinearIssueReconciler } from "./issue-reconciler.js";
|
|
|
5
5
|
import { linearIssueReconciliationEnabled, linearIssueReconciliationInterval } from "./reconciliation-config.js";
|
|
6
6
|
import { buildLinearRoutes } from "./routes.js";
|
|
7
7
|
import { buildLinearAgentTools } from "./agent-tools.js";
|
|
8
|
+
import { createHash } from "crypto";
|
|
8
9
|
//#region src/integrations/linear/integration.ts
|
|
10
|
+
/**
|
|
11
|
+
* `LinearIntegration` — the self-contained Linear integration.
|
|
12
|
+
*
|
|
13
|
+
* Implements the system-wide `FactoryIntegration` contract
|
|
14
|
+
* (`../factory-integration.ts`): the deploy entry reads the Linear OAuth env
|
|
15
|
+
* vars ONCE, constructs an instance with explicit credentials, and passes it
|
|
16
|
+
* to `MastraFactory`. Everything Linear-flavored the system does — the OAuth
|
|
17
|
+
* connect/callback flow, workspace/project/issue reads for Intake, and the
|
|
18
|
+
* agent's issue tools — flows through this instance. No other module reads
|
|
19
|
+
* `LINEAR_*` env vars.
|
|
20
|
+
*
|
|
21
|
+
* The class owns:
|
|
22
|
+
* - OAuth: the user-facing authorize URL, code exchange, and refresh-token
|
|
23
|
+
* rotation against Linear's `/oauth/token` endpoint.
|
|
24
|
+
* - GraphQL reads/writes: viewer workspace, projects, active issues for
|
|
25
|
+
* Intake, full issue detail (description + discussion), issue comments.
|
|
26
|
+
* - The HTTP surface (`routes()`) and per-request agent tools
|
|
27
|
+
* (`agentTools()`), delegating to `./routes.ts` / `./agent-tools.ts` with
|
|
28
|
+
* `this` as the API client.
|
|
29
|
+
*/
|
|
9
30
|
const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
|
|
10
31
|
const LINEAR_TOKEN_URL = "https://api.linear.app/oauth/token";
|
|
11
32
|
const LINEAR_AUTHORIZE_URL = "https://linear.app/oauth/authorize";
|
|
12
33
|
const LINEAR_ISSUES_PAGE_SIZE = 30;
|
|
13
34
|
const ISSUE_COMMENTS_PAGE_SIZE = 50;
|
|
35
|
+
const LINEAR_TEAMS_PAGE_SIZE = 100;
|
|
36
|
+
const LINEAR_TEAMS_MAX_PAGES = 20;
|
|
14
37
|
/** Hard stop for comment pagination so a misbehaving cursor can't loop forever. */
|
|
15
38
|
const ISSUE_COMMENTS_MAX_PAGES = 20;
|
|
16
39
|
/** Refresh this many ms before the recorded expiry to absorb clock skew. */
|
|
@@ -236,13 +259,19 @@ var LinearIntegration = class {
|
|
|
236
259
|
const connection = await this.loadConnection(orgId);
|
|
237
260
|
if (!connection) return [];
|
|
238
261
|
const accessToken = await this.getFreshAccessToken(connection);
|
|
239
|
-
|
|
262
|
+
const [projects, teams] = await Promise.all([this.listProjects(accessToken), this.listTeams(accessToken)]);
|
|
263
|
+
return [...projects.map((project) => ({
|
|
240
264
|
id: project.id,
|
|
241
265
|
name: project.name,
|
|
242
266
|
type: "project"
|
|
243
|
-
}))
|
|
267
|
+
})), ...teams.map((team) => ({
|
|
268
|
+
id: encodeSelfManagedTeamSourceId(team.id),
|
|
269
|
+
name: team.name,
|
|
270
|
+
type: "team",
|
|
271
|
+
metadata: { teamKey: team.key }
|
|
272
|
+
}))];
|
|
244
273
|
},
|
|
245
|
-
listItems: async ({ orgId, sourceIds, cursor }) => {
|
|
274
|
+
listItems: async ({ orgId, sourceIds, attributionSourceIds, cursor }) => {
|
|
246
275
|
if (sourceIds.length === 0) return {
|
|
247
276
|
items: [],
|
|
248
277
|
nextCursor: null
|
|
@@ -253,28 +282,39 @@ var LinearIntegration = class {
|
|
|
253
282
|
nextCursor: null
|
|
254
283
|
};
|
|
255
284
|
const accessToken = await this.getFreshAccessToken(connection);
|
|
256
|
-
const page = await this
|
|
285
|
+
const page = await this.#listIntakeIssues({
|
|
286
|
+
connection: {
|
|
287
|
+
type: "oauth",
|
|
288
|
+
accessToken
|
|
289
|
+
},
|
|
290
|
+
sourceIds,
|
|
291
|
+
attributionSourceIds,
|
|
292
|
+
cursor
|
|
293
|
+
});
|
|
257
294
|
return {
|
|
258
|
-
items: page.issues.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
items: page.issues.flatMap((issue) => {
|
|
296
|
+
if (!issue.sourceId) return [];
|
|
297
|
+
return [{
|
|
298
|
+
source: {
|
|
299
|
+
type: "issue",
|
|
300
|
+
externalId: issue.id,
|
|
301
|
+
url: issue.url
|
|
302
|
+
},
|
|
303
|
+
sourceId: issue.sourceId,
|
|
304
|
+
title: `${issue.identifier}: ${issue.title}`,
|
|
305
|
+
status: issue.state ?? "",
|
|
306
|
+
labels: issue.labels,
|
|
307
|
+
assignee: issue.assignee,
|
|
308
|
+
createdAt: issue.createdAt,
|
|
309
|
+
updatedAt: issue.updatedAt,
|
|
310
|
+
metadata: {
|
|
311
|
+
identifier: issue.identifier,
|
|
312
|
+
stateType: issue.stateType,
|
|
313
|
+
priority: issue.priority,
|
|
314
|
+
team: issue.source
|
|
315
|
+
}
|
|
316
|
+
}];
|
|
317
|
+
}),
|
|
278
318
|
nextCursor: page.nextCursor
|
|
279
319
|
};
|
|
280
320
|
},
|
|
@@ -388,10 +428,52 @@ var LinearIntegration = class {
|
|
|
388
428
|
}
|
|
389
429
|
async #listIntakeIssues(input) {
|
|
390
430
|
const accessToken = getLinearAccessToken(input.connection);
|
|
391
|
-
|
|
431
|
+
if (input.sourceIds.length === 0) return {
|
|
432
|
+
issues: [],
|
|
433
|
+
nextCursor: null
|
|
434
|
+
};
|
|
435
|
+
const attributionSourceIds = input.attributionSourceIds ?? input.sourceIds;
|
|
436
|
+
const { projectIds, teamIds } = splitSelfManagedSourceIds(input.sourceIds);
|
|
437
|
+
const cursors = decodeSelfManagedListCursor(input.cursor, input.sourceIds, attributionSourceIds);
|
|
438
|
+
if (teamIds.length === 0) {
|
|
439
|
+
if (cursors.projects === null) return {
|
|
440
|
+
issues: [],
|
|
441
|
+
nextCursor: null
|
|
442
|
+
};
|
|
443
|
+
const result = await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels);
|
|
444
|
+
return {
|
|
445
|
+
issues: result.issues.map((issue) => linearIssueToIntakeIssue(issue)),
|
|
446
|
+
nextCursor: encodeSelfManagedListCursor({
|
|
447
|
+
projects: result.nextCursor,
|
|
448
|
+
teams: null
|
|
449
|
+
}, input.sourceIds, attributionSourceIds)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const projectResult = projectIds.length > 0 && cursors.projects !== null ? await this.listActiveIssues(accessToken, cursors.projects, projectIds, input.labels) : {
|
|
453
|
+
issues: [],
|
|
454
|
+
nextCursor: null
|
|
455
|
+
};
|
|
456
|
+
const teamResult = cursors.teams !== null ? await this.listActiveIssues(accessToken, cursors.teams, void 0, input.labels, teamIds) : {
|
|
457
|
+
issues: [],
|
|
458
|
+
nextCursor: null
|
|
459
|
+
};
|
|
460
|
+
const projectIssues = projectResult.issues.map((issue) => linearIssueToIntakeIssue(issue));
|
|
461
|
+
const selectedTeamSourceById = new Map(teamIds.map((teamId) => [teamId, encodeSelfManagedTeamSourceId(teamId)]));
|
|
462
|
+
const selectedProjectIds = new Set(splitSelfManagedSourceIds(attributionSourceIds).projectIds);
|
|
463
|
+
const teamIssues = teamResult.issues.filter((issue) => issue.projectId === null || !selectedProjectIds.has(issue.projectId)).map((issue) => {
|
|
464
|
+
const teamSourceId = issue.teamId ? selectedTeamSourceById.get(issue.teamId) : void 0;
|
|
465
|
+
if (!teamSourceId) throw new Error("Linear returned an issue outside the selected teams.");
|
|
466
|
+
return {
|
|
467
|
+
...linearIssueToIntakeIssue(issue),
|
|
468
|
+
sourceId: teamSourceId
|
|
469
|
+
};
|
|
470
|
+
});
|
|
392
471
|
return {
|
|
393
|
-
issues:
|
|
394
|
-
nextCursor:
|
|
472
|
+
issues: dedupeSelfManagedIssues([...projectIssues, ...teamIssues]),
|
|
473
|
+
nextCursor: encodeSelfManagedListCursor({
|
|
474
|
+
projects: projectResult.nextCursor,
|
|
475
|
+
teams: teamResult.nextCursor
|
|
476
|
+
}, input.sourceIds, attributionSourceIds)
|
|
395
477
|
};
|
|
396
478
|
}
|
|
397
479
|
async #getIntakeIssue(input) {
|
|
@@ -454,23 +536,57 @@ var LinearIntegration = class {
|
|
|
454
536
|
}))
|
|
455
537
|
}));
|
|
456
538
|
}
|
|
539
|
+
/** List the workspace's teams (for the Settings intake-source picker). */
|
|
540
|
+
async listTeams(accessToken) {
|
|
541
|
+
const teams = [];
|
|
542
|
+
let after;
|
|
543
|
+
for (let page = 0; page < LINEAR_TEAMS_MAX_PAGES; page += 1) {
|
|
544
|
+
const data = await linearGraphql(accessToken, `query Teams($first: Int!, $after: String) {
|
|
545
|
+
teams(first: $first, after: $after) {
|
|
546
|
+
nodes { id key name }
|
|
547
|
+
pageInfo { hasNextPage endCursor }
|
|
548
|
+
}
|
|
549
|
+
}`, {
|
|
550
|
+
first: LINEAR_TEAMS_PAGE_SIZE,
|
|
551
|
+
...after ? { after } : {}
|
|
552
|
+
});
|
|
553
|
+
teams.push(...data.teams.nodes);
|
|
554
|
+
if (!data.teams.pageInfo.hasNextPage || !data.teams.pageInfo.endCursor) break;
|
|
555
|
+
if (data.teams.pageInfo.endCursor === after) throw invalidLinearCursor();
|
|
556
|
+
after = data.teams.pageInfo.endCursor;
|
|
557
|
+
}
|
|
558
|
+
return teams.map((team) => ({
|
|
559
|
+
id: team.id,
|
|
560
|
+
key: team.key,
|
|
561
|
+
name: team.name,
|
|
562
|
+
sourceId: encodeSelfManagedTeamSourceId(team.id)
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
sourceMatchesIssue(sourceId, issue) {
|
|
566
|
+
return isSelfManagedTeamSourceId(sourceId) ? issue.teamId === decodeSelfManagedTeamSourceId(sourceId) : issue.projectId === sourceId;
|
|
567
|
+
}
|
|
457
568
|
/**
|
|
458
569
|
* List one page of the workspace's active issues (triage/backlog/unstarted/
|
|
459
570
|
* started — completed and canceled are excluded), most recently updated
|
|
460
571
|
* first. When `projectIds` is provided, only issues from those projects are
|
|
461
|
-
* returned.
|
|
572
|
+
* returned. When `teamIds` is provided, only issues in those teams are
|
|
573
|
+
* returned, including issues that belong to no project. Callers list team
|
|
574
|
+
* sources and project sources in separate calls so the two filters are not
|
|
575
|
+
* AND-ed together.
|
|
462
576
|
*/
|
|
463
|
-
async listActiveIssues(accessToken, after, projectIds, labels) {
|
|
577
|
+
async listActiveIssues(accessToken, after, projectIds, labels, teamIds) {
|
|
464
578
|
const normalizedLabels = [...new Set((labels ?? []).map((label) => label.trim()).filter(Boolean))];
|
|
465
579
|
const projectFilter = projectIds?.length ? ", project: { id: { in: $projectIds } }" : "";
|
|
466
580
|
const projectVar = projectIds?.length ? ", $projectIds: [ID!]" : "";
|
|
581
|
+
const teamFilter = teamIds?.length ? ", team: { id: { in: $teamIds } }" : "";
|
|
582
|
+
const teamVar = teamIds?.length ? ", $teamIds: [ID!]" : "";
|
|
467
583
|
const labelFilter = normalizedLabels.length > 0 ? ", labels: { name: { in: $labels } }" : "";
|
|
468
|
-
const { nodes, pageInfo } = (await linearGraphql(accessToken, `query Intake($first: Int!, $after: String${projectVar}${normalizedLabels.length > 0 ? ", $labels: [String!]" : ""}) {
|
|
584
|
+
const { nodes, pageInfo } = (await linearGraphql(accessToken, `query Intake($first: Int!, $after: String${projectVar}${teamVar}${normalizedLabels.length > 0 ? ", $labels: [String!]" : ""}) {
|
|
469
585
|
issues(
|
|
470
586
|
first: $first
|
|
471
587
|
after: $after
|
|
472
588
|
orderBy: updatedAt
|
|
473
|
-
filter: { state: { type: { in: ["triage", "backlog", "unstarted", "started"] } }${projectFilter}${labelFilter} }
|
|
589
|
+
filter: { state: { type: { in: ["triage", "backlog", "unstarted", "started"] } }${projectFilter}${teamFilter}${labelFilter} }
|
|
474
590
|
) {
|
|
475
591
|
nodes {
|
|
476
592
|
id
|
|
@@ -484,7 +600,7 @@ var LinearIntegration = class {
|
|
|
484
600
|
project { id }
|
|
485
601
|
assignee { name }
|
|
486
602
|
creator { name }
|
|
487
|
-
team { key }
|
|
603
|
+
team { id key }
|
|
488
604
|
labels { nodes { name } }
|
|
489
605
|
}
|
|
490
606
|
pageInfo { hasNextPage endCursor }
|
|
@@ -493,12 +609,13 @@ var LinearIntegration = class {
|
|
|
493
609
|
first: LINEAR_ISSUES_PAGE_SIZE,
|
|
494
610
|
after: after ?? null,
|
|
495
611
|
...projectIds?.length ? { projectIds } : {},
|
|
612
|
+
...teamIds?.length ? { teamIds } : {},
|
|
496
613
|
...normalizedLabels.length > 0 ? { labels: normalizedLabels } : {}
|
|
497
614
|
})).issues;
|
|
498
615
|
return {
|
|
499
616
|
issues: nodes.map((node) => ({
|
|
500
617
|
id: node.id,
|
|
501
|
-
projectId: node.project
|
|
618
|
+
projectId: node.project?.id ?? null,
|
|
502
619
|
identifier: node.identifier,
|
|
503
620
|
title: node.title,
|
|
504
621
|
url: node.url,
|
|
@@ -510,7 +627,8 @@ var LinearIntegration = class {
|
|
|
510
627
|
team: node.team?.key ?? null,
|
|
511
628
|
labels: node.labels.nodes.map((label) => label.name),
|
|
512
629
|
createdAt: node.createdAt,
|
|
513
|
-
updatedAt: node.updatedAt
|
|
630
|
+
updatedAt: node.updatedAt,
|
|
631
|
+
teamId: node.team?.id ?? null
|
|
514
632
|
})),
|
|
515
633
|
nextCursor: pageInfo.hasNextPage ? pageInfo.endCursor : null
|
|
516
634
|
};
|
|
@@ -534,6 +652,7 @@ var LinearIntegration = class {
|
|
|
534
652
|
})).issue?.comments;
|
|
535
653
|
if (!comments) break;
|
|
536
654
|
nodes.push(...comments.nodes);
|
|
655
|
+
if (comments.pageInfo.hasNextPage && comments.pageInfo.endCursor === endCursor) throw invalidLinearCursor();
|
|
537
656
|
({hasNextPage, endCursor} = comments.pageInfo);
|
|
538
657
|
}
|
|
539
658
|
return nodes;
|
|
@@ -544,7 +663,7 @@ var LinearIntegration = class {
|
|
|
544
663
|
* `null` when the issue doesn't exist (Linear reports it as an "Entity not
|
|
545
664
|
* found" error).
|
|
546
665
|
*/
|
|
547
|
-
async fetchIssueDetail(accessToken, idOrIdentifier) {
|
|
666
|
+
async fetchIssueDetail(accessToken, idOrIdentifier, _sourceIds, _routedSourceIds) {
|
|
548
667
|
let data;
|
|
549
668
|
try {
|
|
550
669
|
data = await linearGraphql(accessToken, `query IssueDetail($id: String!, $commentsFirst: Int!) {
|
|
@@ -561,7 +680,7 @@ var LinearIntegration = class {
|
|
|
561
680
|
project { id }
|
|
562
681
|
assignee { name }
|
|
563
682
|
creator { name }
|
|
564
|
-
team { key }
|
|
683
|
+
team { id key }
|
|
565
684
|
labels { nodes { name } }
|
|
566
685
|
comments(first: $commentsFirst) {
|
|
567
686
|
nodes { body createdAt user { name } }
|
|
@@ -582,6 +701,7 @@ var LinearIntegration = class {
|
|
|
582
701
|
return {
|
|
583
702
|
id: issue.id,
|
|
584
703
|
projectId: issue.project?.id ?? null,
|
|
704
|
+
teamId: issue.team?.id ?? null,
|
|
585
705
|
identifier: issue.identifier,
|
|
586
706
|
title: issue.title,
|
|
587
707
|
description: issue.description?.trim() ? issue.description : null,
|
|
@@ -651,7 +771,9 @@ var LinearIntegration = class {
|
|
|
651
771
|
baseUrl: ctx.baseUrl,
|
|
652
772
|
intake: ctx.storage.intake,
|
|
653
773
|
projects: ctx.storage.projects,
|
|
654
|
-
ingestFactoryIssues: attachLinearRules(this, ctx)
|
|
774
|
+
ingestFactoryIssues: attachLinearRules(this, ctx),
|
|
775
|
+
workItems: ctx.runtime?.workItems,
|
|
776
|
+
boards: ctx.runtime?.boards
|
|
655
777
|
});
|
|
656
778
|
}
|
|
657
779
|
/**
|
|
@@ -673,6 +795,104 @@ function getLinearAccessToken(connection) {
|
|
|
673
795
|
if (connection.type !== "oauth") throw new Error("Linear capabilities require an OAuth connection.");
|
|
674
796
|
return connection.accessToken;
|
|
675
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Self-managed source ids. Projects use their raw Linear project id (no prefix,
|
|
800
|
+
* preserving existing intake bindings). Teams are prefixed so the two never
|
|
801
|
+
* collide and callers can tell them apart.
|
|
802
|
+
*/
|
|
803
|
+
const SELF_MANAGED_TEAM_SOURCE_PREFIX = "linear-team:";
|
|
804
|
+
function encodeSelfManagedTeamSourceId(teamId) {
|
|
805
|
+
return `${SELF_MANAGED_TEAM_SOURCE_PREFIX}${teamId}`;
|
|
806
|
+
}
|
|
807
|
+
function isSelfManagedTeamSourceId(sourceId) {
|
|
808
|
+
return sourceId.startsWith(SELF_MANAGED_TEAM_SOURCE_PREFIX);
|
|
809
|
+
}
|
|
810
|
+
function decodeSelfManagedTeamSourceId(sourceId) {
|
|
811
|
+
return sourceId.slice(12);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Split selected source ids into raw project ids and raw team ids. A team
|
|
815
|
+
* source id carries the `linear-team:` prefix; everything else is a project id.
|
|
816
|
+
*/
|
|
817
|
+
function splitSelfManagedSourceIds(sourceIds) {
|
|
818
|
+
const projectIds = [];
|
|
819
|
+
const teamIds = [];
|
|
820
|
+
for (const sourceId of sourceIds) if (isSelfManagedTeamSourceId(sourceId)) teamIds.push(decodeSelfManagedTeamSourceId(sourceId));
|
|
821
|
+
else projectIds.push(sourceId);
|
|
822
|
+
return {
|
|
823
|
+
projectIds,
|
|
824
|
+
teamIds
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function linearSourceSetFingerprint(sourceIds, attributionSourceIds) {
|
|
828
|
+
const scope = {
|
|
829
|
+
sourceIds: [...new Set(sourceIds)].sort(),
|
|
830
|
+
attributionSourceIds: [...new Set(attributionSourceIds)].sort()
|
|
831
|
+
};
|
|
832
|
+
return createHash("sha256").update(JSON.stringify(scope)).digest("base64url");
|
|
833
|
+
}
|
|
834
|
+
function invalidLinearCursor() {
|
|
835
|
+
return Object.assign(/* @__PURE__ */ new Error("Linear cursor is invalid or stale."), { code: "invalid_cursor" });
|
|
836
|
+
}
|
|
837
|
+
function isCursorValue(value) {
|
|
838
|
+
return value === null || typeof value === "string";
|
|
839
|
+
}
|
|
840
|
+
function decodeSelfManagedListCursor(cursor, sourceIds, attributionSourceIds) {
|
|
841
|
+
const { projectIds, teamIds } = splitSelfManagedSourceIds(sourceIds);
|
|
842
|
+
if (!cursor) return {
|
|
843
|
+
projects: projectIds.length > 0 ? void 0 : null,
|
|
844
|
+
teams: teamIds.length > 0 ? void 0 : null
|
|
845
|
+
};
|
|
846
|
+
try {
|
|
847
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
848
|
+
if (parsed.v !== 1 || parsed.sourceSet !== linearSourceSetFingerprint(sourceIds, attributionSourceIds) || !isCursorValue(parsed.projects) || !isCursorValue(parsed.teams)) throw invalidLinearCursor();
|
|
849
|
+
return {
|
|
850
|
+
projects: parsed.projects,
|
|
851
|
+
teams: parsed.teams
|
|
852
|
+
};
|
|
853
|
+
} catch {
|
|
854
|
+
throw invalidLinearCursor();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function encodeSelfManagedListCursor(state, sourceIds, attributionSourceIds) {
|
|
858
|
+
if (state.projects === null && state.teams === null) return null;
|
|
859
|
+
return Buffer.from(JSON.stringify({
|
|
860
|
+
v: 1,
|
|
861
|
+
sourceSet: linearSourceSetFingerprint(sourceIds, attributionSourceIds),
|
|
862
|
+
...state
|
|
863
|
+
})).toString("base64url");
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Deduplicate issues that surfaced from both a selected team and one of its
|
|
867
|
+
* selected projects. Keeps one entry per Linear issue UUID.
|
|
868
|
+
*
|
|
869
|
+
* Tie-breaker — MOST-SPECIFIC-WINS: an issue in a selected project is
|
|
870
|
+
* attributed to that project source (routes to the project's board); the team
|
|
871
|
+
* source covers only the remainder (projectless issues and issues in projects
|
|
872
|
+
* that were not separately selected). Change the comparison here to change the
|
|
873
|
+
* routing policy — this is the single precedence point for the self-managed
|
|
874
|
+
* integration, mirroring the platform-backed one.
|
|
875
|
+
*/
|
|
876
|
+
function dedupeSelfManagedIssues(issues) {
|
|
877
|
+
const isProjectSource = (sourceId) => typeof sourceId === "string" && sourceId.length > 0 && !isSelfManagedTeamSourceId(sourceId);
|
|
878
|
+
const winners = /* @__PURE__ */ new Map();
|
|
879
|
+
for (const issue of issues) {
|
|
880
|
+
const existing = winners.get(issue.id);
|
|
881
|
+
if (!existing) {
|
|
882
|
+
winners.set(issue.id, issue);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (isProjectSource(issue.sourceId) && !isProjectSource(existing.sourceId)) winners.set(issue.id, issue);
|
|
886
|
+
}
|
|
887
|
+
const seen = /* @__PURE__ */ new Set();
|
|
888
|
+
const ordered = [];
|
|
889
|
+
for (const issue of issues) {
|
|
890
|
+
if (seen.has(issue.id)) continue;
|
|
891
|
+
seen.add(issue.id);
|
|
892
|
+
ordered.push(winners.get(issue.id));
|
|
893
|
+
}
|
|
894
|
+
return ordered;
|
|
895
|
+
}
|
|
676
896
|
function linearIssueToIntakeIssue(issue) {
|
|
677
897
|
return {
|
|
678
898
|
id: issue.id,
|