@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
|
@@ -7,6 +7,7 @@ import { buildLinearRoutes } from "../../linear/routes.js";
|
|
|
7
7
|
import { buildLinearAgentTools } from "../../linear/agent-tools.js";
|
|
8
8
|
import { PlatformLinearEventWorker } from "./event-worker.js";
|
|
9
9
|
import { registerApiRoute } from "@mastra/core/server";
|
|
10
|
+
import { createHash } from "crypto";
|
|
10
11
|
//#region src/integrations/platform/linear/integration.ts
|
|
11
12
|
const API_PREFIX = "/v1/server/linear";
|
|
12
13
|
const PAGE_SIZE = 30;
|
|
@@ -34,7 +35,8 @@ var PlatformLinearIntegration = class {
|
|
|
34
35
|
intake = {
|
|
35
36
|
resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
|
|
36
37
|
listSources: async () => {
|
|
37
|
-
|
|
38
|
+
const [projectSources, teamSources] = await Promise.all([this.#listProjectSources(), this.#listTeamSources()]);
|
|
39
|
+
const projects = projectSources.map(({ workspace, project }) => ({
|
|
38
40
|
id: encodeSourceId(workspace.linearWorkspaceId, project.id),
|
|
39
41
|
name: project.name,
|
|
40
42
|
type: "project",
|
|
@@ -46,17 +48,29 @@ var PlatformLinearIntegration = class {
|
|
|
46
48
|
teams: project.teams
|
|
47
49
|
}
|
|
48
50
|
}));
|
|
51
|
+
const teams = teamSources.map(({ workspace, team }) => ({
|
|
52
|
+
id: encodeTeamSourceId(workspace.linearWorkspaceId, team.id),
|
|
53
|
+
name: team.name,
|
|
54
|
+
type: "team",
|
|
55
|
+
metadata: {
|
|
56
|
+
workspaceId: workspace.linearWorkspaceId,
|
|
57
|
+
workspaceName: workspace.linearWorkspaceName,
|
|
58
|
+
workspaceUrlKey: workspace.urlKey,
|
|
59
|
+
teamKey: team.key
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
return [...projects, ...teams];
|
|
49
63
|
},
|
|
50
|
-
listItems: async ({ sourceIds, cursor }) => {
|
|
51
|
-
const result = await this.#listIssues(sourceIds, cursor);
|
|
64
|
+
listItems: async ({ sourceIds, attributionSourceIds, cursor }) => {
|
|
65
|
+
const result = await this.#listIssues(sourceIds, cursor, void 0, attributionSourceIds);
|
|
52
66
|
return {
|
|
53
|
-
items: result.issues.map(({ issue,
|
|
67
|
+
items: result.issues.map(({ issue, sourceId, workspace }) => ({
|
|
54
68
|
source: {
|
|
55
69
|
type: "issue",
|
|
56
70
|
externalId: issue.id,
|
|
57
71
|
url: issue.url
|
|
58
72
|
},
|
|
59
|
-
sourceId
|
|
73
|
+
sourceId,
|
|
60
74
|
title: issue.title,
|
|
61
75
|
status: issue.state.name,
|
|
62
76
|
labels: issue.labels.map((label) => label.name),
|
|
@@ -65,10 +79,9 @@ var PlatformLinearIntegration = class {
|
|
|
65
79
|
updatedAt: issue.updatedAt,
|
|
66
80
|
metadata: {
|
|
67
81
|
identifier: issue.identifier,
|
|
68
|
-
workspaceId:
|
|
69
|
-
workspaceName:
|
|
70
|
-
projectId:
|
|
71
|
-
projectName: source.project.name,
|
|
82
|
+
workspaceId: workspace.linearWorkspaceId,
|
|
83
|
+
workspaceName: workspace.linearWorkspaceName,
|
|
84
|
+
projectId: issue.project?.id ?? null,
|
|
72
85
|
team: issue.team.key,
|
|
73
86
|
priority: issue.priorityLabel
|
|
74
87
|
}
|
|
@@ -76,13 +89,13 @@ var PlatformLinearIntegration = class {
|
|
|
76
89
|
nextCursor: result.nextCursor
|
|
77
90
|
};
|
|
78
91
|
},
|
|
79
|
-
listIssues: async ({ connection, sourceIds, labels, cursor }) => {
|
|
92
|
+
listIssues: async ({ connection, sourceIds, attributionSourceIds, labels, cursor }) => {
|
|
80
93
|
requireLinearConnection(connection);
|
|
81
|
-
const result = await this.#listIssues(sourceIds, cursor, labels);
|
|
94
|
+
const result = await this.#listIssues(sourceIds, cursor, labels, attributionSourceIds);
|
|
82
95
|
return {
|
|
83
|
-
issues: result.issues.map(({ issue,
|
|
96
|
+
issues: result.issues.map(({ issue, sourceId }) => ({
|
|
84
97
|
...parseIssue(issue),
|
|
85
|
-
sourceId
|
|
98
|
+
sourceId
|
|
86
99
|
})),
|
|
87
100
|
nextCursor: result.nextCursor
|
|
88
101
|
};
|
|
@@ -285,7 +298,9 @@ var PlatformLinearIntegration = class {
|
|
|
285
298
|
baseUrl: ctx.baseUrl,
|
|
286
299
|
intake: ctx.storage.intake,
|
|
287
300
|
projects: ctx.storage.projects,
|
|
288
|
-
ingestFactoryIssues: attachLinearRules(this, ctx)
|
|
301
|
+
ingestFactoryIssues: attachLinearRules(this, ctx),
|
|
302
|
+
workItems: ctx.runtime?.workItems,
|
|
303
|
+
boards: ctx.runtime?.boards
|
|
289
304
|
}).filter((route) => !route.path.startsWith("/auth/linear/"))];
|
|
290
305
|
}
|
|
291
306
|
#connectRoute(ctx) {
|
|
@@ -331,9 +346,52 @@ var PlatformLinearIntegration = class {
|
|
|
331
346
|
workspaceId: workspace.linearWorkspaceId
|
|
332
347
|
}));
|
|
333
348
|
}
|
|
334
|
-
async
|
|
349
|
+
async listTeams(_accessToken) {
|
|
350
|
+
return (await this.#listTeamSources()).map(({ workspace, team }) => ({
|
|
351
|
+
...team,
|
|
352
|
+
workspaceId: workspace.linearWorkspaceId,
|
|
353
|
+
sourceId: encodeTeamSourceId(workspace.linearWorkspaceId, team.id)
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
async fetchIssueDetail(_accessToken, idOrIdentifier, sourceIds, routedSourceIds) {
|
|
357
|
+
const located = await this.#findIssue(void 0, idOrIdentifier, sourceIds, routedSourceIds);
|
|
358
|
+
if (!located) return null;
|
|
359
|
+
const comments = await this.#loadComments(located.workspaceId, located.issue.id, located.issue.comments);
|
|
360
|
+
const issue = located.issue;
|
|
361
|
+
return {
|
|
362
|
+
id: issue.id,
|
|
363
|
+
projectId: issue.project?.id ?? null,
|
|
364
|
+
workspaceId: located.workspaceId,
|
|
365
|
+
teamId: issue.team.id,
|
|
366
|
+
identifier: issue.identifier,
|
|
367
|
+
title: issue.title,
|
|
368
|
+
description: issue.description?.trim() ? issue.description : null,
|
|
369
|
+
url: issue.url,
|
|
370
|
+
state: issue.state.name,
|
|
371
|
+
stateType: issue.state.type,
|
|
372
|
+
priorityLabel: issue.priorityLabel,
|
|
373
|
+
assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,
|
|
374
|
+
creator: issue.creator?.displayName ?? issue.creator?.name ?? null,
|
|
375
|
+
team: issue.team.key,
|
|
376
|
+
labels: issue.labels.map((label) => label.name),
|
|
377
|
+
createdAt: issue.createdAt,
|
|
378
|
+
updatedAt: issue.updatedAt,
|
|
379
|
+
comments: comments.map((comment) => ({
|
|
380
|
+
author: comment.user?.displayName ?? comment.user?.name ?? null,
|
|
381
|
+
body: comment.body,
|
|
382
|
+
createdAt: comment.createdAt
|
|
383
|
+
}))
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
sourceMatchesIssue(sourceId, issue) {
|
|
387
|
+
const source = parseSourceId(sourceId);
|
|
388
|
+
if (issue.workspaceId !== source.workspaceId) return false;
|
|
389
|
+
return source.kind === "team" ? issue.teamId === source.teamId : issue.projectId === source.projectId;
|
|
390
|
+
}
|
|
391
|
+
async #listProjectSources(workspaceIds) {
|
|
335
392
|
const workspaces = await this.#listWorkspaces();
|
|
336
|
-
|
|
393
|
+
const scopedWorkspaces = workspaceIds ? workspaces.filter((workspace) => workspaceIds.has(workspace.linearWorkspaceId)) : workspaces;
|
|
394
|
+
return (await Promise.all(scopedWorkspaces.map(async (workspace) => {
|
|
337
395
|
const projects = [];
|
|
338
396
|
let after;
|
|
339
397
|
for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {
|
|
@@ -342,6 +400,7 @@ var PlatformLinearIntegration = class {
|
|
|
342
400
|
const result = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`);
|
|
343
401
|
projects.push(...result.projects);
|
|
344
402
|
if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;
|
|
403
|
+
if (result.pageInfo.endCursor === after) throw invalidLinearCursor();
|
|
345
404
|
after = result.pageInfo.endCursor;
|
|
346
405
|
}
|
|
347
406
|
return projects.map((project) => ({
|
|
@@ -350,57 +409,133 @@ var PlatformLinearIntegration = class {
|
|
|
350
409
|
}));
|
|
351
410
|
}))).flat();
|
|
352
411
|
}
|
|
412
|
+
async #listTeamSources(workspaceIds) {
|
|
413
|
+
const workspaces = await this.#listWorkspaces();
|
|
414
|
+
const scopedWorkspaces = workspaceIds ? workspaces.filter((workspace) => workspaceIds.has(workspace.linearWorkspaceId)) : workspaces;
|
|
415
|
+
return (await Promise.all(scopedWorkspaces.map(async (workspace) => {
|
|
416
|
+
const teams = [];
|
|
417
|
+
let after;
|
|
418
|
+
for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {
|
|
419
|
+
const query = new URLSearchParams({ first: "200" });
|
|
420
|
+
if (after) query.set("after", after);
|
|
421
|
+
const result = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/teams?${query}`);
|
|
422
|
+
teams.push(...result.teams);
|
|
423
|
+
if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;
|
|
424
|
+
if (result.pageInfo.endCursor === after) throw invalidLinearCursor();
|
|
425
|
+
after = result.pageInfo.endCursor;
|
|
426
|
+
}
|
|
427
|
+
return teams.map((team) => ({
|
|
428
|
+
workspace,
|
|
429
|
+
team
|
|
430
|
+
}));
|
|
431
|
+
}))).flat();
|
|
432
|
+
}
|
|
353
433
|
async listWorkspaces() {
|
|
354
434
|
return this.#listWorkspaces();
|
|
355
435
|
}
|
|
356
436
|
async #listWorkspaces() {
|
|
357
437
|
return (await this.#client.request("GET", `${API_PREFIX}/workspaces`)).workspaces.filter((workspace) => workspace.connected);
|
|
358
438
|
}
|
|
359
|
-
async #listIssues(sourceIds, cursor, labels) {
|
|
439
|
+
async #listIssues(sourceIds, cursor, labels, attributionSourceIds = sourceIds) {
|
|
360
440
|
if (sourceIds.length === 0) return {
|
|
361
441
|
issues: [],
|
|
362
442
|
nextCursor: null
|
|
363
443
|
};
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
444
|
+
const parsedSources = sourceIds.map((sourceId) => parseSourceId(sourceId));
|
|
445
|
+
const projectWorkspaceIds = new Set(parsedSources.flatMap((source) => source.kind === "project" ? [source.workspaceId] : []));
|
|
446
|
+
const teamWorkspaceIds = new Set(parsedSources.flatMap((source) => source.kind === "team" ? [source.workspaceId] : []));
|
|
447
|
+
const [projectSources, teamSources] = await Promise.all([projectWorkspaceIds.size > 0 ? this.#listProjectSources(projectWorkspaceIds) : Promise.resolve([]), teamWorkspaceIds.size > 0 ? this.#listTeamSources(teamWorkspaceIds) : Promise.resolve([])]);
|
|
448
|
+
const projectMap = new Map(projectSources.map((source) => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]));
|
|
449
|
+
const teamMap = new Map(teamSources.map((source) => [encodeTeamSourceId(source.workspace.linearWorkspaceId, source.team.id), source]));
|
|
450
|
+
const descriptors = [];
|
|
451
|
+
const selectedProjectIdsByWorkspace = /* @__PURE__ */ new Map();
|
|
452
|
+
for (const attributionSourceId of attributionSourceIds) {
|
|
453
|
+
const source = parseSourceId(attributionSourceId);
|
|
454
|
+
if (source.kind !== "project") continue;
|
|
455
|
+
const selectedProjectIds = selectedProjectIdsByWorkspace.get(source.workspaceId) ?? /* @__PURE__ */ new Set();
|
|
456
|
+
selectedProjectIds.add(source.projectId);
|
|
457
|
+
selectedProjectIdsByWorkspace.set(source.workspaceId, selectedProjectIds);
|
|
458
|
+
}
|
|
459
|
+
for (const sourceId of sourceIds) {
|
|
460
|
+
const projectSource = projectMap.get(sourceId);
|
|
461
|
+
if (projectSource) {
|
|
462
|
+
const query = new URLSearchParams({
|
|
463
|
+
first: String(PAGE_SIZE),
|
|
464
|
+
projectIds: projectSource.project.id,
|
|
465
|
+
stateType: "triage,backlog,unstarted,started",
|
|
466
|
+
orderBy: "updatedAt"
|
|
467
|
+
});
|
|
468
|
+
const workspaceId = projectSource.workspace.linearWorkspaceId;
|
|
469
|
+
const selectedProjectIds = selectedProjectIdsByWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Set();
|
|
470
|
+
selectedProjectIds.add(projectSource.project.id);
|
|
471
|
+
selectedProjectIdsByWorkspace.set(workspaceId, selectedProjectIds);
|
|
472
|
+
descriptors.push({
|
|
473
|
+
kind: "project",
|
|
474
|
+
sourceId,
|
|
475
|
+
workspace: projectSource.workspace,
|
|
476
|
+
query
|
|
477
|
+
});
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
const teamSource = teamMap.get(sourceId);
|
|
481
|
+
if (teamSource) {
|
|
482
|
+
const query = new URLSearchParams({
|
|
483
|
+
first: String(PAGE_SIZE),
|
|
484
|
+
teamId: teamSource.team.id,
|
|
485
|
+
stateType: "triage,backlog,unstarted,started",
|
|
486
|
+
orderBy: "updatedAt"
|
|
487
|
+
});
|
|
488
|
+
descriptors.push({
|
|
489
|
+
kind: "team",
|
|
490
|
+
sourceId,
|
|
491
|
+
workspace: teamSource.workspace,
|
|
492
|
+
query
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const cursors = decodeCursor(cursor, sourceIds, attributionSourceIds);
|
|
368
497
|
const normalizedLabels = normalizeLabels(labels);
|
|
369
498
|
const nextState = {};
|
|
370
499
|
let hasNextPage = false;
|
|
371
500
|
return {
|
|
372
|
-
issues: (await Promise.all(
|
|
373
|
-
const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);
|
|
501
|
+
issues: dedupeIssuesBySource((await Promise.all(descriptors.map(async ({ kind, sourceId, workspace, query }) => {
|
|
374
502
|
if (cursors[sourceId] === null) {
|
|
375
503
|
nextState[sourceId] = null;
|
|
376
504
|
return [];
|
|
377
505
|
}
|
|
378
|
-
const query = new URLSearchParams({
|
|
379
|
-
first: String(PAGE_SIZE),
|
|
380
|
-
projectIds: source.project.id,
|
|
381
|
-
stateType: "triage,backlog,unstarted,started",
|
|
382
|
-
orderBy: "updatedAt"
|
|
383
|
-
});
|
|
384
506
|
const after = cursors[sourceId];
|
|
385
507
|
if (after) query.set("after", after);
|
|
386
|
-
const result = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(
|
|
508
|
+
const result = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/issues?${query}`);
|
|
387
509
|
const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;
|
|
510
|
+
if (next !== null && next === after) throw invalidLinearCursor();
|
|
388
511
|
nextState[sourceId] = next;
|
|
389
512
|
hasNextPage ||= next !== null;
|
|
390
|
-
return result.issues.filter((issue) => normalizedLabels.length === 0 || issue.labels.some((label) => normalizedLabels.includes(label.name))).map((issue) => ({
|
|
513
|
+
return result.issues.filter((issue) => normalizedLabels.length === 0 || issue.labels.some((label) => normalizedLabels.includes(label.name))).filter((issue) => kind === "project" || issue.project === null || !selectedProjectIdsByWorkspace.get(workspace.linearWorkspaceId)?.has(issue.project.id)).map((issue) => ({
|
|
391
514
|
issue,
|
|
392
|
-
|
|
515
|
+
sourceId,
|
|
516
|
+
workspace
|
|
393
517
|
}));
|
|
394
|
-
}))).flat(),
|
|
395
|
-
nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null
|
|
518
|
+
}))).flat(), sourceIds),
|
|
519
|
+
nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds, attributionSourceIds) : null
|
|
396
520
|
};
|
|
397
521
|
}
|
|
398
|
-
async #findIssue(sourceId, issueId) {
|
|
399
|
-
const
|
|
522
|
+
async #findIssue(sourceId, issueId, sourceIds, routedSourceIds) {
|
|
523
|
+
const scopedSources = (sourceIds ?? (sourceId ? [sourceId] : void 0))?.map((sourceKey) => ({
|
|
524
|
+
sourceKey,
|
|
525
|
+
source: parseSourceId(sourceKey)
|
|
526
|
+
}));
|
|
527
|
+
const routedSourceSet = routedSourceIds ? new Set(routedSourceIds) : void 0;
|
|
528
|
+
const workspaceIds = routedSourceIds ? [...new Set(routedSourceIds.map((sourceKey) => parseSourceId(sourceKey).workspaceId))] : scopedSources ? [...new Set(scopedSources.map(({ source }) => source.workspaceId))] : await this.#candidateWorkspaceIds(void 0);
|
|
400
529
|
for (const workspaceId of workspaceIds) try {
|
|
530
|
+
const issue = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`);
|
|
531
|
+
if (scopedSources) {
|
|
532
|
+
const matching = scopedSources.filter(({ source }) => source.workspaceId === workspaceId && (source.kind === "team" ? issue.team.id === source.teamId : issue.project?.id === source.projectId));
|
|
533
|
+
const winner = matching.find(({ source }) => source.kind === "project") ?? matching[0];
|
|
534
|
+
if (!winner || routedSourceSet && !routedSourceSet.has(winner.sourceKey)) continue;
|
|
535
|
+
}
|
|
401
536
|
return {
|
|
402
537
|
workspaceId,
|
|
403
|
-
issue
|
|
538
|
+
issue
|
|
404
539
|
};
|
|
405
540
|
} catch (error) {
|
|
406
541
|
if (!isNotFound(error)) throw error;
|
|
@@ -427,7 +562,7 @@ var PlatformLinearIntegration = class {
|
|
|
427
562
|
return result.workflowStates;
|
|
428
563
|
}
|
|
429
564
|
async #candidateWorkspaceIds(sourceId) {
|
|
430
|
-
if (sourceId) return [
|
|
565
|
+
if (sourceId) return [parseSourceId(sourceId).workspaceId];
|
|
431
566
|
return (await this.#listWorkspaces()).map((workspace) => workspace.linearWorkspaceId);
|
|
432
567
|
}
|
|
433
568
|
async #loadComments(workspaceId, issueId, embedded) {
|
|
@@ -437,6 +572,7 @@ var PlatformLinearIntegration = class {
|
|
|
437
572
|
while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {
|
|
438
573
|
const result = await this.#client.request("GET", `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`);
|
|
439
574
|
comments.push(...result.comments);
|
|
575
|
+
if (result.pageInfo.hasNextPage && result.pageInfo.endCursor === pageInfo.endCursor) throw invalidLinearCursor();
|
|
440
576
|
pageInfo = result.pageInfo;
|
|
441
577
|
page += 1;
|
|
442
578
|
}
|
|
@@ -473,6 +609,43 @@ function parseIssueDetail(issue, comments) {
|
|
|
473
609
|
}))
|
|
474
610
|
};
|
|
475
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Deduplicate issues that surfaced from more than one selected source (a team
|
|
614
|
+
* source and one of its projects both select the same issue). Keeps one entry
|
|
615
|
+
* per Linear issue UUID.
|
|
616
|
+
*
|
|
617
|
+
* Tie-breaker — MOST-SPECIFIC-WINS: a project source beats a team source, so an
|
|
618
|
+
* issue that belongs to a selected project is attributed to that project's
|
|
619
|
+
* source (and therefore routes to the project's board). The team source covers
|
|
620
|
+
* only the remainder — projectless issues and issues in projects that were not
|
|
621
|
+
* separately selected. `selectionOrder` breaks a same-kind tie deterministically
|
|
622
|
+
* by the order the sources were selected.
|
|
623
|
+
*
|
|
624
|
+
* This function is the single precedence point; change the comparison here to
|
|
625
|
+
* change the routing policy.
|
|
626
|
+
*/
|
|
627
|
+
function dedupeIssuesBySource(entries, selectionOrder) {
|
|
628
|
+
const rank = new Map(selectionOrder.map((id, index) => [id, index]));
|
|
629
|
+
const specificity = (sourceId) => parseSourceId(sourceId).kind === "project" ? 0 : 1;
|
|
630
|
+
const winners = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const entry of entries) {
|
|
632
|
+
const existing = winners.get(entry.issue.id);
|
|
633
|
+
if (!existing) {
|
|
634
|
+
winners.set(entry.issue.id, entry);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const bySpecificity = specificity(entry.sourceId) - specificity(existing.sourceId);
|
|
638
|
+
if (bySpecificity < 0 || bySpecificity === 0 && (rank.get(entry.sourceId) ?? Number.MAX_SAFE_INTEGER) < (rank.get(existing.sourceId) ?? Number.MAX_SAFE_INTEGER)) winners.set(entry.issue.id, entry);
|
|
639
|
+
}
|
|
640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
641
|
+
const ordered = [];
|
|
642
|
+
for (const entry of entries) {
|
|
643
|
+
if (seen.has(entry.issue.id)) continue;
|
|
644
|
+
seen.add(entry.issue.id);
|
|
645
|
+
ordered.push(winners.get(entry.issue.id));
|
|
646
|
+
}
|
|
647
|
+
return ordered;
|
|
648
|
+
}
|
|
476
649
|
function encodeSourceId(workspaceId, projectId) {
|
|
477
650
|
return `linear-project:${Buffer.from(JSON.stringify({
|
|
478
651
|
workspaceId,
|
|
@@ -493,23 +666,73 @@ function decodeSourceId(sourceId) {
|
|
|
493
666
|
throw new Error("Linear project source id is invalid.");
|
|
494
667
|
}
|
|
495
668
|
}
|
|
669
|
+
const TEAM_SOURCE_PREFIX = "linear-team:";
|
|
670
|
+
function encodeTeamSourceId(workspaceId, teamId) {
|
|
671
|
+
return `${TEAM_SOURCE_PREFIX}${Buffer.from(JSON.stringify({
|
|
672
|
+
workspaceId,
|
|
673
|
+
teamId
|
|
674
|
+
})).toString("base64url")}`;
|
|
675
|
+
}
|
|
676
|
+
function decodeTeamSourceId(sourceId) {
|
|
677
|
+
if (!sourceId.startsWith(TEAM_SOURCE_PREFIX)) throw new Error("Linear team source id is invalid.");
|
|
678
|
+
try {
|
|
679
|
+
const parsed = JSON.parse(Buffer.from(sourceId.slice(12), "base64url").toString("utf8"));
|
|
680
|
+
if (typeof parsed.workspaceId !== "string" || !parsed.workspaceId) throw new Error();
|
|
681
|
+
if (typeof parsed.teamId !== "string" || !parsed.teamId) throw new Error();
|
|
682
|
+
return {
|
|
683
|
+
workspaceId: parsed.workspaceId,
|
|
684
|
+
teamId: parsed.teamId
|
|
685
|
+
};
|
|
686
|
+
} catch {
|
|
687
|
+
throw new Error("Linear team source id is invalid.");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/** Discriminate any Linear source id by its prefix and decode it. */
|
|
691
|
+
function parseSourceId(sourceId) {
|
|
692
|
+
if (sourceId.startsWith(TEAM_SOURCE_PREFIX)) return {
|
|
693
|
+
kind: "team",
|
|
694
|
+
...decodeTeamSourceId(sourceId)
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
kind: "project",
|
|
698
|
+
...decodeSourceId(sourceId)
|
|
699
|
+
};
|
|
700
|
+
}
|
|
496
701
|
function normalizeLabels(labels) {
|
|
497
702
|
return [...new Set((labels ?? []).map((label) => label.trim()).filter(Boolean))];
|
|
498
703
|
}
|
|
499
|
-
function
|
|
704
|
+
function canonicalSourceIds(sourceIds) {
|
|
705
|
+
return [...new Set(sourceIds)].sort();
|
|
706
|
+
}
|
|
707
|
+
function linearSourceSetFingerprint(sourceIds, attributionSourceIds) {
|
|
708
|
+
const scope = {
|
|
709
|
+
sourceIds: canonicalSourceIds(sourceIds),
|
|
710
|
+
attributionSourceIds: canonicalSourceIds(attributionSourceIds)
|
|
711
|
+
};
|
|
712
|
+
return createHash("sha256").update(JSON.stringify(scope)).digest("base64url");
|
|
713
|
+
}
|
|
714
|
+
function invalidLinearCursor() {
|
|
715
|
+
return Object.assign(/* @__PURE__ */ new Error("Linear cursor is invalid or stale."), { code: "invalid_cursor" });
|
|
716
|
+
}
|
|
717
|
+
function decodeCursor(cursor, sourceIds, attributionSourceIds) {
|
|
500
718
|
if (!cursor) return {};
|
|
501
|
-
if (sourceIds.length === 1) return { [sourceIds[0]]: cursor };
|
|
502
719
|
try {
|
|
503
|
-
const parsed = JSON.parse(cursor);
|
|
504
|
-
|
|
505
|
-
|
|
720
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
721
|
+
const canonical = canonicalSourceIds(sourceIds);
|
|
722
|
+
if (parsed.v !== 1 || parsed.sourceSet !== linearSourceSetFingerprint(sourceIds, attributionSourceIds) || !Array.isArray(parsed.cursors) || parsed.cursors.length !== canonical.length || !parsed.cursors.every((value) => value === null || typeof value === "string")) throw invalidLinearCursor();
|
|
723
|
+
return Object.fromEntries(canonical.map((sourceId, index) => [sourceId, parsed.cursors[index]]));
|
|
506
724
|
} catch {
|
|
507
|
-
throw
|
|
725
|
+
throw invalidLinearCursor();
|
|
508
726
|
}
|
|
509
727
|
}
|
|
510
|
-
function encodeCursor(state, sourceIds) {
|
|
511
|
-
|
|
512
|
-
|
|
728
|
+
function encodeCursor(state, sourceIds, attributionSourceIds) {
|
|
729
|
+
const canonical = canonicalSourceIds(sourceIds);
|
|
730
|
+
const cursor = {
|
|
731
|
+
v: 1,
|
|
732
|
+
sourceSet: linearSourceSetFingerprint(sourceIds, attributionSourceIds),
|
|
733
|
+
cursors: canonical.map((sourceId) => state[sourceId] ?? null)
|
|
734
|
+
};
|
|
735
|
+
return Buffer.from(JSON.stringify(cursor)).toString("base64url");
|
|
513
736
|
}
|
|
514
737
|
function requireLinearConnection(connection) {
|
|
515
738
|
if (connection.type !== "oauth") throw new Error("Linear capabilities require an OAuth connection.");
|
|
@@ -525,6 +748,6 @@ function optionalPositiveIntegerEnv(name) {
|
|
|
525
748
|
return parsed;
|
|
526
749
|
}
|
|
527
750
|
//#endregion
|
|
528
|
-
export { PlatformLinearIntegration };
|
|
751
|
+
export { PlatformLinearIntegration, decodeTeamSourceId, encodeSourceId, encodeTeamSourceId, parseSourceId };
|
|
529
752
|
|
|
530
753
|
//# sourceMappingURL=integration.js.map
|