@sentry/junior 0.155.0 → 0.156.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 (79) hide show
  1. package/dist/{agent-hooks-AWVTLAV2.js → agent-hooks-CCAR46PQ.js} +10 -10
  2. package/dist/api/locations/detail.d.ts +1 -0
  3. package/dist/api/schema/conversation.d.ts +19 -4
  4. package/dist/api/schema/location.d.ts +1 -0
  5. package/dist/api/schema/person.d.ts +1 -0
  6. package/dist/api/schema.js +1 -1
  7. package/dist/api.js +48 -36
  8. package/dist/app.js +147 -44
  9. package/dist/{catalog-runtime-V6YD6KH5.js → catalog-runtime-3SNKYCBG.js} +5 -5
  10. package/dist/chat/agent/types.d.ts +2 -0
  11. package/dist/chat/attachments/storage.d.ts +12 -0
  12. package/dist/chat/attachments/store.d.ts +52 -0
  13. package/dist/chat/attachments/vercel.d.ts +3 -0
  14. package/dist/chat/conversations/retention.d.ts +4 -0
  15. package/dist/chat/ingress/bot-mention.d.ts +13 -0
  16. package/dist/chat/plugins/unfinished-work.d.ts +6 -0
  17. package/dist/chat/runtime/agent-runner.d.ts +2 -0
  18. package/dist/chat/services/conversation-memory.d.ts +1 -1
  19. package/dist/chat/slack/tools/send-files.d.ts +17 -11
  20. package/dist/chat/state/conversation.d.ts +2 -1
  21. package/dist/chat/tools/types.d.ts +2 -0
  22. package/dist/{chunk-V57LZDHN.js → chunk-3FFUC32L.js} +6 -6
  23. package/dist/{chunk-J2SYI36O.js → chunk-3L2PMOSR.js} +4 -4
  24. package/dist/{chunk-2AHVM3HK.js → chunk-4ZZDTHFC.js} +10 -8
  25. package/dist/{chunk-AENPRXCG.js → chunk-53W3LZEW.js} +381 -55
  26. package/dist/{chunk-ELOTIL5F.js → chunk-6CINHXIL.js} +4 -4
  27. package/dist/{chunk-37G5HJ2X.js → chunk-6JUU7J2B.js} +4 -4
  28. package/dist/{chunk-KBT2I5ER.js → chunk-7RAETIYO.js} +6 -6
  29. package/dist/{chunk-NWSKFYOH.js → chunk-A6WPDFHQ.js} +1 -1
  30. package/dist/{chunk-773XC7P4.js → chunk-AIJRG227.js} +159 -128
  31. package/dist/{chunk-APHDJYN7.js → chunk-CK2H7CFO.js} +1 -1
  32. package/dist/{chunk-Y7ZUBP2K.js → chunk-CN2XSVKF.js} +6 -6
  33. package/dist/{chunk-TVXAN43I.js → chunk-EDJ64P2P.js} +1 -1
  34. package/dist/{chunk-XFZLQANQ.js → chunk-GKC43TLS.js} +2 -2
  35. package/dist/{chunk-UWTXAYBP.js → chunk-GSBPZHLQ.js} +9 -9
  36. package/dist/{chunk-JJX77MB7.js → chunk-IIO5PFZX.js} +3 -3
  37. package/dist/{chunk-RQ62VKM7.js → chunk-IZEBHYI4.js} +1 -1
  38. package/dist/{chunk-J7MWZFFP.js → chunk-JUJR6WTL.js} +4 -4
  39. package/dist/{chunk-JFZBGFNX.js → chunk-KBGMYLSV.js} +2 -1
  40. package/dist/{chunk-LQT3SWFA.js → chunk-KCA5MKHU.js} +5 -5
  41. package/dist/{chunk-SDRJ2LE3.js → chunk-MC57E5U5.js} +1 -1
  42. package/dist/{chunk-2WLJDM23.js → chunk-MJE7XKWP.js} +1 -1
  43. package/dist/{chunk-TKJ7277U.js → chunk-NTNU6T2D.js} +2 -2
  44. package/dist/{chunk-EORR34UB.js → chunk-OGZPRMZA.js} +1 -1
  45. package/dist/{chunk-NKFRRZIT.js → chunk-OMQJB7YN.js} +19 -18
  46. package/dist/{chunk-Y6MVGU7L.js → chunk-PMFGVLDU.js} +10 -10
  47. package/dist/{chunk-KTVHAHUV.js → chunk-SE3D225X.js} +1 -1
  48. package/dist/{chunk-JIC33O5L.js → chunk-SRJA6QLM.js} +2 -2
  49. package/dist/{chunk-4K5N6HTX.js → chunk-TN4LVILT.js} +5 -5
  50. package/dist/{chunk-MLBH7J7J.js → chunk-TNXGMFWV.js} +1 -1
  51. package/dist/{chunk-RN3AWYEP.js → chunk-WD7SEUZO.js} +1 -1
  52. package/dist/{chunk-ZLSFOSDK.js → chunk-X5NZQH5U.js} +2 -1
  53. package/dist/{chunk-7NYEB2TM.js → chunk-X7W45B5G.js} +1 -1
  54. package/dist/{chunk-CPQBTTI6.js → chunk-ZNWRYNCA.js} +5 -5
  55. package/dist/cli/chat.js +30 -30
  56. package/dist/cli/check.js +6 -6
  57. package/dist/cli/plugins.js +13 -13
  58. package/dist/cli/snapshot-create.js +9 -9
  59. package/dist/cli/upgrade.js +7 -7
  60. package/dist/db/schema/attachments.d.ts +178 -0
  61. package/dist/db/schema.d.ts +179 -1
  62. package/dist/{db-WT7P56AH.js → db-TYUUKGJX.js} +7 -7
  63. package/dist/handlers/retention.d.ts +4 -1
  64. package/dist/instrumentation.js +4 -4
  65. package/dist/nitro.js +2 -2
  66. package/dist/{oauth-callback-server-R2PK2MCA.js → oauth-callback-server-AWVDKPR5.js} +23 -23
  67. package/dist/{runner-O7YYUIHT.js → runner-JUQXMACC.js} +13 -13
  68. package/dist/{skills-S6HLQRH7.js → skills-4GZTZX4V.js} +6 -6
  69. package/dist/spawn-32SSRWU4.js +37 -0
  70. package/dist/{validation-46WD2OAR.js → validation-DRLNJRND.js} +6 -6
  71. package/dist/version.js +1 -1
  72. package/dist/{work-YT5QUBS3.js → work-TGIXRRJY.js} +17 -17
  73. package/dist/worker-IFXZOYED.js +24 -0
  74. package/migrations/0027_attachment_storage.sql +16 -0
  75. package/migrations/meta/0027_snapshot.json +2554 -0
  76. package/migrations/meta/_journal.json +8 -1
  77. package/package.json +5 -4
  78. package/dist/spawn-B4SBFFA2.js +0 -37
  79. package/dist/worker-4C5BEDWN.js +0 -24
@@ -7,7 +7,7 @@ import {
7
7
  PluginCredentialFailureError,
8
8
  agentNameSchema,
9
9
  createPluginAuthOrchestration
10
- } from "./chunk-CPQBTTI6.js";
10
+ } from "./chunk-ZNWRYNCA.js";
11
11
  import {
12
12
  authorizationId
13
13
  } from "./chunk-DIB4GX3G.js";
@@ -32,7 +32,7 @@ import {
32
32
  searchSlackUsers,
33
33
  selectPluginGrant,
34
34
  toMcpProviderError
35
- } from "./chunk-2AHVM3HK.js";
35
+ } from "./chunk-4ZZDTHFC.js";
36
36
  import {
37
37
  RetryableDeliveryError,
38
38
  actorFromRun,
@@ -48,7 +48,7 @@ import {
48
48
  getSlackMessagePermalink,
49
49
  resolveBaseUrl,
50
50
  uploadFilesToConversation
51
- } from "./chunk-KBT2I5ER.js";
51
+ } from "./chunk-7RAETIYO.js";
52
52
  import {
53
53
  parseSandboxEgressAuthRequiredSignal,
54
54
  parseSandboxEgressPermissionDeniedSignal,
@@ -74,7 +74,7 @@ import {
74
74
  runNonInteractiveCommand,
75
75
  throwSandboxOperationError,
76
76
  wrapSandboxSetupError
77
- } from "./chunk-37G5HJ2X.js";
77
+ } from "./chunk-6JUU7J2B.js";
78
78
  import {
79
79
  SCHEDULED_TASK_SYSTEM_ACTOR,
80
80
  compactEventTask,
@@ -106,10 +106,10 @@ import {
106
106
  saveEventTask,
107
107
  scheduledTaskAttributes,
108
108
  writableEventTask
109
- } from "./chunk-LQT3SWFA.js";
109
+ } from "./chunk-KCA5MKHU.js";
110
110
  import {
111
111
  parseSlackThreadId
112
- } from "./chunk-APHDJYN7.js";
112
+ } from "./chunk-CK2H7CFO.js";
113
113
  import {
114
114
  AGENTS_REMOVAL_NOTICE,
115
115
  AGENTS_REPLACEMENT_NOTICE,
@@ -179,7 +179,7 @@ import {
179
179
  stripRuntimeTurnContext,
180
180
  trimTrailingAssistantMessages,
181
181
  truncateText
182
- } from "./chunk-J2SYI36O.js";
182
+ } from "./chunk-3L2PMOSR.js";
183
183
  import {
184
184
  JUNIOR_THREAD_STATE_TTL_MS
185
185
  } from "./chunk-3Y47VMHV.js";
@@ -195,7 +195,7 @@ import {
195
195
  getSlackToolContext,
196
196
  readActorIdentity,
197
197
  resolveChannelCapabilities
198
- } from "./chunk-V57LZDHN.js";
198
+ } from "./chunk-3FFUC32L.js";
199
199
  import {
200
200
  SlackActionError,
201
201
  downloadPrivateSlackFile,
@@ -204,7 +204,7 @@ import {
204
204
  isConversationScopedChannel,
205
205
  normalizeSlackConversationId,
206
206
  withSlackRetries
207
- } from "./chunk-TKJ7277U.js";
207
+ } from "./chunk-NTNU6T2D.js";
208
208
  import {
209
209
  KNOWN_CONVERSATION_EVENT_TYPES,
210
210
  contextProvenance,
@@ -216,10 +216,12 @@ import {
216
216
  instructionActors,
217
217
  instructionProvenanceFor,
218
218
  sameActorIdentity
219
- } from "./chunk-JJX77MB7.js";
219
+ } from "./chunk-IIO5PFZX.js";
220
220
  import {
221
+ juniorAttachments,
222
+ juniorConversations,
221
223
  juniorIdentities
222
- } from "./chunk-773XC7P4.js";
224
+ } from "./chunk-AIJRG227.js";
223
225
  import {
224
226
  SANDBOX_DATA_ROOT,
225
227
  SANDBOX_SKILLS_ROOT,
@@ -231,7 +233,7 @@ import {
231
233
  } from "./chunk-EM225ITJ.js";
232
234
  import {
233
235
  getStateAdapter
234
- } from "./chunk-SDRJ2LE3.js";
236
+ } from "./chunk-MC57E5U5.js";
235
237
  import {
236
238
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
237
239
  COMPACTION_SUMMARY_PREFIX,
@@ -264,7 +266,7 @@ import {
264
266
  resolveGatewayCredential,
265
267
  resolveGatewayModel,
266
268
  unwrapCurrentInstruction
267
- } from "./chunk-XFZLQANQ.js";
269
+ } from "./chunk-GKC43TLS.js";
268
270
  import {
269
271
  canExposeConversationPayload,
270
272
  resolveConversationPrivacy,
@@ -274,16 +276,16 @@ import {
274
276
  toGenAiMessagesTraceAttributes,
275
277
  toGenAiPayloadMetadata,
276
278
  toGenAiPayloadTraceAttributes
277
- } from "./chunk-2WLJDM23.js";
279
+ } from "./chunk-MJE7XKWP.js";
278
280
  import {
279
281
  discoverSkills,
280
282
  findSkillByName,
281
283
  loadSkillsByName,
282
284
  parseSkillInvocation
283
- } from "./chunk-JIC33O5L.js";
285
+ } from "./chunk-SRJA6QLM.js";
284
286
  import {
285
287
  pluginCatalogRuntime
286
- } from "./chunk-TVXAN43I.js";
288
+ } from "./chunk-EDJ64P2P.js";
287
289
  import {
288
290
  CredentialUnavailableError,
289
291
  OAuthProviderError,
@@ -291,7 +293,7 @@ import {
291
293
  getOAuthProviderErrorAttributes,
292
294
  resolveAuthTokenPlaceholder,
293
295
  resolvePluginCommandEnv
294
- } from "./chunk-RN3AWYEP.js";
296
+ } from "./chunk-WD7SEUZO.js";
295
297
  import {
296
298
  credentialUserSubjectId
297
299
  } from "./chunk-BDUENQD3.js";
@@ -319,7 +321,7 @@ import {
319
321
  summarizeMessageText,
320
322
  withLogContext,
321
323
  withSpan
322
- } from "./chunk-7NYEB2TM.js";
324
+ } from "./chunk-X7W45B5G.js";
323
325
  import {
324
326
  startInactiveSpan,
325
327
  withActiveSpan
@@ -10535,15 +10537,327 @@ function createSlackMessageAddReactionTool(context, state) {
10535
10537
  }
10536
10538
 
10537
10539
  // src/chat/slack/tools/send-files.ts
10538
- import { createHash as createHash4 } from "crypto";
10540
+ import { createHash as createHash5 } from "crypto";
10541
+
10542
+ // src/chat/attachments/store.ts
10543
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
10544
+ import { and, asc, eq, inArray, isNotNull, isNull, or } from "drizzle-orm";
10545
+ var ATTACHMENT_GC_BATCH_LIMIT = 200;
10546
+ function attachmentDigest(file) {
10547
+ return createHash4("sha256").update(file.data).digest("hex");
10548
+ }
10549
+ function attachmentId(args) {
10550
+ return createHash4("sha256").update(
10551
+ [
10552
+ args.conversationId,
10553
+ args.provider,
10554
+ args.sha256,
10555
+ args.filename,
10556
+ args.contentType
10557
+ ].join("\0")
10558
+ ).digest("hex");
10559
+ }
10560
+ function attachmentKey(args) {
10561
+ return `conversations/${args.conversationId}/attachments/${args.attachmentId}/${args.writeId}/${args.filename}`;
10562
+ }
10563
+ async function bestEffortDelete(storage, key) {
10564
+ try {
10565
+ await storage.delete([key]);
10566
+ } catch {
10567
+ }
10568
+ }
10569
+ async function isConversationPurged(db, conversationId) {
10570
+ const [row] = await db.db().select({
10571
+ transcriptPurgedAt: juniorConversations.transcriptPurgedAt
10572
+ }).from(juniorConversations).where(eq(juniorConversations.conversationId, conversationId));
10573
+ return row?.transcriptPurgedAt != null;
10574
+ }
10575
+ function purgedConversationError(conversationId) {
10576
+ return new Error(
10577
+ `Cannot store attachment for purged conversation ${conversationId}`
10578
+ );
10579
+ }
10580
+ async function assertConversationNotPurged(db, conversationId) {
10581
+ if (await isConversationPurged(db, conversationId)) {
10582
+ throw purgedConversationError(conversationId);
10583
+ }
10584
+ }
10585
+ async function rejectPurgedAttachmentWrite(args) {
10586
+ await args.db.db().update(juniorAttachments).set({ deleteRequestedAt: args.now }).where(eq(juniorAttachments.id, args.id));
10587
+ await bestEffortDelete(args.storage, args.storageKey);
10588
+ throw purgedConversationError(args.conversationId);
10589
+ }
10590
+ async function reuseLiveAttachment(args) {
10591
+ await bestEffortDelete(args.storage, args.storageKey);
10592
+ await assertConversationNotPurged(args.db, args.conversationId);
10593
+ return { id: args.id };
10594
+ }
10595
+ async function storeAttachment(args) {
10596
+ const now = new Date(args.nowMs ?? Date.now());
10597
+ const sha256 = attachmentDigest(args.file);
10598
+ const id = attachmentId({
10599
+ conversationId: args.conversationId,
10600
+ contentType: args.file.mimeType,
10601
+ filename: args.file.filename,
10602
+ provider: args.storage.provider,
10603
+ sha256
10604
+ });
10605
+ const [existing] = await args.db.db().select({
10606
+ contentType: juniorAttachments.contentType,
10607
+ conversationId: juniorAttachments.conversationId,
10608
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
10609
+ filename: juniorAttachments.filename,
10610
+ id: juniorAttachments.id,
10611
+ provider: juniorAttachments.provider,
10612
+ sha256: juniorAttachments.sha256,
10613
+ storageKey: juniorAttachments.storageKey
10614
+ }).from(juniorAttachments).where(eq(juniorAttachments.id, id));
10615
+ if (existing) {
10616
+ if (existing.contentType !== args.file.mimeType || existing.conversationId !== args.conversationId || existing.filename !== args.file.filename || existing.provider !== args.storage.provider || existing.sha256 !== sha256) {
10617
+ throw new Error(`Attachment write conflicts with ${existing.id}`);
10618
+ }
10619
+ if (existing.deleteRequestedAt === null) {
10620
+ await assertConversationNotPurged(args.db, args.conversationId);
10621
+ return { id: existing.id };
10622
+ }
10623
+ }
10624
+ await assertConversationNotPurged(args.db, args.conversationId);
10625
+ const storageKey = attachmentKey({
10626
+ attachmentId: id,
10627
+ conversationId: args.conversationId,
10628
+ filename: args.file.filename,
10629
+ writeId: randomUUID3()
10630
+ });
10631
+ await args.storage.put({
10632
+ body: args.file.data,
10633
+ contentType: args.file.mimeType,
10634
+ key: storageKey
10635
+ });
10636
+ if (existing?.deleteRequestedAt != null) {
10637
+ if (await isConversationPurged(args.db, args.conversationId)) {
10638
+ await bestEffortDelete(args.storage, storageKey);
10639
+ throw purgedConversationError(args.conversationId);
10640
+ }
10641
+ const oldStorageKey = existing.storageKey;
10642
+ const revived2 = await args.db.db().update(juniorAttachments).set({
10643
+ deleteRequestedAt: null,
10644
+ storageKey
10645
+ }).where(
10646
+ and(
10647
+ eq(juniorAttachments.id, id),
10648
+ isNotNull(juniorAttachments.deleteRequestedAt)
10649
+ )
10650
+ ).returning({ id: juniorAttachments.id });
10651
+ if (revived2.length > 0) {
10652
+ if (await isConversationPurged(args.db, args.conversationId)) {
10653
+ await rejectPurgedAttachmentWrite({
10654
+ conversationId: args.conversationId,
10655
+ db: args.db,
10656
+ id,
10657
+ now,
10658
+ storage: args.storage,
10659
+ storageKey
10660
+ });
10661
+ }
10662
+ if (oldStorageKey !== storageKey) {
10663
+ await bestEffortDelete(args.storage, oldStorageKey);
10664
+ }
10665
+ return { id };
10666
+ }
10667
+ const [afterRace2] = await args.db.db().select({
10668
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
10669
+ id: juniorAttachments.id,
10670
+ storageKey: juniorAttachments.storageKey
10671
+ }).from(juniorAttachments).where(eq(juniorAttachments.id, id));
10672
+ if (afterRace2?.deleteRequestedAt === null) {
10673
+ return reuseLiveAttachment({
10674
+ conversationId: args.conversationId,
10675
+ db: args.db,
10676
+ id: afterRace2.id,
10677
+ storage: args.storage,
10678
+ storageKey
10679
+ });
10680
+ }
10681
+ }
10682
+ let inserted = [];
10683
+ try {
10684
+ inserted = await args.db.db().insert(juniorAttachments).values({
10685
+ id,
10686
+ conversationId: args.conversationId,
10687
+ provider: args.storage.provider,
10688
+ storageKey,
10689
+ filename: args.file.filename,
10690
+ contentType: args.file.mimeType,
10691
+ bytes: args.file.bytes,
10692
+ sha256,
10693
+ createdAt: now
10694
+ }).onConflictDoNothing({ target: juniorAttachments.id }).returning({ id: juniorAttachments.id });
10695
+ } catch (error) {
10696
+ await bestEffortDelete(args.storage, storageKey);
10697
+ throw error;
10698
+ }
10699
+ if (inserted.length > 0) {
10700
+ if (await isConversationPurged(args.db, args.conversationId)) {
10701
+ await rejectPurgedAttachmentWrite({
10702
+ conversationId: args.conversationId,
10703
+ db: args.db,
10704
+ id,
10705
+ now,
10706
+ storage: args.storage,
10707
+ storageKey
10708
+ });
10709
+ }
10710
+ return { id };
10711
+ }
10712
+ const [row] = await args.db.db().select({
10713
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
10714
+ id: juniorAttachments.id,
10715
+ storageKey: juniorAttachments.storageKey
10716
+ }).from(juniorAttachments).where(eq(juniorAttachments.id, id));
10717
+ if (!row) {
10718
+ await bestEffortDelete(args.storage, storageKey);
10719
+ throw new Error(`Attachment row missing after write for ${id}`);
10720
+ }
10721
+ if (row.deleteRequestedAt === null) {
10722
+ return reuseLiveAttachment({
10723
+ conversationId: args.conversationId,
10724
+ db: args.db,
10725
+ id: row.id,
10726
+ storage: args.storage,
10727
+ storageKey
10728
+ });
10729
+ }
10730
+ if (await isConversationPurged(args.db, args.conversationId)) {
10731
+ await bestEffortDelete(args.storage, storageKey);
10732
+ throw purgedConversationError(args.conversationId);
10733
+ }
10734
+ const revived = await args.db.db().update(juniorAttachments).set({
10735
+ deleteRequestedAt: null,
10736
+ storageKey
10737
+ }).where(
10738
+ and(
10739
+ eq(juniorAttachments.id, id),
10740
+ isNotNull(juniorAttachments.deleteRequestedAt)
10741
+ )
10742
+ ).returning({ id: juniorAttachments.id });
10743
+ if (revived.length > 0) {
10744
+ if (await isConversationPurged(args.db, args.conversationId)) {
10745
+ await rejectPurgedAttachmentWrite({
10746
+ conversationId: args.conversationId,
10747
+ db: args.db,
10748
+ id,
10749
+ now,
10750
+ storage: args.storage,
10751
+ storageKey
10752
+ });
10753
+ }
10754
+ if (row.storageKey !== storageKey) {
10755
+ await bestEffortDelete(args.storage, row.storageKey);
10756
+ }
10757
+ return { id };
10758
+ }
10759
+ const [afterRace] = await args.db.db().select({
10760
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
10761
+ id: juniorAttachments.id
10762
+ }).from(juniorAttachments).where(eq(juniorAttachments.id, id));
10763
+ if (afterRace?.deleteRequestedAt === null) {
10764
+ return reuseLiveAttachment({
10765
+ conversationId: args.conversationId,
10766
+ db: args.db,
10767
+ id: afterRace.id,
10768
+ storage: args.storage,
10769
+ storageKey
10770
+ });
10771
+ }
10772
+ await bestEffortDelete(args.storage, storageKey);
10773
+ throw new Error(`Attachment ${id} disappeared while reviving`);
10774
+ }
10775
+ async function storeAttachments(args) {
10776
+ const stored = [];
10777
+ for (const file of args.files) {
10778
+ stored.push(
10779
+ await storeAttachment({
10780
+ conversationId: args.conversationId,
10781
+ db: args.db,
10782
+ file,
10783
+ ...args.nowMs !== void 0 ? { nowMs: args.nowMs } : {},
10784
+ storage: args.storage
10785
+ })
10786
+ );
10787
+ }
10788
+ return stored;
10789
+ }
10790
+ async function requestAttachmentDeletion(db, conversationIds, nowMs) {
10791
+ if (conversationIds.length === 0) return;
10792
+ await db.db().update(juniorAttachments).set({ deleteRequestedAt: new Date(nowMs) }).where(
10793
+ and(
10794
+ inArray(juniorAttachments.conversationId, conversationIds),
10795
+ isNull(juniorAttachments.deleteRequestedAt)
10796
+ )
10797
+ );
10798
+ }
10799
+ async function collectAttachmentGarbage(args) {
10800
+ void args.nowMs;
10801
+ const rows = await args.db.db().select({
10802
+ id: juniorAttachments.id,
10803
+ storageKey: juniorAttachments.storageKey
10804
+ }).from(juniorAttachments).innerJoin(
10805
+ juniorConversations,
10806
+ eq(
10807
+ juniorAttachments.conversationId,
10808
+ juniorConversations.conversationId
10809
+ )
10810
+ ).where(
10811
+ and(
10812
+ eq(juniorAttachments.provider, args.storage.provider),
10813
+ or(
10814
+ isNotNull(juniorAttachments.deleteRequestedAt),
10815
+ isNotNull(juniorConversations.transcriptPurgedAt)
10816
+ )
10817
+ )
10818
+ ).orderBy(asc(juniorAttachments.createdAt), asc(juniorAttachments.id)).limit(args.limit ?? ATTACHMENT_GC_BATCH_LIMIT);
10819
+ if (rows.length === 0) return { deleted: 0 };
10820
+ await args.storage.delete(rows.map((row) => row.storageKey));
10821
+ const stillEligible = await args.db.db().select({ id: juniorAttachments.id }).from(juniorAttachments).innerJoin(
10822
+ juniorConversations,
10823
+ eq(
10824
+ juniorAttachments.conversationId,
10825
+ juniorConversations.conversationId
10826
+ )
10827
+ ).where(
10828
+ and(
10829
+ inArray(
10830
+ juniorAttachments.id,
10831
+ rows.map((row) => row.id)
10832
+ ),
10833
+ or(
10834
+ isNotNull(juniorAttachments.deleteRequestedAt),
10835
+ isNotNull(juniorConversations.transcriptPurgedAt)
10836
+ )
10837
+ )
10838
+ );
10839
+ if (stillEligible.length === 0) return { deleted: 0 };
10840
+ const removed = await args.db.db().delete(juniorAttachments).where(
10841
+ inArray(
10842
+ juniorAttachments.id,
10843
+ stillEligible.map((row) => row.id)
10844
+ )
10845
+ ).returning({
10846
+ id: juniorAttachments.id
10847
+ });
10848
+ return { deleted: removed.length };
10849
+ }
10850
+
10851
+ // src/chat/slack/tools/send-files.ts
10539
10852
  import { z as z42 } from "zod";
10540
10853
  var sendFilesResultSchema = juniorToolOutputSchema.extend({
10541
- target: z42.string().min(1),
10542
- channel_id: z42.string().min(1),
10543
10854
  deduplicated: z42.boolean().optional(),
10544
- file_count: z42.number().int().nonnegative(),
10545
- file_ids: z42.array(z42.string().min(1)).optional(),
10546
- thread_ts: z42.string().min(1)
10855
+ attachment_refs: z42.array(
10856
+ z42.object({
10857
+ id: z42.string().min(1),
10858
+ name: z42.string().min(1)
10859
+ })
10860
+ )
10547
10861
  });
10548
10862
  function normalizeFiles(files) {
10549
10863
  return files.map((file) => ({
@@ -10553,7 +10867,7 @@ function normalizeFiles(files) {
10553
10867
  }));
10554
10868
  }
10555
10869
  function fileContentDigest(data) {
10556
- return createHash4("sha256").update(data).digest("hex");
10870
+ return createHash5("sha256").update(data).digest("hex");
10557
10871
  }
10558
10872
  function fileOperationInput(files) {
10559
10873
  return files.map((file) => ({
@@ -10564,7 +10878,7 @@ function fileOperationInput(files) {
10564
10878
  sha256: fileContentDigest(file.data)
10565
10879
  }));
10566
10880
  }
10567
- function createSendFilesTool(context, state, materializeFile) {
10881
+ function createSendFilesTool(context, state, materializeFile, attachments) {
10568
10882
  return zodTool({
10569
10883
  annotations: {
10570
10884
  destructiveHint: false,
@@ -10606,22 +10920,26 @@ function createSendFilesTool(context, state, materializeFile) {
10606
10920
  deduplicated: true
10607
10921
  });
10608
10922
  }
10923
+ const stored = attachments ? await storeAttachments({
10924
+ conversationId: attachments.conversationId,
10925
+ db: attachments.db,
10926
+ files: materializedFiles,
10927
+ storage: attachments.storage
10928
+ }) : [];
10609
10929
  const uploads = materializedFiles.map((file) => ({
10610
10930
  data: file.data,
10611
10931
  filename: file.filename
10612
10932
  }));
10613
- const uploaded = await uploadFilesToConversation({
10933
+ await uploadFilesToConversation({
10614
10934
  channelId: activeChannelId,
10615
10935
  files: uploads,
10616
10936
  threadTs
10617
10937
  });
10618
- const fileIds = uploaded?.files?.map((file) => file.id).filter((id) => Boolean(id));
10619
10938
  const response = {
10620
- target: `${activeChannelId}:${threadTs}`,
10621
- channel_id: activeChannelId,
10622
- thread_ts: threadTs,
10623
- file_count: uploads.length,
10624
- ...fileIds ? { file_ids: fileIds } : {}
10939
+ attachment_refs: stored.map((attachment, index) => ({
10940
+ id: attachment.id,
10941
+ name: materializedFiles[index].filename
10942
+ }))
10625
10943
  };
10626
10944
  state.setOperationResult(operationKey, response);
10627
10945
  return response;
@@ -12004,7 +12322,7 @@ function createSlackThreadReadTool(context) {
12004
12322
  }
12005
12323
 
12006
12324
  // src/chat/tools/user-lookup.ts
12007
- import { and, eq, or, sql } from "drizzle-orm";
12325
+ import { and as and2, eq as eq2, or as or2, sql } from "drizzle-orm";
12008
12326
  import { alias } from "drizzle-orm/pg-core";
12009
12327
  import { z as z54 } from "zod";
12010
12328
  var linkedSlackIdentity = alias(juniorIdentities, "user_lookup_slack_identity");
@@ -12070,12 +12388,12 @@ async function storedSlackUserByEmail(teamId, email) {
12070
12388
  const emailNormalized = normalizeIdentityEmail(email);
12071
12389
  if (!emailNormalized) return void 0;
12072
12390
  const identities = await getSqlExecutor().db().select().from(juniorIdentities).where(
12073
- and(
12074
- eq(juniorIdentities.provider, "slack"),
12075
- eq(juniorIdentities.providerTenantId, teamId),
12076
- eq(juniorIdentities.kind, "user"),
12077
- eq(juniorIdentities.emailVerified, true),
12078
- eq(juniorIdentities.emailNormalized, emailNormalized)
12391
+ and2(
12392
+ eq2(juniorIdentities.provider, "slack"),
12393
+ eq2(juniorIdentities.providerTenantId, teamId),
12394
+ eq2(juniorIdentities.kind, "user"),
12395
+ eq2(juniorIdentities.emailVerified, true),
12396
+ eq2(juniorIdentities.emailNormalized, emailNormalized)
12079
12397
  )
12080
12398
  ).limit(2);
12081
12399
  if (identities.length > 1) {
@@ -12127,18 +12445,18 @@ async function resolveSlack(teamId, query) {
12127
12445
  async function resolveStoredProvider(teamId, provider, query) {
12128
12446
  const rows = await getSqlExecutor().db().select({ slack: linkedSlackIdentity }).from(juniorIdentities).innerJoin(
12129
12447
  linkedSlackIdentity,
12130
- and(
12131
- eq(linkedSlackIdentity.userId, juniorIdentities.userId),
12132
- eq(linkedSlackIdentity.kind, "user"),
12133
- eq(linkedSlackIdentity.provider, "slack"),
12134
- eq(linkedSlackIdentity.providerTenantId, teamId)
12448
+ and2(
12449
+ eq2(linkedSlackIdentity.userId, juniorIdentities.userId),
12450
+ eq2(linkedSlackIdentity.kind, "user"),
12451
+ eq2(linkedSlackIdentity.provider, "slack"),
12452
+ eq2(linkedSlackIdentity.providerTenantId, teamId)
12135
12453
  )
12136
12454
  ).where(
12137
- and(
12138
- eq(juniorIdentities.kind, "user"),
12139
- eq(juniorIdentities.provider, provider),
12140
- or(
12141
- eq(juniorIdentities.providerSubjectId, query),
12455
+ and2(
12456
+ eq2(juniorIdentities.kind, "user"),
12457
+ eq2(juniorIdentities.provider, provider),
12458
+ or2(
12459
+ eq2(juniorIdentities.providerSubjectId, query),
12142
12460
  sql`lower(${juniorIdentities.handle}) = lower(${query})`
12143
12461
  )
12144
12462
  )
@@ -13318,7 +13636,12 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
13318
13636
  tools.sendFiles = createSendFilesTool(
13319
13637
  slackContext,
13320
13638
  state,
13321
- (input) => readSandboxFileUpload(context.workspace, input)
13639
+ (input) => readSandboxFileUpload(context.workspace, input),
13640
+ context.conversationId && context.attachmentStorage ? {
13641
+ conversationId: context.conversationId,
13642
+ db: getSqlExecutor(),
13643
+ storage: context.attachmentStorage
13644
+ } : void 0
13322
13645
  );
13323
13646
  }
13324
13647
  tools.slackChannelListMessages = createSlackChannelListMessagesTool(slackContext);
@@ -14667,7 +14990,7 @@ function createMcpAuthOrchestration(input) {
14667
14990
  }
14668
14991
 
14669
14992
  // src/chat/egress/plugin.ts
14670
- import { randomUUID as randomUUID3 } from "crypto";
14993
+ import { randomUUID as randomUUID4 } from "crypto";
14671
14994
 
14672
14995
  // src/chat/sandbox/egress/credentials.ts
14673
14996
  var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
@@ -15404,8 +15727,8 @@ async function executeCredentialedEgressRequest(input) {
15404
15727
  function credentialContextForPluginEgress(credentialContext) {
15405
15728
  return {
15406
15729
  credentials: credentialContext,
15407
- contextId: randomUUID3(),
15408
- egressId: `plugin-egress:${randomUUID3()}`,
15730
+ contextId: randomUUID4(),
15731
+ egressId: `plugin-egress:${randomUUID4()}`,
15409
15732
  expiresAtMs: Date.now() + 30 * 60 * 1e3
15410
15733
  };
15411
15734
  }
@@ -15940,7 +16263,8 @@ async function wireAgentTools(args) {
15940
16263
  });
15941
16264
  const toolRuntimeContext = {
15942
16265
  ...commonToolRuntimeContext,
15943
- ...toolRoute
16266
+ ...toolRoute,
16267
+ attachmentStorage: args.run.environment?.attachmentStorage
15944
16268
  };
15945
16269
  const actionReview = createToolActionReview({
15946
16270
  context: {
@@ -18134,6 +18458,8 @@ export {
18134
18458
  deliverResourceEventSubscription,
18135
18459
  sanitizeScheduledTaskPrincipal,
18136
18460
  listThreadReplies,
18461
+ requestAttachmentDeletion,
18462
+ collectAttachmentGarbage,
18137
18463
  renderBlockText,
18138
18464
  renderAttachmentText,
18139
18465
  latestReportedProgress,
@@ -8,14 +8,14 @@ import {
8
8
  getConversationStore,
9
9
  parseDestination,
10
10
  sameDestination
11
- } from "./chunk-JJX77MB7.js";
11
+ } from "./chunk-IIO5PFZX.js";
12
12
  import {
13
13
  getDefaultRedisStateAdapterFor,
14
14
  getStateAdapter
15
- } from "./chunk-SDRJ2LE3.js";
15
+ } from "./chunk-MC57E5U5.js";
16
16
  import {
17
17
  getChatConfig
18
- } from "./chunk-XFZLQANQ.js";
18
+ } from "./chunk-GKC43TLS.js";
19
19
  import {
20
20
  parseStoredSlackActor
21
21
  } from "./chunk-V7YE4WCP.js";
@@ -23,7 +23,7 @@ import {
23
23
  isRecord,
24
24
  toOptionalNumber,
25
25
  toOptionalString
26
- } from "./chunk-7NYEB2TM.js";
26
+ } from "./chunk-X7W45B5G.js";
27
27
 
28
28
  // src/chat/task-execution/state.ts
29
29
  import { randomUUID } from "crypto";
@@ -4,18 +4,18 @@ import {
4
4
  } from "./chunk-EM225ITJ.js";
5
5
  import {
6
6
  getStateAdapter
7
- } from "./chunk-SDRJ2LE3.js";
7
+ } from "./chunk-MC57E5U5.js";
8
8
  import {
9
9
  toOptionalTrimmed
10
- } from "./chunk-XFZLQANQ.js";
10
+ } from "./chunk-GKC43TLS.js";
11
11
  import {
12
12
  pluginCatalogRuntime
13
- } from "./chunk-TVXAN43I.js";
13
+ } from "./chunk-EDJ64P2P.js";
14
14
  import {
15
15
  setSpanAttributes,
16
16
  setSpanStatus,
17
17
  withSpan
18
- } from "./chunk-7NYEB2TM.js";
18
+ } from "./chunk-X7W45B5G.js";
19
19
 
20
20
  // src/chat/sandbox/resources.ts
21
21
  var configuredResources;