@sentry/junior 0.177.0 → 0.178.0

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 (91) hide show
  1. package/dist/{agent-hooks-IYHCN7CO.js → agent-hooks-PBT4NNBV.js} +12 -12
  2. package/dist/api/conversations/archive.d.ts +5 -2
  3. package/dist/api/conversations/list.d.ts +0 -1
  4. package/dist/api/conversations/membership.d.ts +14 -0
  5. package/dist/api/conversations/projection.d.ts +2 -1
  6. package/dist/api/locations/detail.d.ts +1 -1
  7. package/dist/api/people/shared.d.ts +2 -2
  8. package/dist/api/schema/conversation.d.ts +4 -4
  9. package/dist/api/schema/location.d.ts +1 -1
  10. package/dist/api/schema/person.d.ts +1 -1
  11. package/dist/api/schema.js +1 -1
  12. package/dist/api.js +245 -166
  13. package/dist/app.js +35 -35
  14. package/dist/{catalog-runtime-2TD4CXSB.js → catalog-runtime-M7QGKPFZ.js} +5 -5
  15. package/dist/chat/agent/sandbox.d.ts +2 -0
  16. package/dist/chat/conversations/sql/participants.d.ts +1 -0
  17. package/dist/chat/conversations/store.d.ts +0 -1
  18. package/dist/chat/sandbox/errors.d.ts +4 -0
  19. package/dist/chat/sandbox/noninteractive-command.d.ts +1 -0
  20. package/dist/chat/sandbox/prepare-workspace.d.ts +27 -3
  21. package/dist/chat/sandbox/sandbox.d.ts +2 -0
  22. package/dist/chat/sandbox/session.d.ts +2 -0
  23. package/dist/chat/sandbox/snapshot/builder-sandbox.d.ts +5 -0
  24. package/dist/chat/sandbox/snapshot/install.d.ts +2 -2
  25. package/dist/chat/sandbox/snapshot/span.d.ts +3 -0
  26. package/dist/chat/sandbox/snapshot/store.d.ts +20 -17
  27. package/dist/chat/sandbox/snapshot/waiting-error.d.ts +15 -0
  28. package/dist/chat/sandbox/snapshot/workspace.d.ts +17 -0
  29. package/dist/chat/services/workspace-snapshot-wait.d.ts +27 -0
  30. package/dist/chat/workspaces/types.d.ts +1 -0
  31. package/dist/{chunk-3FWXQZJJ.js → chunk-22T3HB44.js} +1 -1
  32. package/dist/{chunk-DLTP6TUJ.js → chunk-3ZZEPLLB.js} +2 -2
  33. package/dist/{chunk-7ZFESKJ2.js → chunk-4K6VNP3S.js} +9 -9
  34. package/dist/{chunk-N4YEVOEC.js → chunk-534BO42N.js} +45 -276
  35. package/dist/{chunk-6NZRKJWU.js → chunk-6OYVH66J.js} +1 -1
  36. package/dist/{chunk-J2EWL6GJ.js → chunk-ABLODWPO.js} +1 -1
  37. package/dist/{chunk-ORMJY4RG.js → chunk-AGJ4OQ2V.js} +1 -1
  38. package/dist/{chunk-Q362TECJ.js → chunk-AQHSXA3J.js} +331 -2
  39. package/dist/{chunk-4SVAFAI2.js → chunk-AVRWMQ4U.js} +1 -1
  40. package/dist/{chunk-I7PKGMKI.js → chunk-BFLMRTPD.js} +6 -6
  41. package/dist/{chunk-ZCYABD7N.js → chunk-BHHWCNXI.js} +5 -5
  42. package/dist/{chunk-LFZODSAY.js → chunk-BUGO42R6.js} +4 -4
  43. package/dist/{chunk-7B6NW4P7.js → chunk-D3UJTX4U.js} +1 -1
  44. package/dist/{chunk-2IMQLZHS.js → chunk-FQ6BMP5P.js} +767 -81
  45. package/dist/{chunk-4TWDRU64.js → chunk-G3CQEUHI.js} +5 -5
  46. package/dist/{chunk-VGDOEKO2.js → chunk-H6ZXBFGX.js} +1 -1
  47. package/dist/{chunk-SNPQGTHJ.js → chunk-IQRPDKPJ.js} +1 -1
  48. package/dist/{chunk-CAWBLVBY.js → chunk-JVXZ3WDH.js} +6 -6
  49. package/dist/{chunk-7PZDAXIK.js → chunk-KEQAA42D.js} +1 -1
  50. package/dist/{chunk-UB5YB2ED.js → chunk-LAOP4EPL.js} +6 -6
  51. package/dist/{chunk-WYO2HXY5.js → chunk-OGRD5PFM.js} +1 -1
  52. package/dist/{chunk-AFOFVEXP.js → chunk-PRDKO3QB.js} +124 -13
  53. package/dist/{chunk-QLLOX7JY.js → chunk-QCCLDJHB.js} +8 -8
  54. package/dist/{chunk-FVVYHML7.js → chunk-RE4XWXBV.js} +6 -6
  55. package/dist/{chunk-4TRHPYTL.js → chunk-RGV6KHDM.js} +4 -4
  56. package/dist/{chunk-DGGIKWYQ.js → chunk-RGXOLPWO.js} +1 -1
  57. package/dist/{chunk-ELEAIRTG.js → chunk-TKB25A3L.js} +2 -2
  58. package/dist/{chunk-6KGMMVHF.js → chunk-U3UDPGZG.js} +2 -2
  59. package/dist/{chunk-7CVYXUOJ.js → chunk-UGWBJAGN.js} +1 -1
  60. package/dist/{chunk-LVL5NG5G.js → chunk-UMG7M32G.js} +19 -19
  61. package/dist/{chunk-LPMLAJYR.js → chunk-XCNS3CXE.js} +11 -17
  62. package/dist/{chunk-PA7QDS7P.js → chunk-XWUJWHFD.js} +204 -100
  63. package/dist/{chunk-WCQUKX74.js → chunk-ZCHHVTMF.js} +14 -5
  64. package/dist/cli/chat.js +31 -30
  65. package/dist/cli/check.js +6 -6
  66. package/dist/cli/plugins.js +13 -13
  67. package/dist/cli/snapshot-create.js +10 -10
  68. package/dist/cli/upgrade.js +7 -7
  69. package/dist/db/schema/conversation-participants.d.ts +17 -0
  70. package/dist/db/schema/conversations.d.ts +629 -1
  71. package/dist/db/schema/snapshots.d.ts +1 -1
  72. package/dist/db/schema.d.ts +18 -1
  73. package/dist/{db-A3CB76US.js → db-QVYOWATH.js} +7 -7
  74. package/dist/instrumentation.js +4 -4
  75. package/dist/nitro.js +2 -2
  76. package/dist/{oauth-callback-server-64QMI3IB.js → oauth-callback-server-PLPAFXO6.js} +23 -23
  77. package/dist/{runner-EIAE2BGD.js → runner-AGNEIOZS.js} +15 -15
  78. package/dist/{skills-ZADWFKDG.js → skills-U5KQ66EM.js} +6 -6
  79. package/dist/spawn-ZRYYZP2B.js +37 -0
  80. package/dist/{validation-4I6AF4CV.js → validation-QE4U45KS.js} +6 -6
  81. package/dist/version.js +1 -1
  82. package/dist/{work-BS6BZ66U.js → work-ATAXAHJJ.js} +18 -18
  83. package/dist/worker-LIL5AOER.js +24 -0
  84. package/migrations/0033_workspace_snapshot_active_build.sql +1 -0
  85. package/migrations/0034_strong_randall.sql +31 -0
  86. package/migrations/meta/0033_snapshot.json +2914 -0
  87. package/migrations/meta/0034_snapshot.json +3038 -0
  88. package/migrations/meta/_journal.json +15 -1
  89. package/package.json +5 -5
  90. package/dist/spawn-DVUOUFFC.js +0 -37
  91. package/dist/worker-RS5PWTCE.js +0 -24
@@ -1,4 +1,5 @@
1
1
  import {
2
+ conversationReadColumns,
2
3
  createJuniorSqlExecutor,
3
4
  juniorConversationAnnotations,
4
5
  juniorConversationBindings,
@@ -7,12 +8,12 @@ import {
7
8
  juniorConversations,
8
9
  juniorIdentities,
9
10
  juniorUsers
10
- } from "./chunk-WCQUKX74.js";
11
+ } from "./chunk-ZCHHVTMF.js";
11
12
  import {
12
13
  TURN_REASONING_LEVELS,
13
14
  getChatConfig,
14
15
  modelProfileSchema
15
- } from "./chunk-DLTP6TUJ.js";
16
+ } from "./chunk-3ZZEPLLB.js";
16
17
  import {
17
18
  juniorDestinations
18
19
  } from "./chunk-VFUK3X5B.js";
@@ -23,7 +24,7 @@ import {
23
24
  } from "./chunk-M63DETLL.js";
24
25
  import {
25
26
  normalizeIdentityEmail
26
- } from "./chunk-7CVYXUOJ.js";
27
+ } from "./chunk-UGWBJAGN.js";
27
28
 
28
29
  // src/chat/conversations/sql/store.ts
29
30
  import { randomUUID as randomUUID2 } from "crypto";
@@ -240,7 +241,8 @@ async function recordConversationParticipant(executor, args) {
240
241
  juniorConversationParticipants.rootConversationId
241
242
  ],
242
243
  set: {
243
- lastMessageAt: sql2`greatest(${juniorConversationParticipants.lastMessageAt}, excluded.last_message_at)`
244
+ lastMessageAt: sql2`greatest(${juniorConversationParticipants.lastMessageAt}, excluded.last_message_at)`,
245
+ ...args.restoreArchive ? { archivedAt: null } : {}
244
246
  }
245
247
  });
246
248
  }
@@ -596,7 +598,6 @@ function conversationFromRow(readRow) {
596
598
  ...destination ? { destination } : {},
597
599
  ...location ? { location } : {},
598
600
  ...actor ? { actor } : {},
599
- ...msFromDate(row.archivedAt) !== void 0 ? { archivedAtMs: msFromDate(row.archivedAt) } : {},
600
601
  ...row.channelName ? { channelName: row.channelName } : {},
601
602
  ...source ? { source } : {},
602
603
  ...sessionSource ? { sessionSource } : {},
@@ -813,7 +814,7 @@ var SqlStore = class {
813
814
  }
814
815
  async listByActivity(args = {}) {
815
816
  const rows = await this.executor.db().select({
816
- conversation: juniorConversations,
817
+ conversation: conversationReadColumns(),
817
818
  destination: juniorDestinations,
818
819
  actorIdentity: juniorIdentities,
819
820
  actorUserDisplayName: juniorUsers.displayName
@@ -896,7 +897,7 @@ var SqlStore = class {
896
897
  }
897
898
  async readConversationRow(conversationId) {
898
899
  const rows = await this.executor.db().select({
899
- conversation: juniorConversations,
900
+ conversation: conversationReadColumns(),
900
901
  destination: juniorDestinations,
901
902
  actorIdentity: juniorIdentities,
902
903
  actorUserDisplayName: juniorUsers.displayName
@@ -1053,7 +1054,6 @@ import {
1053
1054
  gt,
1054
1055
  gte,
1055
1056
  inArray as inArray2,
1056
- isNotNull,
1057
1057
  lt,
1058
1058
  lte,
1059
1059
  sql as sql6
@@ -1757,14 +1757,6 @@ var SqlConversationEventStore = class {
1757
1757
  newestCreatedAtMs,
1758
1758
  options
1759
1759
  );
1760
- if (options.activity !== "preserve" && pending.some((event) => eventUnarchivesConversation(event.data))) {
1761
- await this.executor.db().update(juniorConversations).set({ archivedAt: null }).where(
1762
- and6(
1763
- eq6(juniorConversations.conversationId, conversationId),
1764
- isNotNull(juniorConversations.archivedAt)
1765
- )
1766
- );
1767
- }
1768
1760
  const cursor = await this.readCursor(conversationId);
1769
1761
  const historyVersion = cursor.maxHistoryVersion ?? 0;
1770
1762
  let seq = cursor.nextSeq;
@@ -1791,7 +1783,8 @@ var SqlConversationEventStore = class {
1791
1783
  await recordConversationParticipant(this.executor, {
1792
1784
  actorIdentityId,
1793
1785
  conversationId,
1794
- atMs: event.createdAtMs
1786
+ atMs: event.createdAtMs,
1787
+ restoreArchive: eventUnarchivesConversation(event.data)
1795
1788
  });
1796
1789
  }
1797
1790
  }
@@ -2156,6 +2149,7 @@ export {
2156
2149
  upsertIdentity,
2157
2150
  upsertLinkedIdentity,
2158
2151
  deleteProviderIdentityForSlackUser,
2152
+ resolveRootConversationId,
2159
2153
  parseSessionSource,
2160
2154
  bindProviderConversation,
2161
2155
  locationFromRow,
@@ -5,7 +5,7 @@ import {
5
5
  getConversationEventStore,
6
6
  getDb,
7
7
  getSqlExecutor
8
- } from "./chunk-LPMLAJYR.js";
8
+ } from "./chunk-XCNS3CXE.js";
9
9
  import {
10
10
  juniorConversationAnnotations,
11
11
  juniorConversationEvents,
@@ -14,23 +14,25 @@ import {
14
14
  juniorUsers,
15
15
  juniorWorkspaceRepos,
16
16
  juniorWorkspaces
17
- } from "./chunk-WCQUKX74.js";
17
+ } from "./chunk-ZCHHVTMF.js";
18
18
  import {
19
19
  SANDBOX_WORKSPACE_ROOT,
20
+ getVercelSandboxCredentials,
20
21
  hash,
22
+ isSandboxMissingError,
21
23
  runNonInteractiveCommand,
22
24
  sleep
23
- } from "./chunk-Q362TECJ.js";
25
+ } from "./chunk-AQHSXA3J.js";
24
26
  import {
25
27
  getStateAdapter
26
- } from "./chunk-VGDOEKO2.js";
28
+ } from "./chunk-H6ZXBFGX.js";
27
29
  import {
28
30
  botConfig,
29
31
  completeObject,
30
32
  embedTexts,
31
33
  getSlackBotToken,
32
34
  standardModelId
33
- } from "./chunk-DLTP6TUJ.js";
35
+ } from "./chunk-3ZZEPLLB.js";
34
36
  import {
35
37
  isActorUserId,
36
38
  parseActorUserId,
@@ -46,7 +48,7 @@ import {
46
48
  setSpanAttributes,
47
49
  setSpanStatus,
48
50
  withSpan
49
- } from "./chunk-7CVYXUOJ.js";
51
+ } from "./chunk-UGWBJAGN.js";
50
52
 
51
53
  // src/chat/plugins/agent-hooks.ts
52
54
  import {
@@ -1664,15 +1666,65 @@ function workspaceRepoCheckoutPath(repo) {
1664
1666
  }
1665
1667
 
1666
1668
  // src/chat/workspaces/store.ts
1667
- import { randomUUID as randomUUID3 } from "crypto";
1668
- import { and as and5, asc as asc2, eq as eq5, sql as sql2 } from "drizzle-orm";
1669
+ import { randomUUID as randomUUID2 } from "crypto";
1670
+ import { and as and5, asc as asc3, eq as eq5, sql as sql2 } from "drizzle-orm";
1671
+
1672
+ // src/chat/sandbox/snapshot/builder-sandbox.ts
1673
+ import { Sandbox } from "@vercel/sandbox";
1674
+ async function getWorkspaceSnapshotBuilder(name, signal) {
1675
+ const credentials = getVercelSandboxCredentials();
1676
+ return await Sandbox.get({
1677
+ name,
1678
+ resume: true,
1679
+ signal,
1680
+ ...credentials ?? {}
1681
+ });
1682
+ }
1683
+ async function deleteWorkspaceSnapshotBuilders(names) {
1684
+ const credentials = getVercelSandboxCredentials();
1685
+ let hasError = false;
1686
+ let firstError;
1687
+ for (const name of new Set(names)) {
1688
+ try {
1689
+ const sandbox = await Sandbox.get({
1690
+ name,
1691
+ resume: false,
1692
+ ...credentials ?? {}
1693
+ });
1694
+ await sandbox.delete();
1695
+ } catch (error) {
1696
+ if (!isSandboxMissingError(error)) {
1697
+ if (!hasError) firstError = error;
1698
+ hasError = true;
1699
+ }
1700
+ }
1701
+ }
1702
+ if (hasError) throw firstError;
1703
+ }
1669
1704
 
1670
1705
  // src/chat/sandbox/snapshot/runtime.ts
1671
1706
  var SANDBOX_RUNTIME = "node22";
1672
1707
 
1673
1708
  // src/chat/sandbox/snapshot/store.ts
1674
- import { randomUUID as randomUUID2 } from "crypto";
1675
- import { and as and4, desc as desc2, eq as eq4, ne } from "drizzle-orm";
1709
+ import { and as and4, asc as asc2, desc as desc2, eq as eq4, ne, or } from "drizzle-orm";
1710
+ function builderNames(rows) {
1711
+ return [
1712
+ ...new Set(
1713
+ rows.map((row) => row.sandboxName).filter((name) => Boolean(name))
1714
+ )
1715
+ ];
1716
+ }
1717
+ function sameWorkspaceRecipe(stored, expected) {
1718
+ if (stored.setupScript !== expected.setupScript || stored.repos.length !== expected.repos.length) {
1719
+ return false;
1720
+ }
1721
+ const expectedRepos = new Set(
1722
+ expected.repos.map((repo) => JSON.stringify([repo.provider, repo.repo]))
1723
+ );
1724
+ return stored.repos.every(
1725
+ (repo) => expectedRepos.has(JSON.stringify([repo.provider, repo.repo]))
1726
+ );
1727
+ }
1676
1728
  function snapshotFromRow(row) {
1677
1729
  if (row.status !== "ready" || !row.snapshotId || !row.generatedAt || row.buildDurationMs == null) {
1678
1730
  throw new Error(`Invalid ready Workspace snapshot row: ${row.id}`);
@@ -1689,6 +1741,7 @@ function snapshotBuildFromRow(row) {
1689
1741
  throw new Error(`Invalid Workspace snapshot build row: ${row.id}`);
1690
1742
  }
1691
1743
  return {
1744
+ id: row.id,
1692
1745
  status: row.status,
1693
1746
  phase: row.buildPhase,
1694
1747
  profileHash: row.profileHash,
@@ -1710,99 +1763,131 @@ async function loadSnapshotsForProfile(db, workspaceId, profileHash) {
1710
1763
  ];
1711
1764
  const [readyRows, buildRows] = await Promise.all([
1712
1765
  db.select().from(juniorSnapshots).where(and4(profile, eq4(juniorSnapshots.status, "ready"))).orderBy(...order).limit(1),
1713
- db.select().from(juniorSnapshots).where(and4(profile, ne(juniorSnapshots.status, "ready"))).orderBy(...order).limit(1)
1766
+ db.select().from(juniorSnapshots).where(and4(profile, ne(juniorSnapshots.status, "ready"))).orderBy(asc2(juniorSnapshots.status), ...order).limit(1)
1714
1767
  ]);
1715
1768
  return {
1716
1769
  ready: readyRows[0] ? snapshotFromRow(readyRows[0]) : null,
1717
1770
  build: buildRows[0] ? snapshotBuildFromRow(buildRows[0]) : null
1718
1771
  };
1719
1772
  }
1720
- async function clearNonReadySnapshots(db, workspaceId) {
1721
- const deleted = await db.delete(juniorSnapshots).where(
1722
- and4(
1723
- eq4(juniorSnapshots.workspaceId, workspaceId),
1724
- ne(juniorSnapshots.status, "ready")
1725
- )
1726
- ).returning({
1727
- sandboxName: juniorSnapshots.buildSandboxName
1728
- });
1729
- return deleted.map((row) => row.sandboxName).filter((name) => Boolean(name));
1773
+ async function clearWorkspaceSnapshots(db, workspaceId) {
1774
+ await db.select({ id: juniorWorkspaces.id }).from(juniorWorkspaces).where(eq4(juniorWorkspaces.id, workspaceId)).for("update");
1775
+ const deleted = await db.delete(juniorSnapshots).where(eq4(juniorSnapshots.workspaceId, workspaceId)).returning({ sandboxName: juniorSnapshots.buildSandboxName });
1776
+ return builderNames(deleted);
1730
1777
  }
1731
- async function setWorkspaceSnapshot(workspaceId, snapshot) {
1778
+ async function invalidateReadySnapshot(params) {
1732
1779
  const executor = getSqlExecutor();
1733
- await executor.transaction(async () => {
1780
+ return await executor.transaction(async () => {
1734
1781
  const db = executor.db();
1735
- const workspaceRows = await db.select({ id: juniorWorkspaces.id }).from(juniorWorkspaces).where(eq4(juniorWorkspaces.id, workspaceId)).limit(1);
1736
- if (!workspaceRows[0]) return;
1737
- const now = /* @__PURE__ */ new Date();
1738
- const existing = await db.select().from(juniorSnapshots).where(
1782
+ const deleted = await db.delete(juniorSnapshots).where(
1739
1783
  and4(
1740
- eq4(juniorSnapshots.workspaceId, workspaceId),
1741
- eq4(juniorSnapshots.profileHash, snapshot.profileHash)
1784
+ eq4(juniorSnapshots.workspaceId, params.workspaceId),
1785
+ eq4(juniorSnapshots.profileHash, params.profileHash),
1786
+ eq4(juniorSnapshots.status, "ready"),
1787
+ eq4(juniorSnapshots.snapshotId, params.snapshotId)
1742
1788
  )
1743
- ).orderBy(desc2(juniorSnapshots.updatedAt), desc2(juniorSnapshots.createdAt)).limit(1);
1744
- const current = existing[0];
1745
- if (current && current.status !== "ready") {
1746
- await db.update(juniorSnapshots).set({
1747
- status: "ready",
1748
- snapshotId: snapshot.id,
1749
- buildDurationMs: snapshot.buildDurationMs,
1750
- generatedAt: snapshot.generatedAt,
1751
- buildStartedAt: null,
1752
- buildPhase: null,
1753
- buildSandboxName: null,
1754
- buildCommandId: null,
1755
- buildError: null,
1756
- updatedAt: now
1757
- }).where(eq4(juniorSnapshots.id, current.id));
1758
- return;
1759
- }
1760
- if (current && current.status === "ready" && current.snapshotId === snapshot.id) {
1761
- await db.update(juniorSnapshots).set({
1762
- buildDurationMs: snapshot.buildDurationMs,
1763
- generatedAt: snapshot.generatedAt,
1764
- buildStartedAt: null,
1765
- buildPhase: null,
1766
- buildSandboxName: null,
1767
- buildCommandId: null,
1768
- buildError: null,
1769
- updatedAt: now
1770
- }).where(eq4(juniorSnapshots.id, current.id));
1771
- return;
1772
- }
1773
- await db.insert(juniorSnapshots).values({
1774
- id: randomUUID2(),
1775
- workspaceId,
1776
- profileHash: snapshot.profileHash,
1789
+ ).returning({ sandboxName: juniorSnapshots.buildSandboxName });
1790
+ return deleted[0]?.sandboxName ?? null;
1791
+ });
1792
+ }
1793
+ async function setWorkspaceSnapshot(workspaceId, snapshot, options) {
1794
+ const executor = getSqlExecutor();
1795
+ return await executor.transaction(async () => {
1796
+ const db = executor.db();
1797
+ const now = /* @__PURE__ */ new Date();
1798
+ const updated = await db.update(juniorSnapshots).set({
1777
1799
  status: "ready",
1778
1800
  snapshotId: snapshot.id,
1779
1801
  buildDurationMs: snapshot.buildDurationMs,
1780
1802
  generatedAt: snapshot.generatedAt,
1781
1803
  buildStartedAt: null,
1782
- buildSandboxName: null,
1804
+ buildPhase: null,
1783
1805
  buildCommandId: null,
1784
1806
  buildError: null,
1785
- createdAt: now,
1786
1807
  updatedAt: now
1787
- });
1808
+ }).where(
1809
+ and4(
1810
+ eq4(juniorSnapshots.id, options.buildId),
1811
+ eq4(juniorSnapshots.workspaceId, workspaceId),
1812
+ eq4(juniorSnapshots.profileHash, snapshot.profileHash),
1813
+ eq4(juniorSnapshots.status, "building")
1814
+ )
1815
+ ).returning({ id: juniorSnapshots.id });
1816
+ if (updated.length === 0) {
1817
+ return { written: false, replacedBuilderNames: [] };
1818
+ }
1819
+ const replaced = await db.delete(juniorSnapshots).where(
1820
+ and4(
1821
+ eq4(juniorSnapshots.workspaceId, workspaceId),
1822
+ eq4(juniorSnapshots.profileHash, snapshot.profileHash),
1823
+ ne(juniorSnapshots.id, options.buildId),
1824
+ or(
1825
+ eq4(juniorSnapshots.status, "failed"),
1826
+ eq4(juniorSnapshots.status, "ready")
1827
+ )
1828
+ )
1829
+ ).returning({ sandboxName: juniorSnapshots.buildSandboxName });
1830
+ return {
1831
+ written: true,
1832
+ replacedBuilderNames: builderNames(replaced)
1833
+ };
1788
1834
  });
1789
1835
  }
1790
- async function recordResolvedWorkspaceSnapshot(workspaceId, snapshot) {
1791
- if (!snapshot.snapshotId || !snapshot.profileHash || snapshot.createdAtMs == null || snapshot.buildDurationMs == null) {
1792
- return;
1793
- }
1794
- try {
1795
- await setWorkspaceSnapshot(workspaceId, {
1796
- id: snapshot.snapshotId,
1797
- generatedAt: new Date(snapshot.createdAtMs),
1798
- buildDurationMs: snapshot.buildDurationMs,
1799
- profileHash: snapshot.profileHash
1800
- });
1801
- } catch (error) {
1802
- logException(error, "sandbox.workspace_snapshot.persist.failed", {
1803
- "app.workspace.id": workspaceId
1804
- });
1805
- }
1836
+ async function setWorkspaceSnapshotBuild(workspaceId, build, options = {}) {
1837
+ const executor = getSqlExecutor();
1838
+ return await executor.transaction(async () => {
1839
+ const db = executor.db();
1840
+ const now = /* @__PURE__ */ new Date();
1841
+ const updated = await db.update(juniorSnapshots).set({
1842
+ status: build.status,
1843
+ buildStartedAt: build.startedAt,
1844
+ buildPhase: build.phase,
1845
+ buildSandboxName: build.sandboxName,
1846
+ buildCommandId: build.commandId,
1847
+ buildError: build.error,
1848
+ updatedAt: now
1849
+ }).where(
1850
+ and4(
1851
+ eq4(juniorSnapshots.id, build.id),
1852
+ eq4(juniorSnapshots.workspaceId, workspaceId),
1853
+ eq4(juniorSnapshots.profileHash, build.profileHash),
1854
+ eq4(juniorSnapshots.status, "building")
1855
+ )
1856
+ ).returning({ id: juniorSnapshots.id });
1857
+ if (updated.length > 0) {
1858
+ return true;
1859
+ }
1860
+ if (!options.insertIfMissing) return false;
1861
+ const workspaceRows = await db.select({ setupScript: juniorWorkspaces.setupScript }).from(juniorWorkspaces).where(eq4(juniorWorkspaces.id, workspaceId)).for("update");
1862
+ const workspace = workspaceRows[0];
1863
+ if (!workspace) return false;
1864
+ if (options.expectedRecipe) {
1865
+ const repos = await db.select({
1866
+ provider: juniorWorkspaceRepos.provider,
1867
+ repo: juniorWorkspaceRepos.repo
1868
+ }).from(juniorWorkspaceRepos).where(eq4(juniorWorkspaceRepos.workspaceId, workspaceId));
1869
+ if (!sameWorkspaceRecipe({ ...workspace, repos }, options.expectedRecipe)) {
1870
+ return false;
1871
+ }
1872
+ }
1873
+ const inserted = await db.insert(juniorSnapshots).values({
1874
+ id: build.id,
1875
+ workspaceId,
1876
+ profileHash: build.profileHash,
1877
+ status: build.status,
1878
+ snapshotId: null,
1879
+ buildDurationMs: null,
1880
+ generatedAt: null,
1881
+ buildStartedAt: build.startedAt,
1882
+ buildPhase: build.phase,
1883
+ buildSandboxName: build.sandboxName,
1884
+ buildCommandId: build.commandId,
1885
+ buildError: build.error,
1886
+ createdAt: now,
1887
+ updatedAt: now
1888
+ }).onConflictDoNothing().returning({ id: juniorSnapshots.id });
1889
+ return inserted.length > 0;
1890
+ });
1806
1891
  }
1807
1892
 
1808
1893
  // src/chat/workspaces/validation.ts
@@ -1914,8 +1999,8 @@ async function workspaceWithSnapshot(db, row, repos) {
1914
1999
  }
1915
2000
  async function loadWorkspaceRepos(db, workspaceId) {
1916
2001
  return await db.select().from(juniorWorkspaceRepos).where(eq5(juniorWorkspaceRepos.workspaceId, workspaceId)).orderBy(
1917
- asc2(juniorWorkspaceRepos.provider),
1918
- asc2(juniorWorkspaceRepos.repo)
2002
+ asc3(juniorWorkspaceRepos.provider),
2003
+ asc3(juniorWorkspaceRepos.repo)
1919
2004
  );
1920
2005
  }
1921
2006
  async function replaceWorkspaceRepos(db, workspaceId, repos) {
@@ -1946,13 +2031,24 @@ function isUniqueViolation(error) {
1946
2031
  }
1947
2032
  return false;
1948
2033
  }
2034
+ async function cleanupSnapshotBuilders(workspaceId, names) {
2035
+ if (names.length === 0) return;
2036
+ try {
2037
+ await deleteWorkspaceSnapshotBuilders(names);
2038
+ } catch (error) {
2039
+ logException(error, "sandbox.workspace_snapshot.builder.delete_failed", {
2040
+ "app.workspace.id": workspaceId,
2041
+ "app.sandbox.snapshot.builder_count": names.length
2042
+ });
2043
+ }
2044
+ }
1949
2045
  async function listWorkspaces(db) {
1950
2046
  const [workspaces, repos] = await Promise.all([
1951
- db.select().from(juniorWorkspaces).orderBy(asc2(juniorWorkspaces.name)),
2047
+ db.select().from(juniorWorkspaces).orderBy(asc3(juniorWorkspaces.name)),
1952
2048
  db.select().from(juniorWorkspaceRepos).orderBy(
1953
- asc2(juniorWorkspaceRepos.workspaceId),
1954
- asc2(juniorWorkspaceRepos.provider),
1955
- asc2(juniorWorkspaceRepos.repo)
2049
+ asc3(juniorWorkspaceRepos.workspaceId),
2050
+ asc3(juniorWorkspaceRepos.provider),
2051
+ asc3(juniorWorkspaceRepos.repo)
1956
2052
  )
1957
2053
  ]);
1958
2054
  return workspaces.map(
@@ -1975,7 +2071,7 @@ async function listWorkspaceNamesByRepository(db, input) {
1975
2071
  // Recipes store the provided repo casing; identity is case-insensitive.
1976
2072
  sql2`lower(${juniorWorkspaceRepos.repo}) = lower(${repo})`
1977
2073
  )
1978
- ).orderBy(asc2(juniorWorkspaces.name));
2074
+ ).orderBy(asc3(juniorWorkspaces.name));
1979
2075
  return rows.map((row) => row.name);
1980
2076
  }
1981
2077
  async function getWorkspaceByName(db, name) {
@@ -2002,7 +2098,7 @@ async function createWorkspace(input) {
2002
2098
  const recipe = normalizeWorkspaceRecipe(input);
2003
2099
  const executor = getSqlExecutor();
2004
2100
  const now = /* @__PURE__ */ new Date();
2005
- const id = randomUUID3();
2101
+ const id = randomUUID2();
2006
2102
  try {
2007
2103
  await executor.transaction(async () => {
2008
2104
  const db = executor.db();
@@ -2030,7 +2126,7 @@ async function updateWorkspace(id, input) {
2030
2126
  const executor = getSqlExecutor();
2031
2127
  const now = /* @__PURE__ */ new Date();
2032
2128
  try {
2033
- const updated = await executor.transaction(async () => {
2129
+ const result = await executor.transaction(async () => {
2034
2130
  const db = executor.db();
2035
2131
  const existingRows = await db.select().from(juniorWorkspaces).where(eq5(juniorWorkspaces.id, id)).limit(1);
2036
2132
  const existing = existingRows[0];
@@ -2043,12 +2139,11 @@ async function updateWorkspace(id, input) {
2043
2139
  updatedAt: now
2044
2140
  }).where(eq5(juniorWorkspaces.id, id)).returning();
2045
2141
  await replaceWorkspaceRepos(db, id, recipe.repos);
2046
- if (snapshotChanged) {
2047
- await clearNonReadySnapshots(db, id);
2048
- }
2049
- return rows[0];
2142
+ const builderNames2 = snapshotChanged ? await clearWorkspaceSnapshots(db, id) : [];
2143
+ return { builderNames: builderNames2, workspace: rows[0] };
2050
2144
  });
2051
- if (!updated) return void 0;
2145
+ if (!result) return void 0;
2146
+ await cleanupSnapshotBuilders(id, result.builderNames);
2052
2147
  } catch (error) {
2053
2148
  if (isUniqueViolation(error)) {
2054
2149
  throw new WorkspaceValidationError(
@@ -2061,10 +2156,14 @@ async function updateWorkspace(id, input) {
2061
2156
  }
2062
2157
  async function deleteWorkspace(id) {
2063
2158
  const executor = getSqlExecutor();
2064
- return await executor.transaction(async () => {
2065
- const rows = await executor.db().delete(juniorWorkspaces).where(eq5(juniorWorkspaces.id, id)).returning({ id: juniorWorkspaces.id });
2066
- return rows.length > 0;
2159
+ const result = await executor.transaction(async () => {
2160
+ const db = executor.db();
2161
+ const builderNames2 = await clearWorkspaceSnapshots(db, id);
2162
+ const rows = await db.delete(juniorWorkspaces).where(eq5(juniorWorkspaces.id, id)).returning({ id: juniorWorkspaces.id });
2163
+ return { builderNames: builderNames2, deleted: rows.length > 0 };
2067
2164
  });
2165
+ await cleanupSnapshotBuilders(id, result.builderNames);
2166
+ return result.deleted;
2068
2167
  }
2069
2168
 
2070
2169
  // src/chat/plugins/agent-hooks.ts
@@ -3341,8 +3440,13 @@ export {
3341
3440
  resolveChannelCapabilities,
3342
3441
  tryWorkspaceRepoCheckoutPath,
3343
3442
  workspaceRepoCheckoutPath,
3443
+ getWorkspaceSnapshotBuilder,
3444
+ deleteWorkspaceSnapshotBuilders,
3344
3445
  SANDBOX_RUNTIME,
3345
- recordResolvedWorkspaceSnapshot,
3446
+ loadSnapshotsForProfile,
3447
+ invalidateReadySnapshot,
3448
+ setWorkspaceSnapshot,
3449
+ setWorkspaceSnapshotBuild,
3346
3450
  WorkspaceValidationError,
3347
3451
  listWorkspaces,
3348
3452
  getWorkspaceByName,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_SQL_STATEMENT_TIMEOUT_MS
3
- } from "./chunk-DLTP6TUJ.js";
3
+ } from "./chunk-3ZZEPLLB.js";
4
4
  import {
5
5
  juniorDestinations,
6
6
  timestamptz
@@ -56,7 +56,7 @@ var juniorArtifacts = pgTable(
56
56
  import { index as index4, integer as integer3, pgTable as pgTable5, text as text5 } from "drizzle-orm/pg-core";
57
57
 
58
58
  // src/db/schema/conversations.ts
59
- import { sql as sql2 } from "drizzle-orm";
59
+ import { getTableColumns, sql as sql2 } from "drizzle-orm";
60
60
  import {
61
61
  index as index3,
62
62
  integer as integer2,
@@ -181,7 +181,8 @@ var juniorConversations = pgTable4(
181
181
  executionDurationMs: integer2("execution_duration_ms").notNull().default(0),
182
182
  executionUsage: jsonb2("execution_usage_json").$type(),
183
183
  metricRunId: text4("metric_run_id"),
184
- archivedAt: timestamptz("archived_at")
184
+ // Keep the legacy column in the schema. Runtime code must not read or write it.
185
+ legacyArchivedAt: timestamptz("archived_at")
185
186
  },
186
187
  (table) => [
187
188
  index3("junior_conversations_last_activity_idx").on(
@@ -210,6 +211,11 @@ var juniorConversations = pgTable4(
210
211
  index3("junior_conversations_root_idx").on(table.rootConversationId)
211
212
  ]
212
213
  );
214
+ function conversationReadColumns() {
215
+ const { legacyArchivedAt, ...columns } = getTableColumns(juniorConversations);
216
+ void legacyArchivedAt;
217
+ return columns;
218
+ }
213
219
 
214
220
  // src/db/schema/attachments.ts
215
221
  var juniorAttachments = pgTable5(
@@ -387,7 +393,8 @@ var juniorConversationParticipants = pgTable10(
387
393
  {
388
394
  userId: text10("user_id").notNull().references(() => juniorUsers.id),
389
395
  rootConversationId: text10("root_conversation_id").notNull().references(() => juniorConversations.conversationId),
390
- lastMessageAt: timestamptz("last_message_at").notNull()
396
+ lastMessageAt: timestamptz("last_message_at").notNull(),
397
+ archivedAt: timestamptz("archived_at")
391
398
  },
392
399
  (table) => [
393
400
  primaryKey4({
@@ -693,7 +700,7 @@ var juniorSnapshots = pgTable18(
693
700
  snapshotId: text18("snapshot_id"),
694
701
  buildDurationMs: integer6("build_duration_ms"),
695
702
  generatedAt: timestamptz("generated_at"),
696
- // In-flight builder references for check-in across execution slices.
703
+ // Builder references for check-in and snapshot-owner cleanup.
697
704
  buildStartedAt: timestamptz("build_started_at"),
698
705
  buildPhase: text18("build_phase").$type(),
699
706
  buildSandboxName: text18("build_sandbox_name"),
@@ -731,6 +738,7 @@ var juniorSnapshots = pgTable18(
731
738
  table.profileHash,
732
739
  table.status
733
740
  ),
741
+ uniqueIndex8("junior_snapshots_active_build_uidx").on(table.workspaceId, table.profileHash).where(sql6`${table.status} = 'building'`),
734
742
  index13("junior_snapshots_workspace_status_idx").on(
735
743
  table.workspaceId,
736
744
  table.status
@@ -1018,6 +1026,7 @@ export {
1018
1026
  juniorUsers,
1019
1027
  juniorIdentities,
1020
1028
  juniorConversations,
1029
+ conversationReadColumns,
1021
1030
  juniorAttachments,
1022
1031
  juniorConversationAnnotations,
1023
1032
  juniorApiTokens,