@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1,242 +1,239 @@
1
- // src/integrations/linear/routes.ts
1
+ import { LinearReauthRequiredError } from "./integration.js";
2
2
  import { registerApiRoute } from "@mastra/core/server";
3
-
4
- // src/integrations/linear/agent-tools.ts
5
- import { createTool } from "@mastra/core/tools";
6
- import { z } from "zod";
7
-
8
- // src/integrations/linear/integration.ts
9
- var LinearReauthRequiredError = class extends Error {
10
- constructor() {
11
- super("Linear authorization expired. Reconnect Linear to keep syncing intake issues.");
12
- }
13
- };
14
-
15
- // src/integrations/linear/routes.ts
16
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+ //#region src/integrations/linear/routes.ts
4
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ /** Erase a route handler's path-parameterized context to a plain `Context`. */
17
6
  function loose(c) {
18
- return c;
7
+ return c;
19
8
  }
9
+ /**
10
+ * Resolve the org-scoped tenant for a Linear request. The connection is
11
+ * org-owned, so it requires both a signed-in user and an organization — same
12
+ * tenancy rules as the GitHub routes.
13
+ */
20
14
  async function resolveOrgTenant(c, auth) {
21
- await auth.ensureUser(c);
22
- const tenant = auth.tenant(c);
23
- if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
24
- if (!tenant.orgId) {
25
- return {
26
- response: c.json(
27
- {
28
- error: "organization_required",
29
- message: "Linear intake requires an organization. Personal accounts cannot connect Linear."
30
- },
31
- 403
32
- )
33
- };
34
- }
35
- return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };
15
+ await auth.ensureUser(c);
16
+ const tenant = auth.tenant(c);
17
+ if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
18
+ if (!tenant.orgId) return { response: c.json({
19
+ error: "organization_required",
20
+ message: "Linear intake requires an organization. Personal accounts cannot connect Linear."
21
+ }, 403) };
22
+ return { tenant: {
23
+ orgId: tenant.orgId,
24
+ userId: tenant.userId
25
+ } };
36
26
  }
27
+ /**
28
+ * Validate an opaque Linear pagination cursor from the query string. Cursors
29
+ * are server-issued (`pageInfo.endCursor`), so anything outside a conservative
30
+ * charset/length is rejected rather than forwarded to Linear.
31
+ */
37
32
  function parseAfterCursor(raw) {
38
- if (raw === void 0 || raw === "") return void 0;
39
- if (raw.length > 512 || !/^[\w+/=.:-]+$/.test(raw)) return null;
40
- return raw;
33
+ if (raw === void 0 || raw === "") return void 0;
34
+ if (raw.length > 512 || !/^[\w+/=.:-]+$/.test(raw)) return null;
35
+ return raw;
41
36
  }
37
+ /** Map a Linear read failure to the API response for the SPA. */
42
38
  function linearFetchError(c, err) {
43
- if (err instanceof LinearReauthRequiredError || err.status === 401) {
44
- return c.json({ error: "linear_reauth_required", message: new LinearReauthRequiredError().message }, 409);
45
- }
46
- return c.json({ error: "linear_fetch_failed", message: err instanceof Error ? err.message : String(err) }, 502);
39
+ if (err instanceof LinearReauthRequiredError || err.status === 401) return c.json({
40
+ error: "linear_reauth_required",
41
+ message: new LinearReauthRequiredError().message
42
+ }, 409);
43
+ return c.json({
44
+ error: "linear_fetch_failed",
45
+ message: err instanceof Error ? err.message : String(err)
46
+ }, 502);
47
47
  }
48
+ /**
49
+ * Build the Linear routes as Mastra `apiRoutes`. When the feature is disabled,
50
+ * returns only the `status` route so the SPA can detect the disabled state.
51
+ */
48
52
  function buildLinearRoutes(options) {
49
- const routes = [];
50
- const { linear, auth, stateSigner, intake } = options;
51
- const enabled = Boolean(linear) && auth.enabled();
52
- const diagnostics = () => ({
53
- linearAppConfigured: Boolean(linear),
54
- factoryAuthEnabled: auth.enabled(),
55
- appDbConfigured: true
56
- });
57
- routes.push(
58
- registerApiRoute("/web/linear/status", {
59
- method: "GET",
60
- requiresAuth: false,
61
- handler: async (c) => {
62
- if (!enabled || !linear || !stateSigner) {
63
- return c.json({
64
- enabled: false,
65
- connected: false,
66
- workspace: null,
67
- reason: "missing_config",
68
- diagnostics: diagnostics()
69
- });
70
- }
71
- await auth.ensureUser(loose(c));
72
- const tenant = auth.tenant(loose(c));
73
- if (!tenant) return c.json({ error: "unauthorized", reason: "auth_required" }, 401);
74
- if (!tenant.orgId) {
75
- return c.json({
76
- enabled: true,
77
- organizationRequired: true,
78
- connected: false,
79
- workspace: null,
80
- reason: "organization_required",
81
- diagnostics: diagnostics()
82
- });
83
- }
84
- const connection = await linear.loadConnection(tenant.orgId);
85
- return c.json({
86
- enabled: true,
87
- connected: Boolean(connection),
88
- workspace: connection ? { name: connection.workspaceName, urlKey: connection.workspaceUrlKey } : null,
89
- reason: connection ? "ready" : "not_connected",
90
- diagnostics: diagnostics()
91
- });
92
- }
93
- })
94
- );
95
- if (!enabled || !linear || !stateSigner || !intake) {
96
- return routes;
97
- }
98
- const redirectUri = options.redirectUri ?? `${(options.baseUrl ?? "").replace(/\/$/, "")}/auth/linear/callback`;
99
- routes.push(
100
- registerApiRoute("/auth/linear/connect", {
101
- method: "GET",
102
- requiresAuth: false,
103
- handler: async (c) => {
104
- const resolved = await resolveOrgTenant(loose(c), auth);
105
- if ("response" in resolved) return resolved.response;
106
- const state = stateSigner.sign(resolved.tenant.orgId, resolved.tenant.userId);
107
- return c.redirect(linear.buildAuthorizeUrl(state, redirectUri));
108
- }
109
- })
110
- );
111
- routes.push(
112
- registerApiRoute("/auth/linear/callback", {
113
- method: "GET",
114
- requiresAuth: false,
115
- handler: async (c) => {
116
- const resolved = await resolveOrgTenant(loose(c), auth);
117
- if ("response" in resolved) return resolved.response;
118
- const { orgId, userId } = resolved.tenant;
119
- const stateTenant = stateSigner.verify(c.req.query("state"));
120
- if (!stateTenant || stateTenant.userId !== userId || stateTenant.orgId !== orgId) {
121
- console.warn("[Linear] OAuth callback rejected: state/tenant mismatch.");
122
- return c.redirect("/?linear=error");
123
- }
124
- const code = c.req.query("code");
125
- if (!code) {
126
- return c.redirect("/?linear=error");
127
- }
128
- try {
129
- const tokens = await linear.exchangeOAuthCode(code, redirectUri);
130
- const workspace = await linear.fetchWorkspace(tokens.accessToken);
131
- await linear.upsertConnection({
132
- orgId,
133
- userId,
134
- accessToken: tokens.accessToken,
135
- refreshToken: tokens.refreshToken,
136
- expiresAt: tokens.expiresAt,
137
- scope: tokens.scope,
138
- workspaceName: workspace.name,
139
- workspaceUrlKey: workspace.urlKey
140
- });
141
- } catch (error) {
142
- console.warn(`[Linear] OAuth callback failed to persist connection for org ${orgId}.`, error);
143
- return c.redirect("/?linear=error");
144
- }
145
- return c.redirect("/?linear=connected");
146
- }
147
- })
148
- );
149
- routes.push(
150
- registerApiRoute("/web/linear/projects", {
151
- method: "GET",
152
- requiresAuth: false,
153
- handler: async (c) => {
154
- const resolved = await resolveOrgTenant(loose(c), auth);
155
- if ("response" in resolved) return resolved.response;
156
- const connection = await linear.loadConnection(resolved.tenant.orgId);
157
- if (!connection) {
158
- return c.json({ error: "linear_not_connected", message: "Connect Linear to list Linear projects." }, 409);
159
- }
160
- try {
161
- const accessToken = await linear.getFreshAccessToken(connection);
162
- const projects = await linear.listProjects(accessToken);
163
- return c.json({ projects });
164
- } catch (err) {
165
- return linearFetchError(loose(c), err);
166
- }
167
- }
168
- })
169
- );
170
- routes.push(
171
- registerApiRoute("/web/linear/issues", {
172
- method: "GET",
173
- requiresAuth: false,
174
- handler: async (c) => {
175
- const resolved = await resolveOrgTenant(loose(c), auth);
176
- if ("response" in resolved) return resolved.response;
177
- const after = parseAfterCursor(c.req.query("after"));
178
- if (after === null) return c.json({ error: "invalid_cursor" }, 400);
179
- const factoryProjectId = c.req.query("factoryProjectId");
180
- if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {
181
- return c.json({ error: "invalid_factory_project_id" }, 400);
182
- }
183
- const connection = await linear.loadConnection(resolved.tenant.orgId);
184
- if (!connection) {
185
- return c.json({ error: "linear_not_connected", message: "Connect Linear to see intake issues." }, 409);
186
- }
187
- await intake.ensureReady();
188
- const config = await intake.getConfig({
189
- orgId: resolved.tenant.orgId,
190
- userId: resolved.tenant.userId,
191
- integrationIds: ["linear"]
192
- });
193
- const selection = config.linear;
194
- if (!selection.enabled) {
195
- return c.json({ error: "linear_intake_disabled", message: "Linear intake is turned off in Settings." }, 404);
196
- }
197
- const projectIds = selection.sourceIds ?? [];
198
- if (projectIds.length === 0) {
199
- return c.json({ issues: [], nextCursor: null });
200
- }
201
- try {
202
- const accessToken = await linear.getFreshAccessToken(connection);
203
- const { issues, nextCursor } = await linear.intake.listIssues({
204
- connection: { type: "oauth", accessToken },
205
- sourceIds: projectIds,
206
- cursor: after
207
- });
208
- const issuePayload = issues.map((issue) => ({
209
- id: issue.id,
210
- identifier: issue.identifier,
211
- title: issue.title,
212
- url: issue.url,
213
- state: issue.state ?? "",
214
- stateType: issue.stateType ?? "",
215
- priorityLabel: issue.priority ?? "",
216
- assignee: issue.assignee,
217
- team: issue.source,
218
- labels: issue.labels,
219
- createdAt: issue.createdAt,
220
- updatedAt: issue.updatedAt
221
- }));
222
- if (factoryProjectId && options.ingestFactoryIssues) {
223
- await options.ingestFactoryIssues({
224
- orgId: resolved.tenant.orgId,
225
- userId: resolved.tenant.userId,
226
- factoryProjectId,
227
- issues: issuePayload
228
- });
229
- }
230
- return c.json({ issues: issuePayload, nextCursor });
231
- } catch (err) {
232
- return linearFetchError(loose(c), err);
233
- }
234
- }
235
- })
236
- );
237
- return routes;
53
+ const routes = [];
54
+ const { linear, auth, stateSigner, intake } = options;
55
+ const enabled = Boolean(linear) && auth.enabled();
56
+ const diagnostics = () => ({
57
+ linearAppConfigured: Boolean(linear),
58
+ factoryAuthEnabled: auth.enabled(),
59
+ appDbConfigured: true
60
+ });
61
+ routes.push(registerApiRoute("/web/linear/status", {
62
+ method: "GET",
63
+ requiresAuth: false,
64
+ handler: async (c) => {
65
+ if (!enabled || !linear || !stateSigner) return c.json({
66
+ enabled: false,
67
+ connected: false,
68
+ workspace: null,
69
+ reason: "missing_config",
70
+ diagnostics: diagnostics()
71
+ });
72
+ await auth.ensureUser(loose(c));
73
+ const tenant = auth.tenant(loose(c));
74
+ if (!tenant) return c.json({
75
+ error: "unauthorized",
76
+ reason: "auth_required"
77
+ }, 401);
78
+ if (!tenant.orgId) return c.json({
79
+ enabled: true,
80
+ organizationRequired: true,
81
+ connected: false,
82
+ workspace: null,
83
+ reason: "organization_required",
84
+ diagnostics: diagnostics()
85
+ });
86
+ const connection = await linear.loadConnection(tenant.orgId);
87
+ return c.json({
88
+ enabled: true,
89
+ connected: Boolean(connection),
90
+ workspace: connection ? {
91
+ name: connection.workspaceName,
92
+ urlKey: connection.workspaceUrlKey
93
+ } : null,
94
+ reason: connection ? "ready" : "not_connected",
95
+ diagnostics: diagnostics()
96
+ });
97
+ }
98
+ }));
99
+ if (!enabled || !linear || !stateSigner || !intake) return routes;
100
+ const redirectUri = options.redirectUri ?? `${(options.baseUrl ?? "").replace(/\/$/, "")}/auth/linear/callback`;
101
+ routes.push(registerApiRoute("/auth/linear/connect", {
102
+ method: "GET",
103
+ requiresAuth: false,
104
+ handler: async (c) => {
105
+ const resolved = await resolveOrgTenant(loose(c), auth);
106
+ if ("response" in resolved) return resolved.response;
107
+ const state = stateSigner.sign(resolved.tenant.orgId, resolved.tenant.userId);
108
+ return c.redirect(linear.buildAuthorizeUrl(state, redirectUri));
109
+ }
110
+ }));
111
+ routes.push(registerApiRoute("/auth/linear/callback", {
112
+ method: "GET",
113
+ requiresAuth: false,
114
+ handler: async (c) => {
115
+ const resolved = await resolveOrgTenant(loose(c), auth);
116
+ if ("response" in resolved) return resolved.response;
117
+ const { orgId, userId } = resolved.tenant;
118
+ const stateTenant = stateSigner.verify(c.req.query("state"));
119
+ if (!stateTenant || stateTenant.userId !== userId || stateTenant.orgId !== orgId) {
120
+ console.warn("[Linear] OAuth callback rejected: state/tenant mismatch.");
121
+ return c.redirect("/?linear=error");
122
+ }
123
+ const code = c.req.query("code");
124
+ if (!code) return c.redirect("/?linear=error");
125
+ try {
126
+ const tokens = await linear.exchangeOAuthCode(code, redirectUri);
127
+ const workspace = await linear.fetchWorkspace(tokens.accessToken);
128
+ await linear.upsertConnection({
129
+ orgId,
130
+ userId,
131
+ accessToken: tokens.accessToken,
132
+ refreshToken: tokens.refreshToken,
133
+ expiresAt: tokens.expiresAt,
134
+ scope: tokens.scope,
135
+ workspaceName: workspace.name,
136
+ workspaceUrlKey: workspace.urlKey
137
+ });
138
+ } catch (error) {
139
+ console.warn(`[Linear] OAuth callback failed to persist connection for org ${orgId}.`, error);
140
+ return c.redirect("/?linear=error");
141
+ }
142
+ return c.redirect("/?linear=connected");
143
+ }
144
+ }));
145
+ routes.push(registerApiRoute("/web/linear/projects", {
146
+ method: "GET",
147
+ requiresAuth: false,
148
+ handler: async (c) => {
149
+ const resolved = await resolveOrgTenant(loose(c), auth);
150
+ if ("response" in resolved) return resolved.response;
151
+ const connection = await linear.loadConnection(resolved.tenant.orgId);
152
+ if (!connection) return c.json({
153
+ error: "linear_not_connected",
154
+ message: "Connect Linear to list Linear projects."
155
+ }, 409);
156
+ try {
157
+ const accessToken = await linear.getFreshAccessToken(connection);
158
+ const projects = await linear.listProjects(accessToken);
159
+ return c.json({ projects });
160
+ } catch (err) {
161
+ return linearFetchError(loose(c), err);
162
+ }
163
+ }
164
+ }));
165
+ routes.push(registerApiRoute("/web/linear/issues", {
166
+ method: "GET",
167
+ requiresAuth: false,
168
+ handler: async (c) => {
169
+ const resolved = await resolveOrgTenant(loose(c), auth);
170
+ if ("response" in resolved) return resolved.response;
171
+ const after = parseAfterCursor(c.req.query("after"));
172
+ if (after === null) return c.json({ error: "invalid_cursor" }, 400);
173
+ const factoryProjectId = c.req.query("factoryProjectId");
174
+ if (factoryProjectId && !UUID_RE.test(factoryProjectId)) return c.json({ error: "invalid_factory_project_id" }, 400);
175
+ const connection = await linear.loadConnection(resolved.tenant.orgId);
176
+ if (!connection) return c.json({
177
+ error: "linear_not_connected",
178
+ message: "Connect Linear to see intake issues."
179
+ }, 409);
180
+ await intake.ensureReady();
181
+ const selection = (await intake.getConfig({
182
+ orgId: resolved.tenant.orgId,
183
+ userId: resolved.tenant.userId,
184
+ integrationIds: ["linear"]
185
+ })).linear;
186
+ if (!selection.enabled) return c.json({
187
+ error: "linear_intake_disabled",
188
+ message: "Linear intake is turned off in Settings."
189
+ }, 404);
190
+ const projectIds = selection.sourceIds ?? [];
191
+ if (projectIds.length === 0) return c.json({
192
+ issues: [],
193
+ nextCursor: null
194
+ });
195
+ try {
196
+ const accessToken = await linear.getFreshAccessToken(connection);
197
+ const { issues, nextCursor } = await linear.intake.listIssues({
198
+ connection: {
199
+ type: "oauth",
200
+ accessToken
201
+ },
202
+ sourceIds: projectIds,
203
+ cursor: after
204
+ });
205
+ const issuePayload = issues.map((issue) => ({
206
+ id: issue.id,
207
+ identifier: issue.identifier,
208
+ title: issue.title,
209
+ url: issue.url,
210
+ state: issue.state ?? "",
211
+ stateType: issue.stateType ?? "",
212
+ priorityLabel: issue.priority ?? "",
213
+ assignee: issue.assignee,
214
+ team: issue.source,
215
+ labels: issue.labels,
216
+ createdAt: issue.createdAt,
217
+ updatedAt: issue.updatedAt
218
+ }));
219
+ if (factoryProjectId && options.ingestFactoryIssues) await options.ingestFactoryIssues({
220
+ orgId: resolved.tenant.orgId,
221
+ userId: resolved.tenant.userId,
222
+ factoryProjectId,
223
+ issues: issuePayload
224
+ });
225
+ return c.json({
226
+ issues: issuePayload,
227
+ nextCursor
228
+ });
229
+ } catch (err) {
230
+ return linearFetchError(loose(c), err);
231
+ }
232
+ }
233
+ }));
234
+ return routes;
238
235
  }
239
- export {
240
- buildLinearRoutes
241
- };
236
+ //#endregion
237
+ export { buildLinearRoutes };
238
+
242
239
  //# sourceMappingURL=routes.js.map