@mastra/factory 0.2.1-alpha.2 → 0.2.1-alpha.4

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 (59) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +3462 -3583
  4. package/dist/factory.js.map +1 -1
  5. package/dist/index.js +3462 -3583
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/base.d.ts +13 -25
  8. package/dist/integrations/base.d.ts.map +1 -1
  9. package/dist/integrations/github/integration.d.ts.map +1 -1
  10. package/dist/integrations/github/integration.js +625 -217
  11. package/dist/integrations/github/integration.js.map +1 -1
  12. package/dist/integrations/github/routes.d.ts +1 -4
  13. package/dist/integrations/github/routes.d.ts.map +1 -1
  14. package/dist/integrations/github/routes.js +113 -203
  15. package/dist/integrations/github/routes.js.map +1 -1
  16. package/dist/integrations/github/rules.d.ts +30 -0
  17. package/dist/integrations/github/rules.d.ts.map +1 -0
  18. package/dist/{rules/github-service.js → integrations/github/rules.js} +27 -8
  19. package/dist/integrations/github/rules.js.map +1 -0
  20. package/dist/integrations/linear/agent-tools.js.map +1 -1
  21. package/dist/integrations/linear/integration.d.ts.map +1 -1
  22. package/dist/integrations/linear/integration.js +352 -3
  23. package/dist/integrations/linear/integration.js.map +1 -1
  24. package/dist/integrations/linear/routes.d.ts +2 -2
  25. package/dist/integrations/linear/routes.d.ts.map +1 -1
  26. package/dist/integrations/linear/routes.js +2 -2
  27. package/dist/integrations/linear/routes.js.map +1 -1
  28. package/dist/integrations/linear/rules.d.ts +42 -0
  29. package/dist/integrations/linear/rules.d.ts.map +1 -0
  30. package/dist/{rules/linear-service.js → integrations/linear/rules.js} +14 -4
  31. package/dist/integrations/linear/rules.js.map +1 -0
  32. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  33. package/dist/integrations/platform/github/integration.js +626 -218
  34. package/dist/integrations/platform/github/integration.js.map +1 -1
  35. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  36. package/dist/integrations/platform/linear/integration.js +353 -3
  37. package/dist/integrations/platform/linear/integration.js.map +1 -1
  38. package/dist/routes/oauth.js +6 -39
  39. package/dist/routes/oauth.js.map +1 -1
  40. package/dist/routes/surface.d.ts +3 -1
  41. package/dist/routes/surface.d.ts.map +1 -1
  42. package/dist/routes/surface.js +230 -619
  43. package/dist/routes/surface.js.map +1 -1
  44. package/dist/routes/work-items.js.map +1 -1
  45. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  46. package/dist/storage/domains/work-items/base.js +74 -75
  47. package/dist/storage/domains/work-items/base.js.map +1 -1
  48. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  49. package/package.json +5 -5
  50. package/dist/integrations/github/project-lock.d.ts +0 -100
  51. package/dist/integrations/github/project-lock.d.ts.map +0 -1
  52. package/dist/integrations/github/project-lock.js +0 -103
  53. package/dist/integrations/github/project-lock.js.map +0 -1
  54. package/dist/rules/github-service.d.ts +0 -29
  55. package/dist/rules/github-service.d.ts.map +0 -1
  56. package/dist/rules/github-service.js.map +0 -1
  57. package/dist/rules/linear-service.d.ts +0 -27
  58. package/dist/rules/linear-service.d.ts.map +0 -1
  59. package/dist/rules/linear-service.js.map +0 -1
@@ -29,10 +29,7 @@ export interface MountGithubRoutesOptions {
29
29
  * respond 503.
30
30
  */
31
31
  fleet: SandboxFleet;
32
- /**
33
- * Factory storage backend. Supplies the cross-replica `withDistributedLock`
34
- * capability for git write routes and the `appDbConfigured` diagnostic.
35
- */
32
+ /** Factory storage backend used for the `appDbConfigured` diagnostic. */
36
33
  storage?: FactoryStorage;
37
34
  /**
38
35
  * The GitHub App integration the handlers operate on (Octokit access, token
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AASrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAmB1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgB/G,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,iEAAiE;IACjE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC3F,6EAA6E;IAC7E,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AAuPD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CA4oB/E"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AASrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAwC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgB/G,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,iEAAiE;IACjE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC3F,6EAA6E;IAC7E,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AAuPD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CA4oB/E"}
@@ -90,95 +90,8 @@ async function clearGithubPat(storage, orgId, kind = "default") {
90
90
  await storage.settings.save(orgId, PAT_SETTINGS_USER_ID, rest);
91
91
  }
92
92
 
93
- // src/integrations/github/project-lock.ts
94
- import { createHash } from "crypto";
95
- var inProcessLocks = /* @__PURE__ */ new Map();
96
- var DEFAULT_PROJECT_LOCK_TIMEOUT_MS = 6e4;
97
- var ProjectLockTimeoutError = class extends Error {
98
- key;
99
- timeoutMs;
100
- constructor(key, timeoutMs) {
101
- super(`Project lock critical section for "${key}" exceeded ${timeoutMs}ms`);
102
- this.name = "ProjectLockTimeoutError";
103
- this.key = key;
104
- this.timeoutMs = timeoutMs;
105
- }
106
- };
107
- async function runWithTimeout(key, timeoutMs, fn) {
108
- const controller = new AbortController();
109
- const timer = setTimeout(() => controller.abort(), timeoutMs);
110
- const abortError = new Promise((_, reject) => {
111
- controller.signal.addEventListener("abort", () => reject(new ProjectLockTimeoutError(key, timeoutMs)), {
112
- once: true
113
- });
114
- });
115
- const work = fn(controller.signal);
116
- work.catch(() => {
117
- });
118
- try {
119
- return await Promise.race([work, abortError]);
120
- } finally {
121
- clearTimeout(timer);
122
- }
123
- }
124
- function hashKey(key) {
125
- const digest = createHash("sha256").update(key).digest();
126
- const a = digest.readInt32BE(0);
127
- const b = digest.readInt32BE(4);
128
- return [a, b];
129
- }
130
- function withProjectLock(options) {
131
- const { key, storage, fn, pool, timeoutMs = DEFAULT_PROJECT_LOCK_TIMEOUT_MS } = options;
132
- const prev = inProcessLocks.get(key) ?? Promise.resolve();
133
- const run = () => withDbAdvisoryLock({ key, storage, fn, pool, timeoutMs });
134
- const next = prev.then(run, run);
135
- const tail = next.then(
136
- () => void 0,
137
- () => void 0
138
- );
139
- inProcessLocks.set(key, tail);
140
- void tail.then(() => {
141
- if (inProcessLocks.get(key) === tail) {
142
- inProcessLocks.delete(key);
143
- }
144
- });
145
- return next;
146
- }
147
- async function withDbAdvisoryLock(options) {
148
- const { key, storage, fn, pool, timeoutMs = DEFAULT_PROJECT_LOCK_TIMEOUT_MS } = options;
149
- if (process.env.MASTRACODE_DISTRIBUTED_LOCK === "0") {
150
- return runWithTimeout(key, timeoutMs, fn);
151
- }
152
- if (pool) return advisoryLockOver(pool, key, timeoutMs, fn);
153
- if (typeof storage?.withDistributedLock !== "function") {
154
- return runWithTimeout(key, timeoutMs, fn);
155
- }
156
- return storage.withDistributedLock(key, () => runWithTimeout(key, timeoutMs, fn));
157
- }
158
- async function advisoryLockOver(pool, key, timeoutMs, fn) {
159
- const [k1, k2] = hashKey(key);
160
- const client = await pool.connect();
161
- try {
162
- await client.query("BEGIN");
163
- await client.query("SELECT pg_advisory_xact_lock($1, $2)", [k1, k2]);
164
- try {
165
- const result = await runWithTimeout(key, timeoutMs, fn);
166
- await client.query("COMMIT");
167
- return result;
168
- } catch (err) {
169
- try {
170
- await client.query("ROLLBACK");
171
- } catch {
172
- }
173
- throw err;
174
- }
175
- } finally {
176
- client.release();
177
- }
178
- }
179
-
180
93
  // src/integrations/github/sandbox.ts
181
- import { createHash as createHash2 } from "crypto";
94
+ import { createHash } from "crypto";
182
95
  function bindingStore(row, storage) {
183
96
  return {
184
97
  sandboxId: row.sandboxId,
@@ -374,7 +287,7 @@ var WorktreeError = class extends Error {
374
287
  function safeBranchDir(branch) {
375
288
  const sanitized = branch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/\/+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 100) || "work";
376
289
  if (sanitized === branch) return sanitized;
377
- const hash = createHash2("sha256").update(branch).digest("hex").slice(0, 8);
290
+ const hash = createHash("sha256").update(branch).digest("hex").slice(0, 8);
378
291
  return `${sanitized}-${hash}`;
379
292
  }
380
293
  function computeWorktreePath(repoWorkdir, branch) {
@@ -792,6 +705,20 @@ async function handleGithubWebhook(c, options) {
792
705
  }
793
706
 
794
707
  // src/integrations/github/routes.ts
708
+ var sessionOperationLocks = /* @__PURE__ */ new Map();
709
+ function withSessionOperationLock(sessionId, fn) {
710
+ const previous = sessionOperationLocks.get(sessionId) ?? Promise.resolve();
711
+ const next = previous.then(fn, fn);
712
+ const tail = next.then(
713
+ () => void 0,
714
+ () => void 0
715
+ );
716
+ sessionOperationLocks.set(sessionId, tail);
717
+ void tail.then(() => {
718
+ if (sessionOperationLocks.get(sessionId) === tail) sessionOperationLocks.delete(sessionId);
719
+ });
720
+ return next;
721
+ }
795
722
  function loose(c) {
796
723
  return c;
797
724
  }
@@ -1476,7 +1403,7 @@ function buildGithubRoutes(options) {
1476
1403
  }
1477
1404
  })
1478
1405
  );
1479
- routes.push(...buildProjectGitRoutes({ github, auth, fleet, storage, emitAudit }));
1406
+ routes.push(...buildProjectGitRoutes({ github, auth, fleet, emitAudit }));
1480
1407
  return routes;
1481
1408
  }
1482
1409
  async function loadOrgProject(options) {
@@ -1593,7 +1520,6 @@ function buildProjectGitRoutes({
1593
1520
  github,
1594
1521
  auth,
1595
1522
  fleet,
1596
- storage,
1597
1523
  emitAudit
1598
1524
  }) {
1599
1525
  return [
@@ -1714,31 +1640,27 @@ function buildProjectGitRoutes({
1714
1640
  }
1715
1641
  const { workdir, sandboxBinding } = sessionWorkspace;
1716
1642
  try {
1717
- return await withProjectLock({
1718
- key: `${project.id}:${userId}`,
1719
- storage,
1720
- fn: async () => {
1721
- const sandbox = await resolveProjectSandbox({ fleet, sandboxRow: sandboxBinding });
1722
- const result = await commitAll(
1723
- sandbox,
1724
- workdir,
1725
- body.message,
1726
- identityFromUser(await auth.ensureUser(loose(c)))
1727
- );
1728
- if (result.committed) {
1729
- await emitAudit?.({
1730
- context: loose(c),
1731
- input: {
1732
- action: "factory.git.commit",
1733
- factoryProjectId: project.factoryProjectId,
1734
- projectRepositoryId: project.id,
1735
- targets: [{ type: "session", id: sessionWorkspace.session.sessionId }],
1736
- metadata: { sessionId: sessionWorkspace.session.sessionId }
1737
- }
1738
- });
1739
- }
1740
- return c.json({ committed: result.committed });
1643
+ return await withSessionOperationLock(sessionWorkspace.session.sessionId, async () => {
1644
+ const sandbox = await resolveProjectSandbox({ fleet, sandboxRow: sandboxBinding });
1645
+ const result = await commitAll(
1646
+ sandbox,
1647
+ workdir,
1648
+ body.message,
1649
+ identityFromUser(await auth.ensureUser(loose(c)))
1650
+ );
1651
+ if (result.committed) {
1652
+ await emitAudit?.({
1653
+ context: loose(c),
1654
+ input: {
1655
+ action: "factory.git.commit",
1656
+ factoryProjectId: project.factoryProjectId,
1657
+ projectRepositoryId: project.id,
1658
+ targets: [{ type: "session", id: sessionWorkspace.session.sessionId }],
1659
+ metadata: { sessionId: sessionWorkspace.session.sessionId }
1660
+ }
1661
+ });
1741
1662
  }
1663
+ return c.json({ committed: result.committed });
1742
1664
  });
1743
1665
  } catch (err) {
1744
1666
  return gitErrorResponse(loose(c), err);
@@ -1769,29 +1691,25 @@ function buildProjectGitRoutes({
1769
1691
  }
1770
1692
  const { workdir, sandboxBinding } = sessionWorkspace;
1771
1693
  try {
1772
- return await withProjectLock({
1773
- key: `${project.id}:${userId}`,
1774
- storage,
1775
- fn: async () => {
1776
- const sandbox = await resolveProjectSandbox({ fleet, sandboxRow: sandboxBinding });
1777
- const access = await github.versionControl.getRepositoryAccess({
1778
- orgId,
1779
- repositoryId: project.repository.id
1780
- });
1781
- if (!access.authorization) throw new Error("Repository access did not include a bearer token.");
1782
- await pushBranch(sandbox, workdir, branch, access.authorization.token, project.repository.slug);
1783
- await emitAudit?.({
1784
- context: loose(c),
1785
- input: {
1786
- action: "factory.git.push",
1787
- factoryProjectId: project.factoryProjectId,
1788
- projectRepositoryId: project.id,
1789
- targets: [{ type: "branch", id: branch }],
1790
- metadata: { branch, sessionId: sessionWorkspace.session.sessionId }
1791
- }
1792
- });
1793
- return c.json({ pushed: true, branch });
1794
- }
1694
+ return await withSessionOperationLock(sessionWorkspace.session.sessionId, async () => {
1695
+ const sandbox = await resolveProjectSandbox({ fleet, sandboxRow: sandboxBinding });
1696
+ const access = await github.versionControl.getRepositoryAccess({
1697
+ orgId,
1698
+ repositoryId: project.repository.id
1699
+ });
1700
+ if (!access.authorization) throw new Error("Repository access did not include a bearer token.");
1701
+ await pushBranch(sandbox, workdir, branch, access.authorization.token, project.repository.slug);
1702
+ await emitAudit?.({
1703
+ context: loose(c),
1704
+ input: {
1705
+ action: "factory.git.push",
1706
+ factoryProjectId: project.factoryProjectId,
1707
+ projectRepositoryId: project.id,
1708
+ targets: [{ type: "branch", id: branch }],
1709
+ metadata: { branch, sessionId: sessionWorkspace.session.sessionId }
1710
+ }
1711
+ });
1712
+ return c.json({ pushed: true, branch });
1795
1713
  });
1796
1714
  } catch (err) {
1797
1715
  return gitErrorResponse(loose(c), err);
@@ -1833,60 +1751,56 @@ function buildProjectGitRoutes({
1833
1751
  return c.json({ error: "Invalid sessionId" }, 400);
1834
1752
  }
1835
1753
  try {
1836
- return await withProjectLock({
1837
- key: `${project.id}:${userId}`,
1838
- storage,
1839
- fn: async () => {
1840
- const result = await github.versionControl.createPullRequest({
1841
- connection: {
1842
- type: "app-installation",
1843
- installationId: Number(project.installation.externalId)
1844
- },
1845
- sourceId: project.repository.slug,
1846
- baseBranch: base,
1847
- headBranch: head,
1848
- title,
1849
- body: prBody,
1850
- actingUserId: userId
1851
- });
1852
- await emitAudit?.({
1853
- context: loose(c),
1854
- input: {
1855
- action: "factory.git.pr_opened",
1856
- factoryProjectId: project.factoryProjectId,
1754
+ return await withSessionOperationLock(sessionWorkspace.session.sessionId, async () => {
1755
+ const result = await github.versionControl.createPullRequest({
1756
+ connection: {
1757
+ type: "app-installation",
1758
+ installationId: Number(project.installation.externalId)
1759
+ },
1760
+ sourceId: project.repository.slug,
1761
+ baseBranch: base,
1762
+ headBranch: head,
1763
+ title,
1764
+ body: prBody,
1765
+ actingUserId: userId
1766
+ });
1767
+ await emitAudit?.({
1768
+ context: loose(c),
1769
+ input: {
1770
+ action: "factory.git.pr_opened",
1771
+ factoryProjectId: project.factoryProjectId,
1772
+ projectRepositoryId: project.id,
1773
+ targets: [{ type: "pull_request", id: result.url, name: title }],
1774
+ metadata: { branch: head, base, url: result.url }
1775
+ }
1776
+ });
1777
+ const pullRequestNumber = pullRequestNumberFromUrl(result.url, project.repository.slug);
1778
+ if (pullRequestNumber) {
1779
+ const sessionId = sessionWorkspace.session.sessionId;
1780
+ await subscribeToPullRequest(
1781
+ {
1782
+ orgId,
1783
+ installationExternalId: project.installation.externalId,
1857
1784
  projectRepositoryId: project.id,
1858
- targets: [{ type: "pull_request", id: result.url, name: title }],
1859
- metadata: { branch: head, base, url: result.url }
1860
- }
1785
+ repositoryExternalId: project.repository.externalId,
1786
+ repositorySlug: project.repository.slug,
1787
+ changeRequestId: pullRequestNumber.toString(),
1788
+ sessionId,
1789
+ ownerId: userId,
1790
+ resourceId: sessionId,
1791
+ threadId: sessionId,
1792
+ source: "factory-pr-create",
1793
+ subscribedByUserId: userId
1794
+ },
1795
+ github.integrationStorage
1796
+ ).catch((error) => {
1797
+ console.warn(
1798
+ `[GitHub] Pull request ${result.url} was created but automatic subscription failed.`,
1799
+ error
1800
+ );
1861
1801
  });
1862
- const pullRequestNumber = pullRequestNumberFromUrl(result.url, project.repository.slug);
1863
- if (pullRequestNumber) {
1864
- const sessionId = sessionWorkspace.session.sessionId;
1865
- await subscribeToPullRequest(
1866
- {
1867
- orgId,
1868
- installationExternalId: project.installation.externalId,
1869
- projectRepositoryId: project.id,
1870
- repositoryExternalId: project.repository.externalId,
1871
- repositorySlug: project.repository.slug,
1872
- changeRequestId: pullRequestNumber.toString(),
1873
- sessionId,
1874
- ownerId: userId,
1875
- resourceId: sessionId,
1876
- threadId: sessionId,
1877
- source: "factory-pr-create",
1878
- subscribedByUserId: userId
1879
- },
1880
- github.integrationStorage
1881
- ).catch((error) => {
1882
- console.warn(
1883
- `[GitHub] Pull request ${result.url} was created but automatic subscription failed.`,
1884
- error
1885
- );
1886
- });
1887
- }
1888
- return c.json({ url: result.url });
1889
1802
  }
1803
+ return c.json({ url: result.url });
1890
1804
  });
1891
1805
  } catch (err) {
1892
1806
  return c.json(
@@ -1906,24 +1820,20 @@ function buildProjectGitRoutes({
1906
1820
  handler: async (c) => {
1907
1821
  const owned = await loadOwnedProject({ github, auth, fleet, c: loose(c) });
1908
1822
  if ("response" in owned) return owned.response;
1909
- const { userId, project, sandboxRow } = owned;
1823
+ const { sandboxRow } = owned;
1910
1824
  if (!sandboxRow.sandboxId) {
1911
1825
  return c.json({ tornDown: false });
1912
1826
  }
1913
1827
  try {
1914
- return await withProjectLock({
1915
- key: `${project.id}:${userId}`,
1916
- storage,
1917
- fn: async () => {
1918
- const sandbox = await fleet.reattachSandbox(sandboxRow.sandboxId);
1919
- await teardownProjectSandbox({
1920
- fleet,
1921
- row: sandboxRow,
1922
- storage: github.sourceControlStorage.sandboxes,
1923
- sandbox
1924
- });
1925
- return c.json({ tornDown: true });
1926
- }
1828
+ return await withSessionOperationLock(`sandbox:${sandboxRow.id}`, async () => {
1829
+ const sandbox = await fleet.reattachSandbox(sandboxRow.sandboxId);
1830
+ await teardownProjectSandbox({
1831
+ fleet,
1832
+ row: sandboxRow,
1833
+ storage: github.sourceControlStorage.sandboxes,
1834
+ sandbox
1835
+ });
1836
+ return c.json({ tornDown: true });
1927
1837
  });
1928
1838
  } catch (err) {
1929
1839
  return gitErrorResponse(loose(c), err);