@sentry/junior 0.94.0 → 0.96.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 (127) hide show
  1. package/dist/{agent-hooks-ZG7IDOIN.js → agent-hooks-5URSMY3O.js} +10 -11
  2. package/dist/api/people/list.js +8 -8
  3. package/dist/api/people/profile.js +8 -8
  4. package/dist/api/people/shared.d.ts +2 -2
  5. package/dist/api-reference.d.ts +2 -2
  6. package/dist/app.js +434 -199
  7. package/dist/{catalog-runtime-NZHMSMYC.js → catalog-runtime-MSMAYYIK.js} +4 -4
  8. package/dist/chat/advisor-request.d.ts +4 -0
  9. package/dist/chat/agent/request.d.ts +2 -0
  10. package/dist/chat/agent/resume.d.ts +2 -0
  11. package/dist/chat/conversations/history.d.ts +128 -0
  12. package/dist/chat/conversations/legacy-import.d.ts +38 -0
  13. package/dist/chat/conversations/messages.d.ts +39 -0
  14. package/dist/chat/conversations/projection.d.ts +94 -0
  15. package/dist/chat/conversations/retention.d.ts +36 -0
  16. package/dist/chat/conversations/sql/conversation-row.d.ts +14 -0
  17. package/dist/chat/conversations/sql/history.d.ts +4 -0
  18. package/dist/chat/conversations/sql/legacy-history-import.d.ts +55 -0
  19. package/dist/chat/conversations/sql/messages.d.ts +4 -0
  20. package/dist/chat/conversations/sql/migrations.d.ts +14 -3
  21. package/dist/chat/conversations/sql/purge.d.ts +53 -0
  22. package/dist/chat/conversations/sql/store.d.ts +7 -2
  23. package/dist/chat/conversations/store.d.ts +19 -0
  24. package/dist/chat/conversations/visible-compactions.d.ts +11 -0
  25. package/dist/chat/conversations/visible-messages.d.ts +47 -0
  26. package/dist/chat/db.d.ts +12 -1
  27. package/dist/chat/identities/sql.d.ts +1 -1
  28. package/dist/chat/logging.d.ts +4 -3
  29. package/dist/chat/plugins/auth/oauth-request.d.ts +2 -0
  30. package/dist/chat/resource-events/notification.d.ts +2 -0
  31. package/dist/chat/resource-events/store.d.ts +2 -1
  32. package/dist/chat/runtime/agent-run-outcome.d.ts +3 -0
  33. package/dist/chat/runtime/reply-executor.d.ts +8 -0
  34. package/dist/chat/runtime/thread-state.d.ts +2 -0
  35. package/dist/chat/sandbox/resources.d.ts +4 -0
  36. package/dist/chat/sentry.d.ts +1 -1
  37. package/dist/chat/services/mcp-auth-orchestration.d.ts +8 -0
  38. package/dist/chat/services/plugin-auth-orchestration.d.ts +12 -0
  39. package/dist/chat/services/turn-session-record.d.ts +14 -2
  40. package/dist/chat/sleep.d.ts +2 -0
  41. package/dist/chat/state/conversation.d.ts +7 -4
  42. package/dist/chat/state/session-log.d.ts +23 -1
  43. package/dist/chat/state/turn-session.d.ts +9 -1
  44. package/dist/chat/tool-support/private-trace-result.d.ts +4 -0
  45. package/dist/chat/tool-support/zod-tool.d.ts +1 -0
  46. package/dist/chat/tools/advisor/tool.d.ts +9 -4
  47. package/dist/chat/tools/definition.d.ts +2 -0
  48. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
  49. package/dist/chat/tools/system-time.d.ts +8 -0
  50. package/dist/chat/usage.d.ts +27 -13
  51. package/dist/chat/xml.d.ts +2 -0
  52. package/dist/{chunk-TNUUATT7.js → chunk-2NAZZPPS.js} +2 -2
  53. package/dist/{chunk-NYWPISSO.js → chunk-2XXEKR44.js} +1 -1
  54. package/dist/chunk-5C6XTTFI.js +1037 -0
  55. package/dist/{chunk-3DA7X2U3.js → chunk-6GWA276C.js} +15 -0
  56. package/dist/{chunk-LFLTJQVR.js → chunk-7FBGKXPE.js} +4 -0
  57. package/dist/{chunk-LNCREA5Z.js → chunk-AAZXTVJT.js} +4 -128
  58. package/dist/{chunk-GHGPTPBL.js → chunk-B7PUBQQM.js} +1 -1
  59. package/dist/{chunk-6H4PP63X.js → chunk-BYZWPRJA.js} +24 -14
  60. package/dist/chunk-CM5EZD5F.js +17 -0
  61. package/dist/{chunk-G3E7SCME.js → chunk-CZEX6KIQ.js} +23 -0
  62. package/dist/{chunk-YJ4OPVNA.js → chunk-DZENG5ML.js} +1 -1
  63. package/dist/{chunk-QGEWC27H.js → chunk-GQ3GK22G.js} +5 -7
  64. package/dist/{chunk-YYBX2BD5.js → chunk-GY34VGP5.js} +1 -1
  65. package/dist/{chunk-S6QKIGRM.js → chunk-I35CTGPO.js} +71 -29
  66. package/dist/{chunk-KRMANDHI.js → chunk-IYM2SIVG.js} +1489 -355
  67. package/dist/{chunk-BTH37NCU.js → chunk-L7CDEIVJ.js} +12 -4
  68. package/dist/chunk-MNTINMWR.js +865 -0
  69. package/dist/{chunk-MAVOJ54A.js → chunk-P3YFQLEN.js} +25 -388
  70. package/dist/{chunk-YTNPU7I2.js → chunk-PMNN6ST4.js} +2 -2
  71. package/dist/{chunk-PFXC67GJ.js → chunk-RAIB6WSG.js} +6 -80
  72. package/dist/{chunk-NFXSLTYG.js → chunk-RITD7E5L.js} +864 -62
  73. package/dist/{chunk-UUB3D2XI.js → chunk-S5KZ5LPG.js} +8 -10
  74. package/dist/{chunk-HDNWZMUN.js → chunk-UKHVMPX5.js} +112 -103
  75. package/dist/chunk-WM6KHPBQ.js +48 -0
  76. package/dist/{chunk-MC4ZZE4Q.js → chunk-YDT7SNCN.js} +1 -1
  77. package/dist/{chunk-NAPITCZ6.js → chunk-YDVEH3SB.js} +251 -6
  78. package/dist/cli/chat.js +25 -24
  79. package/dist/cli/check.js +5 -5
  80. package/dist/cli/main.js +1 -1
  81. package/dist/cli/plugins.js +12 -13
  82. package/dist/cli/snapshot-warmup.js +8 -9
  83. package/dist/cli/upgrade.js +79 -18
  84. package/dist/db/schema/agent-steps.d.ts +134 -0
  85. package/dist/db/schema/conversation-messages.d.ts +153 -0
  86. package/dist/{chat/conversations/sql → db}/schema/conversations.d.ts +34 -0
  87. package/dist/{chat/conversations/sql → db}/schema.d.ts +319 -8
  88. package/dist/db-2D6RMG67.js +24 -0
  89. package/dist/deployment.d.ts +2 -0
  90. package/dist/handlers/retention.d.ts +6 -0
  91. package/dist/instrumentation.js +17 -4
  92. package/dist/legacy-import-VZ2MEGP7.js +17 -0
  93. package/dist/nitro.js +14 -4
  94. package/dist/reporting/conversations/activity.d.ts +40 -0
  95. package/dist/reporting/conversations/context.d.ts +8 -0
  96. package/dist/reporting/conversations/details.d.ts +6 -0
  97. package/dist/reporting/conversations/index.d.ts +4 -0
  98. package/dist/reporting/conversations/shared.d.ts +9 -0
  99. package/dist/reporting/conversations/statistics.d.ts +4 -0
  100. package/dist/reporting/conversations/summaries.d.ts +20 -0
  101. package/dist/reporting/conversations/transcript.d.ts +28 -0
  102. package/dist/reporting/conversations/types.d.ts +191 -0
  103. package/dist/reporting/conversations.d.ts +1 -178
  104. package/dist/reporting.d.ts +5 -4
  105. package/dist/reporting.js +703 -837
  106. package/dist/{runner-XAZPJNAQ.js → runner-JZBRQP4F.js} +36 -42
  107. package/dist/{sentry-EGFOV3DH.js → sentry-JMWTS32N.js} +11 -1
  108. package/dist/{validation-KYAWETI4.js → validation-6XOOQGME.js} +5 -5
  109. package/migrations/README.md +32 -0
  110. package/migrations/meta/0000_snapshot.json +978 -0
  111. package/migrations/meta/_journal.json +13 -0
  112. package/package.json +10 -7
  113. package/dist/chat/sql/schema.d.ts +0 -1049
  114. package/dist/chat/state/conversation-details.d.ts +0 -47
  115. package/dist/chunk-7STN2ZSK.js +0 -250
  116. package/dist/chunk-LE5ANBUZ.js +0 -1040
  117. package/dist/chunk-QL66APYJ.js +0 -1377
  118. package/dist/db-YDN6AFJH.js +0 -18
  119. /package/dist/{chat/sql → db}/db.d.ts +0 -0
  120. /package/dist/{chat/sql → db}/executor.d.ts +0 -0
  121. /package/dist/{chat/sql → db}/neon.d.ts +0 -0
  122. /package/dist/{chat/sql → db}/postgres.d.ts +0 -0
  123. /package/dist/{chat/conversations/sql → db}/schema/destinations.d.ts +0 -0
  124. /package/dist/{chat/conversations/sql → db}/schema/identities.d.ts +0 -0
  125. /package/dist/{chat/conversations/sql → db}/schema/migrations.d.ts +0 -0
  126. /package/dist/{chat/conversations/sql → db}/schema/timestamps.d.ts +0 -0
  127. /package/dist/{chat/conversations/sql → db}/schema/users.d.ts +0 -0
package/dist/app.js CHANGED
@@ -1,23 +1,11 @@
1
1
  import {
2
- buildConversationContext,
3
2
  buildDeliveredTurnStatePatch,
4
- createConversationMemoryService,
5
- estimateTextTokens,
6
3
  finalizeFailedTurnReply,
7
- generateConversationId,
8
- getAgentContextCompactionTriggerTokens,
9
4
  getAgentTurnDiagnosticsAttributes,
10
- getConversationMessageSlackTs,
11
- getThreadTitleSourceMessage,
12
- isHumanConversationMessage,
13
- markConversationMessage,
14
- normalizeConversationText,
15
5
  processPluginTask,
16
6
  requireTurnFailureEventId,
17
- scheduleSessionCompletedPluginTasks,
18
- updateConversationStats,
19
- upsertConversationMessage
20
- } from "./chunk-MAVOJ54A.js";
7
+ scheduleSessionCompletedPluginTasks
8
+ } from "./chunk-P3YFQLEN.js";
21
9
  import {
22
10
  CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
23
11
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
@@ -41,12 +29,12 @@ import {
41
29
  requestConversationContinuation,
42
30
  requestConversationWork,
43
31
  startConversationWork
44
- } from "./chunk-QGEWC27H.js";
32
+ } from "./chunk-GQ3GK22G.js";
45
33
  import {
46
34
  getVercelConversationWorkQueue,
47
35
  resolveConversationWorkQueueTopic,
48
36
  verifyConversationQueueMessage
49
- } from "./chunk-NYWPISSO.js";
37
+ } from "./chunk-2XXEKR44.js";
50
38
  import {
51
39
  PLUGIN_TASK_QUEUE_TOPIC,
52
40
  createVercelQueueClient,
@@ -56,15 +44,13 @@ import {
56
44
  GET,
57
45
  buildSentryConversationUrl,
58
46
  conversationVisibilityFromSlackChannelType,
59
- initConversationContext,
60
47
  resolveSlackChannelTypeFromMessage,
61
- resolveSlackConversationContext,
62
- setConversationTitle
63
- } from "./chunk-LNCREA5Z.js";
48
+ resolveSlackConversationContext
49
+ } from "./chunk-AAZXTVJT.js";
64
50
  import {
65
51
  validatePluginEgressCredentialHooks,
66
52
  validatePluginRegistrations
67
- } from "./chunk-MC4ZZE4Q.js";
53
+ } from "./chunk-YDT7SNCN.js";
68
54
  import {
69
55
  defineJuniorPlugins,
70
56
  pluginCatalogConfigFromEnv,
@@ -77,6 +63,7 @@ import {
77
63
  SANDBOX_EGRESS_PROXY_PATH,
78
64
  addReactionToMessage,
79
65
  appendSlackLegacyAttachmentText,
66
+ buildSlackOutputMessage,
80
67
  buildSteeringPiMessage,
81
68
  createAgentRunner,
82
69
  createUserTokenStore,
@@ -85,15 +72,18 @@ import {
85
72
  deleteMcpServerSessionId,
86
73
  deleteMcpStoredOAuthCredentials,
87
74
  deliverResourceEventSubscription,
75
+ escapeSlackMrkdwnText,
88
76
  executeAgentRun,
89
77
  executeCredentialedEgressRequest,
90
78
  finalizeMcpAuthorization,
91
79
  findMatchingResourceEventSubscriptions,
92
80
  formatProviderLabel,
81
+ formatSlackLink,
93
82
  getConfigDefaults,
94
83
  getMcpStoredOAuthCredentials,
95
84
  listThreadReplies,
96
85
  normalizeSandboxEgressTracePropagationDomains,
86
+ normalizeSlackStatusText,
97
87
  parseOAuthStatePayload,
98
88
  parseSandboxEgressCredentialToken,
99
89
  postSlackMessage,
@@ -105,8 +95,10 @@ import {
105
95
  setConfigDefaults,
106
96
  shouldEmitDevAgentTrace,
107
97
  shouldPropagateSandboxEgressTrace,
108
- startOAuthFlow
109
- } from "./chunk-KRMANDHI.js";
98
+ splitSlackReplyText,
99
+ startOAuthFlow,
100
+ truncateStatusText
101
+ } from "./chunk-IYM2SIVG.js";
110
102
  import {
111
103
  CooperativeTurnYieldError,
112
104
  TurnInputCommitLostError,
@@ -129,37 +121,48 @@ import {
129
121
  markTurnFailed,
130
122
  mergeArtifactsState,
131
123
  persistCompletedSessionRecord,
124
+ persistThreadRuntimeState,
132
125
  persistThreadState,
133
126
  persistThreadStateById,
127
+ persistWithRetry,
134
128
  persistYieldSessionRecord,
135
129
  startActiveTurn
136
- } from "./chunk-HDNWZMUN.js";
130
+ } from "./chunk-UKHVMPX5.js";
137
131
  import {
138
- JUNIOR_THREAD_STATE_TTL_MS,
139
132
  coerceThreadConversationState
140
- } from "./chunk-PFXC67GJ.js";
141
- import "./chunk-6H4PP63X.js";
142
- import {
143
- buildSlackOutputMessage,
144
- escapeSlackMrkdwnText,
145
- formatSlackLink,
146
- normalizeSlackStatusText,
147
- splitSlackReplyText,
148
- truncateStatusText
149
- } from "./chunk-LE5ANBUZ.js";
133
+ } from "./chunk-RAIB6WSG.js";
134
+ import "./chunk-BYZWPRJA.js";
135
+ import "./chunk-CM5EZD5F.js";
136
+ import "./chunk-WM6KHPBQ.js";
150
137
  import {
151
138
  abandonAgentTurnSessionRecord,
152
139
  commitMessages,
153
- contextProvenance,
154
140
  failAgentTurnSessionRecord,
155
141
  getAgentTurnSessionRecord,
156
- instructionProvenanceFor,
157
142
  listAgentTurnSessionSummariesForConversation,
158
143
  loadProjection,
159
144
  loadProjectionWithProvenance,
160
145
  recordAgentTurnSessionSummary,
161
146
  recordAuthorizationCompleted
162
- } from "./chunk-QL66APYJ.js";
147
+ } from "./chunk-MNTINMWR.js";
148
+ import {
149
+ JUNIOR_THREAD_STATE_TTL_MS,
150
+ buildConversationContext,
151
+ createConversationMemoryService,
152
+ estimateTextTokens,
153
+ generateConversationId,
154
+ getAgentContextCompactionTriggerTokens,
155
+ getConversationMessageSlackTs,
156
+ getThreadTitleSourceMessage,
157
+ hydrateConversationMessages,
158
+ isHumanConversationMessage,
159
+ markConversationMessage,
160
+ normalizeConversationText,
161
+ persistConversationCompactions,
162
+ persistConversationMessages,
163
+ updateConversationStats,
164
+ upsertConversationMessage
165
+ } from "./chunk-5C6XTTFI.js";
163
166
  import {
164
167
  SlackActionError,
165
168
  bindSlackDirectCredentialSubject,
@@ -178,22 +181,29 @@ import {
178
181
  setPlugins,
179
182
  validatePlugins,
180
183
  verifySlackDirectCredentialSubject
181
- } from "./chunk-UUB3D2XI.js";
184
+ } from "./chunk-S5KZ5LPG.js";
182
185
  import {
183
186
  createPluginLogger,
184
187
  createPluginState
185
- } from "./chunk-TNUUATT7.js";
186
- import "./chunk-G3E7SCME.js";
188
+ } from "./chunk-2NAZZPPS.js";
187
189
  import {
188
- acquireActiveLock,
189
- getStateAdapter
190
- } from "./chunk-7STN2ZSK.js";
190
+ sleep
191
+ } from "./chunk-CZEX6KIQ.js";
191
192
  import {
193
+ contextProvenance,
194
+ getAgentStepStore,
192
195
  getConversationStore,
193
- getDb
194
- } from "./chunk-NFXSLTYG.js";
196
+ getDb,
197
+ getSqlExecutor,
198
+ instructionProvenanceFor,
199
+ juniorAgentSteps,
200
+ juniorConversationMessages,
201
+ juniorConversations,
202
+ juniorDestinations
203
+ } from "./chunk-RITD7E5L.js";
195
204
  import {
196
205
  FUNCTION_TIMEOUT_BUFFER_SECONDS,
206
+ acquireActiveLock,
197
207
  botConfig,
198
208
  completeObject,
199
209
  completeText,
@@ -205,35 +215,36 @@ import {
205
215
  getSlackClientSecret,
206
216
  getSlackReactionConfig,
207
217
  getSlackSigningSecret,
218
+ getStateAdapter,
208
219
  isProviderRetryError,
209
220
  normalizeSlackEmojiName,
210
221
  setSlackReactionConfig,
211
222
  stripRuntimeTurnContext,
212
223
  trimTrailingAssistantMessages,
213
224
  unwrapCurrentInstruction
214
- } from "./chunk-NAPITCZ6.js";
225
+ } from "./chunk-YDVEH3SB.js";
215
226
  import {
216
227
  parseSlackThreadId,
217
228
  readSlackRawMessageContext,
218
229
  resolveSlackChannelIdFromMessage,
219
230
  resolveSlackChannelIdFromThreadId
220
- } from "./chunk-YYBX2BD5.js";
231
+ } from "./chunk-GY34VGP5.js";
221
232
  import {
222
233
  createSlackDestination,
223
234
  destinationKey,
224
235
  parseDestination,
225
236
  requireSlackDestination,
226
237
  sameDestination
227
- } from "./chunk-GHGPTPBL.js";
238
+ } from "./chunk-B7PUBQQM.js";
228
239
  import {
229
240
  discoverSkills
230
- } from "./chunk-YTNPU7I2.js";
241
+ } from "./chunk-PMNN6ST4.js";
231
242
  import {
232
243
  buildOAuthTokenRequest,
233
244
  hasRequiredOAuthScope,
234
245
  parseOAuthTokenResponse,
235
246
  pluginCatalogRuntime
236
- } from "./chunk-BTH37NCU.js";
247
+ } from "./chunk-L7CDEIVJ.js";
237
248
  import {
238
249
  buildTurnFailureResponse,
239
250
  createActor,
@@ -253,16 +264,15 @@ import {
253
264
  setSpanAttributes,
254
265
  setSpanStatus,
255
266
  setTags,
256
- toStoredSlackActor,
257
267
  withContext,
258
268
  withSpan
259
- } from "./chunk-S6QKIGRM.js";
269
+ } from "./chunk-I35CTGPO.js";
260
270
  import {
261
271
  toOptionalString
262
272
  } from "./chunk-OB42YVAE.js";
263
273
  import {
264
274
  continueTrace
265
- } from "./chunk-3DA7X2U3.js";
275
+ } from "./chunk-6GWA276C.js";
266
276
  import {
267
277
  homeDir
268
278
  } from "./chunk-6APU57E6.js";
@@ -270,7 +280,7 @@ import {
270
280
  JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
271
281
  parseSlackChannelId,
272
282
  parseSlackMessageTs
273
- } from "./chunk-LFLTJQVR.js";
283
+ } from "./chunk-7FBGKXPE.js";
274
284
  import "./chunk-MLKGABMK.js";
275
285
 
276
286
  // src/app.ts
@@ -449,27 +459,6 @@ async function postSlackApiReplyPosts(args) {
449
459
  return lastPostedMessageTs;
450
460
  }
451
461
 
452
- // src/chat/services/persist-retry.ts
453
- var PERSIST_ATTEMPTS = 3;
454
- function sleep(ms) {
455
- return new Promise((resolve) => setTimeout(resolve, ms));
456
- }
457
- async function persistWithRetry(persist) {
458
- let lastError;
459
- for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
460
- try {
461
- await persist();
462
- return;
463
- } catch (error) {
464
- lastError = error;
465
- if (attempt < PERSIST_ATTEMPTS) {
466
- await sleep(attempt * 100);
467
- }
468
- }
469
- }
470
- throw lastError;
471
- }
472
-
473
462
  // src/chat/agent-dispatch/signing.ts
474
463
  import { createHmac, timingSafeEqual } from "crypto";
475
464
  var DISPATCH_CALLBACK_PATH = "/api/internal/agent-dispatch";
@@ -970,6 +959,7 @@ async function runAgentDispatchSlice(callback, deps) {
970
959
  dispatch = startedDispatch;
971
960
  const persisted = await getPersistedThreadState(conversationId);
972
961
  const conversation = coerceThreadConversationState(persisted);
962
+ await hydrateConversationMessages({ conversation, conversationId });
973
963
  const deliveredMessage = conversation.messages.find(
974
964
  (message) => message.id === getAssistantMessageId(dispatch) && message.meta?.replied === true && typeof message.meta.slackTs === "string"
975
965
  );
@@ -993,6 +983,7 @@ async function runAgentDispatchSlice(callback, deps) {
993
983
  dispatch,
994
984
  nowMs
995
985
  });
986
+ await persistConversationMessages({ conversation, conversationId });
996
987
  const conversationContext = buildConversationContext(conversation, {
997
988
  excludeMessageId: userMessageId
998
989
  });
@@ -1000,7 +991,9 @@ async function runAgentDispatchSlice(callback, deps) {
1000
991
  input: {
1001
992
  messageText: dispatch.input,
1002
993
  conversationContext,
1003
- piMessages: conversation.piMessages
994
+ // Pi history for redelivered dispatch slices comes from the SQL
995
+ // step-store projection, not a thread-state mirror.
996
+ piMessages: await loadProjection({ conversationId })
1004
997
  },
1005
998
  routing: {
1006
999
  credentialContext: {
@@ -1147,6 +1140,7 @@ async function runAgentDispatchSlice(callback, deps) {
1147
1140
  messages: reply.piMessages,
1148
1141
  durationMs: reply.diagnostics.durationMs,
1149
1142
  usage: reply.diagnostics.usage,
1143
+ reasoningLevel: reply.diagnostics.thinkingLevel,
1150
1144
  destination: dispatch.destination,
1151
1145
  source: dispatch.source,
1152
1146
  actor: dispatch.actor,
@@ -1262,42 +1256,31 @@ function now() {
1262
1256
  return Date.now();
1263
1257
  }
1264
1258
  async function recordExecutionMetadata(args) {
1265
- try {
1266
- const conversation = await getConversation({
1267
- conversationId: args.conversationId,
1268
- state: args.state
1269
- });
1270
- if (!conversation) {
1271
- return;
1272
- }
1273
- await metadataStore(args).recordExecution({
1274
- channelName: conversation.channelName,
1275
- conversationId: conversation.conversationId,
1276
- createdAtMs: conversation.createdAtMs,
1277
- destination: conversation.destination,
1278
- execution: {
1279
- lastCheckpointAtMs: conversation.execution.lastCheckpointAtMs,
1280
- lastEnqueuedAtMs: conversation.execution.lastEnqueuedAtMs,
1281
- runId: conversation.execution.runId,
1282
- status: conversation.execution.status,
1283
- updatedAtMs: conversation.execution.updatedAtMs
1284
- },
1285
- lastActivityAtMs: conversation.lastActivityAtMs,
1286
- actor: conversation.actor,
1287
- source: conversation.source,
1288
- title: conversation.title,
1289
- updatedAtMs: conversation.updatedAtMs
1290
- });
1291
- } catch (error) {
1292
- logWarn(
1293
- "conversation_execution_metadata_update_failed",
1294
- { conversationId: args.conversationId },
1295
- {
1296
- "exception.message": error instanceof Error ? error.message : String(error)
1297
- },
1298
- "Failed to update conversation execution metadata"
1299
- );
1259
+ const conversation = await getConversation({
1260
+ conversationId: args.conversationId,
1261
+ state: args.state
1262
+ });
1263
+ if (!conversation) {
1264
+ return;
1300
1265
  }
1266
+ await metadataStore(args).recordExecution({
1267
+ channelName: conversation.channelName,
1268
+ conversationId: conversation.conversationId,
1269
+ createdAtMs: conversation.createdAtMs,
1270
+ destination: conversation.destination,
1271
+ execution: {
1272
+ lastCheckpointAtMs: conversation.execution.lastCheckpointAtMs,
1273
+ lastEnqueuedAtMs: conversation.execution.lastEnqueuedAtMs,
1274
+ runId: conversation.execution.runId,
1275
+ status: conversation.execution.status,
1276
+ updatedAtMs: conversation.execution.updatedAtMs
1277
+ },
1278
+ lastActivityAtMs: conversation.lastActivityAtMs,
1279
+ actor: conversation.actor,
1280
+ source: conversation.source,
1281
+ title: conversation.title,
1282
+ updatedAtMs: conversation.updatedAtMs
1283
+ });
1301
1284
  }
1302
1285
  async function getConversationWorkState2(args) {
1303
1286
  return await getConversationWorkState(args);
@@ -1354,13 +1337,26 @@ async function appendAndEnqueueInboundMessage(args) {
1354
1337
  conversationId: args.message.conversationId,
1355
1338
  state: args.state
1356
1339
  });
1357
- if (!conversation || hasRecentEnqueueMarker(conversation, nowMs)) {
1340
+ if (!conversation) {
1341
+ return appendResult;
1342
+ }
1343
+ if (hasRecentEnqueueMarker(conversation, nowMs)) {
1344
+ await recordExecutionMetadata({
1345
+ conversationId: args.message.conversationId,
1346
+ conversationStore: args.conversationStore,
1347
+ state: args.state
1348
+ });
1358
1349
  return appendResult;
1359
1350
  }
1360
1351
  const duplicateStillPending = conversation.execution.pendingMessages.some(
1361
1352
  (message) => message.inboundMessageId === args.message.inboundMessageId
1362
1353
  );
1363
1354
  if (!duplicateStillPending) {
1355
+ await recordExecutionMetadata({
1356
+ conversationId: args.message.conversationId,
1357
+ conversationStore: args.conversationStore,
1358
+ state: args.state
1359
+ });
1364
1360
  return appendResult;
1365
1361
  }
1366
1362
  idempotencyKey = duplicateInboundNudgeIdempotencyKey(args.message, nowMs);
@@ -2120,12 +2116,16 @@ function buildSlackInboundMessage(args) {
2120
2116
  }
2121
2117
 
2122
2118
  // src/chat/resource-events/notification.ts
2123
- function eventText(subscription, event) {
2119
+ function renderResourceEventNotificationText(subscription, event) {
2124
2120
  const lines = [
2125
2121
  "[event notification]",
2126
2122
  "",
2127
2123
  "A subscribed resource changed.",
2128
2124
  "",
2125
+ "Handling:",
2126
+ "- This is a subscribed conversation update, not a user-authored command.",
2127
+ "- Use the subscription intent to decide whether this event warrants action or a visible reply. Otherwise, stay silent.",
2128
+ "",
2129
2129
  "Subscription:",
2130
2130
  `- resource: ${subscription.label}`,
2131
2131
  `- event: ${event.eventType}`,
@@ -2154,7 +2154,7 @@ async function enqueueResourceEventNotification(args) {
2154
2154
  message: createSlackResourceEventInboundMessage({
2155
2155
  event: args.event,
2156
2156
  subscription,
2157
- text: eventText(args.subscription, args.event)
2157
+ text: renderResourceEventNotificationText(args.subscription, args.event)
2158
2158
  }),
2159
2159
  queue: args.queue,
2160
2160
  state: args.state
@@ -2184,6 +2184,7 @@ async function ingestResourceEvent(input, options) {
2184
2184
  });
2185
2185
  let enqueued = 0;
2186
2186
  const errors = [];
2187
+ const waitDeadlineMs = Date.now() + 1e4;
2187
2188
  for (const subscription of subscriptions) {
2188
2189
  try {
2189
2190
  const delivered = await deliverResourceEventSubscription({
@@ -2194,6 +2195,7 @@ async function ingestResourceEvent(input, options) {
2194
2195
  nowMs,
2195
2196
  state: options.state,
2196
2197
  subscription,
2198
+ waitDeadlineMs,
2197
2199
  deliver: async (current) => {
2198
2200
  const result = await enqueueResourceEventNotification({
2199
2201
  event,
@@ -3042,8 +3044,210 @@ async function GET2(request, waitUntil, options = {}) {
3042
3044
  return new Response("Accepted", { status: 202 });
3043
3045
  }
3044
3046
 
3045
- // src/handlers/mcp-oauth-callback.ts
3046
- import { THREAD_STATE_TTL_MS } from "chat";
3047
+ // src/handlers/retention.ts
3048
+ import { timingSafeEqual as timingSafeEqual4 } from "crypto";
3049
+
3050
+ // src/chat/conversations/sql/purge.ts
3051
+ import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
3052
+ async function conversationTreeIds(executor, rootConversationId) {
3053
+ const all = /* @__PURE__ */ new Set([rootConversationId]);
3054
+ let frontier = [rootConversationId];
3055
+ while (frontier.length > 0) {
3056
+ const children = await executor.db().select({ id: juniorConversations.conversationId }).from(juniorConversations).where(inArray(juniorConversations.parentConversationId, frontier));
3057
+ frontier = [];
3058
+ for (const child of children) {
3059
+ if (!all.has(child.id)) {
3060
+ all.add(child.id);
3061
+ frontier.push(child.id);
3062
+ }
3063
+ }
3064
+ }
3065
+ return [...all];
3066
+ }
3067
+ async function selectExpiredRoots(executor, args) {
3068
+ const publicCutoff = new Date(args.nowMs - args.publicWindowMs).toISOString();
3069
+ const privateCutoff = new Date(
3070
+ args.nowMs - args.privateWindowMs
3071
+ ).toISOString();
3072
+ const cutoff = sql`case when ${juniorDestinations.visibility} = 'public' then ${publicCutoff}::timestamptz else ${privateCutoff}::timestamptz end`;
3073
+ const hasTreeWork = sql`exists (
3074
+ with recursive conversation_tree(conversation_id) as (
3075
+ select ${juniorConversations.conversationId}
3076
+ union all
3077
+ select child.conversation_id
3078
+ from junior_conversations child
3079
+ join conversation_tree parent on child.parent_conversation_id = parent.conversation_id
3080
+ )
3081
+ select 1
3082
+ from conversation_tree tree
3083
+ where exists (
3084
+ select 1 from junior_agent_steps steps
3085
+ where steps.conversation_id = tree.conversation_id
3086
+ )
3087
+ or exists (
3088
+ select 1 from junior_conversation_messages messages
3089
+ where messages.conversation_id = tree.conversation_id
3090
+ )
3091
+ or (
3092
+ ${juniorDestinations.visibility} is distinct from 'public'
3093
+ and exists (
3094
+ select 1 from junior_conversations metadata
3095
+ where metadata.conversation_id = tree.conversation_id
3096
+ and (
3097
+ metadata.title is not null
3098
+ or metadata.channel_name is not null
3099
+ or metadata.actor_json is not null
3100
+ )
3101
+ )
3102
+ )
3103
+ )`;
3104
+ const rows = await executor.db().select({
3105
+ conversationId: juniorConversations.conversationId,
3106
+ visibility: juniorDestinations.visibility
3107
+ }).from(juniorConversations).leftJoin(
3108
+ juniorDestinations,
3109
+ eq(juniorDestinations.id, juniorConversations.destinationId)
3110
+ ).where(
3111
+ and(
3112
+ isNull(juniorConversations.parentConversationId),
3113
+ sql`${juniorConversations.lastActivityAt} < ${cutoff}`,
3114
+ hasTreeWork
3115
+ )
3116
+ ).orderBy(
3117
+ asc(juniorConversations.lastActivityAt),
3118
+ asc(juniorConversations.conversationId)
3119
+ ).limit(Math.max(0, args.limit));
3120
+ return rows.map((row) => ({
3121
+ conversationId: row.conversationId,
3122
+ visibility: row.visibility
3123
+ }));
3124
+ }
3125
+ async function purgeConversationTree(executor, args) {
3126
+ return await executor.transaction(async () => {
3127
+ const roots = await executor.db().select({
3128
+ destinationId: juniorConversations.destinationId,
3129
+ lastActivityAt: juniorConversations.lastActivityAt,
3130
+ parentConversationId: juniorConversations.parentConversationId
3131
+ }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.rootConversationId)).for("update");
3132
+ const root = roots[0];
3133
+ if (!root || args.retention && root.parentConversationId !== null) {
3134
+ return { purged: false, conversations: 0 };
3135
+ }
3136
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, root.destinationId)) : [];
3137
+ const isPublic = destinations[0]?.visibility === "public";
3138
+ if (args.retention) {
3139
+ const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
3140
+ if (root.lastActivityAt.getTime() >= args.nowMs - windowMs) {
3141
+ return { purged: false, conversations: 0 };
3142
+ }
3143
+ }
3144
+ const ids = await conversationTreeIds(executor, args.rootConversationId);
3145
+ await executor.db().delete(juniorAgentSteps).where(inArray(juniorAgentSteps.conversationId, ids));
3146
+ await executor.db().delete(juniorConversationMessages).where(inArray(juniorConversationMessages.conversationId, ids));
3147
+ await executor.db().update(juniorConversations).set({
3148
+ transcriptPurgedAt: new Date(args.nowMs),
3149
+ ...(args.retention ? !isPublic : args.scrubMetadata) ? { title: null, channelName: null, actor: null } : {}
3150
+ }).where(inArray(juniorConversations.conversationId, ids));
3151
+ return { purged: true, conversations: ids.length };
3152
+ });
3153
+ }
3154
+
3155
+ // src/chat/conversations/retention.ts
3156
+ var DAY_MS = 24 * 60 * 60 * 1e3;
3157
+ var CONTENT_RETENTION_MS = {
3158
+ public: 90 * DAY_MS,
3159
+ private: 14 * DAY_MS
3160
+ };
3161
+ var RETENTION_BATCH_LIMIT = 200;
3162
+ async function runRetentionPurge(executor, args) {
3163
+ const limit = args.limit ?? RETENTION_BATCH_LIMIT;
3164
+ const startedAtMs = Date.now();
3165
+ const roots = await selectExpiredRoots(executor, {
3166
+ nowMs: args.nowMs,
3167
+ publicWindowMs: CONTENT_RETENTION_MS.public,
3168
+ privateWindowMs: CONTENT_RETENTION_MS.private,
3169
+ limit
3170
+ });
3171
+ let purged = 0;
3172
+ let failed = 0;
3173
+ let conversations = 0;
3174
+ for (const root of roots) {
3175
+ try {
3176
+ const result = await purgeConversationTree(executor, {
3177
+ rootConversationId: root.conversationId,
3178
+ nowMs: args.nowMs,
3179
+ retention: {
3180
+ publicWindowMs: CONTENT_RETENTION_MS.public,
3181
+ privateWindowMs: CONTENT_RETENTION_MS.private
3182
+ }
3183
+ });
3184
+ if (result.purged) {
3185
+ purged += 1;
3186
+ conversations += result.conversations;
3187
+ }
3188
+ } catch (error) {
3189
+ failed += 1;
3190
+ logException(
3191
+ error,
3192
+ "retention_purge_tree_failed",
3193
+ { conversationId: root.conversationId },
3194
+ {},
3195
+ "Retention purge failed for one conversation tree"
3196
+ );
3197
+ }
3198
+ }
3199
+ logInfo(
3200
+ "retention_purge_completed",
3201
+ {},
3202
+ {
3203
+ "app.retention.scanned": roots.length,
3204
+ "app.retention.purged": purged,
3205
+ "app.retention.failed": failed,
3206
+ "app.retention.conversations": conversations,
3207
+ "app.retention.duration_ms": Date.now() - startedAtMs
3208
+ },
3209
+ "Retention purge batch completed"
3210
+ );
3211
+ return { scanned: roots.length, purged, failed, conversations };
3212
+ }
3213
+
3214
+ // src/handlers/retention.ts
3215
+ function getRetentionSecret() {
3216
+ return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
3217
+ }
3218
+ function verifyRetentionRequest(request) {
3219
+ const secret = getRetentionSecret();
3220
+ if (!secret) {
3221
+ return false;
3222
+ }
3223
+ const authorization = request.headers.get("authorization")?.trim();
3224
+ if (!authorization?.startsWith("Bearer ")) {
3225
+ return false;
3226
+ }
3227
+ const actual = Buffer.from(authorization.slice("Bearer ".length));
3228
+ const expected = Buffer.from(secret);
3229
+ return actual.length === expected.length && timingSafeEqual4(actual, expected);
3230
+ }
3231
+ async function GET3(request) {
3232
+ if (!verifyRetentionRequest(request)) {
3233
+ return new Response("Unauthorized", { status: 401 });
3234
+ }
3235
+ try {
3236
+ const result = await runRetentionPurge(getSqlExecutor(), {
3237
+ nowMs: Date.now()
3238
+ });
3239
+ return Response.json(result, { status: 200 });
3240
+ } catch (error) {
3241
+ logException(
3242
+ error,
3243
+ "retention_run_failed",
3244
+ {},
3245
+ {},
3246
+ "Retention purge run failed"
3247
+ );
3248
+ return new Response("Retention purge failed", { status: 500 });
3249
+ }
3250
+ }
3047
3251
 
3048
3252
  // src/chat/runtime/turn-user-message.ts
3049
3253
  function getTurnUserMessage(conversation, sessionId) {
@@ -4184,6 +4388,10 @@ function completeAuthPauseTurn(args) {
4184
4388
  async function persistAuthPauseTurnState(args) {
4185
4389
  const currentState = await getPersistedThreadState(args.threadStateId);
4186
4390
  const conversation = coerceThreadConversationState(currentState);
4391
+ await hydrateConversationMessages({
4392
+ conversation,
4393
+ conversationId: args.threadStateId
4394
+ });
4187
4395
  completeAuthPauseTurn({
4188
4396
  conversation,
4189
4397
  sessionId: args.sessionId
@@ -4292,6 +4500,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
4292
4500
  const threadId = `slack:${channelId}:${threadTs}`;
4293
4501
  const currentState = await getPersistedThreadState(threadId);
4294
4502
  const conversation = coerceThreadConversationState(currentState);
4503
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4295
4504
  const artifacts = coerceThreadArtifactsState(currentState);
4296
4505
  const userMessage2 = getTurnUserMessage(conversation, sessionId);
4297
4506
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4330,6 +4539,7 @@ async function persistFailedReplyState(channelId, threadTs, sessionId, expectedV
4330
4539
  const threadId = `slack:${channelId}:${threadTs}`;
4331
4540
  const currentState = await getPersistedThreadState(threadId);
4332
4541
  const conversation = coerceThreadConversationState(currentState);
4542
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4333
4543
  clearPendingAuth(conversation, sessionId);
4334
4544
  markTurnFailed({
4335
4545
  conversation,
@@ -4361,6 +4571,7 @@ async function resumeAuthorizedMcpTurn(args) {
4361
4571
  const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4362
4572
  const currentState = await getPersistedThreadState(threadId);
4363
4573
  const conversation = coerceThreadConversationState(currentState);
4574
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4364
4575
  const pendingAuth = getConversationPendingAuth({
4365
4576
  conversation,
4366
4577
  kind: "mcp",
@@ -4397,6 +4608,10 @@ async function resumeAuthorizedMcpTurn(args) {
4397
4608
  beforeStart: async () => {
4398
4609
  const lockedState = await getPersistedThreadState(threadId);
4399
4610
  const lockedConversation = coerceThreadConversationState(lockedState);
4611
+ await hydrateConversationMessages({
4612
+ conversation: lockedConversation,
4613
+ conversationId: threadId
4614
+ });
4400
4615
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4401
4616
  const lockedPendingAuth = getConversationPendingAuth({
4402
4617
  conversation: lockedConversation,
@@ -4480,8 +4695,7 @@ async function resumeAuthorizedMcpTurn(args) {
4480
4695
  authorizationId: mcpAuthorizationId({
4481
4696
  provider,
4482
4697
  sessionId: lockedSessionId
4483
- }),
4484
- ttlMs: THREAD_STATE_TTL_MS
4698
+ })
4485
4699
  });
4486
4700
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
4487
4701
  return {
@@ -4490,7 +4704,9 @@ async function resumeAuthorizedMcpTurn(args) {
4490
4704
  replyContext: {
4491
4705
  input: {
4492
4706
  conversationContext: lockedConversationContext,
4493
- piMessages: lockedConversation.piMessages,
4707
+ // Pi history is SQL-authoritative: the resumed run reads its
4708
+ // session record first and falls back to the step projection.
4709
+ piMessages: await loadProjection({ conversationId: threadId }),
4494
4710
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
4495
4711
  },
4496
4712
  routing: {
@@ -4589,7 +4805,7 @@ async function resumeAuthorizedMcpTurn(args) {
4589
4805
  }
4590
4806
  });
4591
4807
  }
4592
- async function GET3(request, provider, waitUntil, options) {
4808
+ async function GET4(request, provider, waitUntil, options) {
4593
4809
  const url = new URL(request.url);
4594
4810
  const state = url.searchParams.get("state")?.trim();
4595
4811
  const code = url.searchParams.get("code")?.trim();
@@ -4636,9 +4852,6 @@ async function GET3(request, provider, waitUntil, options) {
4636
4852
  }
4637
4853
  }
4638
4854
 
4639
- // src/handlers/oauth-callback.ts
4640
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
4641
-
4642
4855
  // src/chat/slack/app-home.ts
4643
4856
  import fs from "fs";
4644
4857
  import path from "path";
@@ -4801,6 +5014,10 @@ function htmlErrorResponse(title, message, status) {
4801
5014
  async function persistCompletedOAuthReplyState(args) {
4802
5015
  const currentState = await getPersistedThreadState(args.conversationId);
4803
5016
  const conversation = coerceThreadConversationState(currentState);
5017
+ await hydrateConversationMessages({
5018
+ conversation,
5019
+ conversationId: args.conversationId
5020
+ });
4804
5021
  const artifacts = coerceThreadArtifactsState(currentState);
4805
5022
  const userMessage2 = getTurnUserMessage(conversation, args.sessionId);
4806
5023
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4841,6 +5058,10 @@ async function failSessionRecordBestEffort2(args) {
4841
5058
  async function persistFailedOAuthReplyState(args) {
4842
5059
  const currentState = await getPersistedThreadState(args.conversationId);
4843
5060
  const conversation = coerceThreadConversationState(currentState);
5061
+ await hydrateConversationMessages({
5062
+ conversation,
5063
+ conversationId: args.conversationId
5064
+ });
4844
5065
  clearPendingAuth(conversation, args.sessionId);
4845
5066
  markTurnFailed({
4846
5067
  conversation,
@@ -4872,6 +5093,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4872
5093
  stored.resumeConversationId
4873
5094
  );
4874
5095
  const conversation = coerceThreadConversationState(currentState);
5096
+ await hydrateConversationMessages({
5097
+ conversation,
5098
+ conversationId: stored.resumeConversationId
5099
+ });
4875
5100
  const pendingAuth = getConversationPendingAuth({
4876
5101
  conversation,
4877
5102
  kind: "plugin",
@@ -4927,6 +5152,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4927
5152
  stored.resumeConversationId
4928
5153
  );
4929
5154
  const lockedConversation = coerceThreadConversationState(lockedState);
5155
+ await hydrateConversationMessages({
5156
+ conversation: lockedConversation,
5157
+ conversationId: stored.resumeConversationId
5158
+ });
4930
5159
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4931
5160
  const lockedPendingAuth = getConversationPendingAuth({
4932
5161
  conversation: lockedConversation,
@@ -5011,8 +5240,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5011
5240
  authorizationId: pluginAuthorizationId({
5012
5241
  provider: stored.provider,
5013
5242
  sessionId: lockedSessionId
5014
- }),
5015
- ttlMs: THREAD_STATE_TTL_MS2
5243
+ })
5016
5244
  });
5017
5245
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
5018
5246
  return {
@@ -5021,7 +5249,11 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5021
5249
  replyContext: {
5022
5250
  input: {
5023
5251
  conversationContext: lockedConversationContext,
5024
- piMessages: lockedConversation.piMessages,
5252
+ // Pi history is SQL-authoritative: the resumed run reads its
5253
+ // session record first and falls back to the step projection.
5254
+ piMessages: await loadProjection({
5255
+ conversationId: stored.resumeConversationId
5256
+ }),
5025
5257
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
5026
5258
  },
5027
5259
  routing: {
@@ -5124,6 +5356,7 @@ async function resumePendingOAuthMessage(stored, options) {
5124
5356
  const conversation = coerceThreadConversationState(
5125
5357
  await getPersistedThreadState(threadId)
5126
5358
  );
5359
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
5127
5360
  const latestUserMessage = [...conversation.messages].reverse().find((message) => message.role === "user");
5128
5361
  const conversationContext = buildConversationContext(conversation, {
5129
5362
  excludeMessageId: latestUserMessage?.id
@@ -5144,7 +5377,8 @@ async function resumePendingOAuthMessage(stored, options) {
5144
5377
  replyContext: {
5145
5378
  input: {
5146
5379
  conversationContext,
5147
- piMessages: conversation.piMessages
5380
+ // Pi history is SQL-authoritative via the step-store projection.
5381
+ piMessages: await loadProjection({ conversationId: threadId })
5148
5382
  },
5149
5383
  routing: {
5150
5384
  credentialContext: {
@@ -5178,7 +5412,7 @@ async function resumePendingOAuthMessage(stored, options) {
5178
5412
  }
5179
5413
  });
5180
5414
  }
5181
- async function GET4(request, provider, waitUntil, options) {
5415
+ async function GET5(request, provider, waitUntil, options) {
5182
5416
  const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
5183
5417
  if (!providerConfig) {
5184
5418
  return htmlErrorResponse(
@@ -8305,7 +8539,6 @@ function createSlackTurnRuntime(deps) {
8305
8539
  }
8306
8540
 
8307
8541
  // src/chat/services/context-compaction.ts
8308
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
8309
8542
  import {
8310
8543
  estimateContextTokens,
8311
8544
  estimateTokens
@@ -8363,6 +8596,10 @@ function userMessage(text) {
8363
8596
  timestamp: Date.now()
8364
8597
  };
8365
8598
  }
8599
+ function piMessageTimestamp(message) {
8600
+ const timestamp = message.timestamp;
8601
+ return typeof timestamp === "number" ? timestamp : Date.now();
8602
+ }
8366
8603
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
8367
8604
  const selected = [];
8368
8605
  let remaining = maxTokens;
@@ -8545,6 +8782,7 @@ async function maybeCompactWithDeps(args, deps) {
8545
8782
  });
8546
8783
  }
8547
8784
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
8785
+ const stepStore = getAgentStepStore();
8548
8786
  const sourceProjection = await loadProjectionWithProvenance({
8549
8787
  conversationId: args.conversationId
8550
8788
  });
@@ -8556,14 +8794,17 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
8556
8794
  userMessage(`${COMPACTION_SUMMARY_PREFIX}
8557
8795
  ${summary}`)
8558
8796
  ];
8559
- await commitMessages({
8560
- conversationId: args.conversationId,
8561
- messages: replacement,
8562
- provenance: buildReplacementProvenance({
8563
- retained,
8564
- sourceProvenance: sourceProjection.provenance
8565
- }),
8566
- ttlMs: THREAD_STATE_TTL_MS3
8797
+ const replacementProvenance = buildReplacementProvenance({
8798
+ retained,
8799
+ sourceProvenance: sourceProjection.provenance
8800
+ });
8801
+ await stepStore.startEpoch(args.conversationId, {
8802
+ reason: "compaction",
8803
+ messages: replacement.map((message, index) => ({
8804
+ message,
8805
+ createdAtMs: piMessageTimestamp(message),
8806
+ provenance: replacementProvenance[index]
8807
+ }))
8567
8808
  });
8568
8809
  updateConversationStats(args.conversation);
8569
8810
  setSpanAttributes({
@@ -9183,7 +9424,6 @@ function createJuniorRuntimeServices(overrides = {}) {
9183
9424
  }
9184
9425
 
9185
9426
  // src/chat/runtime/reply-executor.ts
9186
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
9187
9427
  import { createSlackSource } from "@sentry/junior-plugin-api";
9188
9428
 
9189
9429
  // src/chat/slack/message.ts
@@ -9311,8 +9551,8 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
9311
9551
  }
9312
9552
 
9313
9553
  // src/chat/runtime/reply-executor.ts
9314
- async function persistThreadStateWithRetry(thread, patch) {
9315
- await persistWithRetry(() => persistThreadState(thread, patch));
9554
+ async function persistThreadRuntimeStateWithRetry(thread, patch) {
9555
+ await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
9316
9556
  }
9317
9557
  function collectCanvasUrls(artifacts) {
9318
9558
  return new Set(
@@ -9322,9 +9562,6 @@ function collectCanvasUrls(artifacts) {
9322
9562
  ].filter((url) => typeof url === "string" && url !== "")
9323
9563
  );
9324
9564
  }
9325
- function turnActor(actor) {
9326
- return toStoredSlackActor(actor);
9327
- }
9328
9565
  function parkedInputKey(message) {
9329
9566
  if (message.role !== "user") {
9330
9567
  return void 0;
@@ -9437,9 +9674,8 @@ function collectTurnAttachments(message, queuedMessages) {
9437
9674
  ];
9438
9675
  }
9439
9676
  async function loadPiMessagesForTurn(args) {
9440
- const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
9441
9677
  if (!args.conversationId) {
9442
- return { piMessages: fallback };
9678
+ return {};
9443
9679
  }
9444
9680
  if (args.activeTurnId) {
9445
9681
  const sessionRecord = await getAgentTurnSessionRecord(
@@ -9463,7 +9699,7 @@ async function loadPiMessagesForTurn(args) {
9463
9699
  piMessages: projection
9464
9700
  };
9465
9701
  }
9466
- return { piMessages: fallback };
9702
+ return {};
9467
9703
  }
9468
9704
  function createReplyToThread(deps) {
9469
9705
  return async function replyToThread(thread, message, options) {
@@ -9540,7 +9776,6 @@ function createReplyToThread(deps) {
9540
9776
  deps.services.lookupSlackUser
9541
9777
  ) : void 0;
9542
9778
  const executionActor = "type" in credentialContext.actor ? actor : credentialContext.actor;
9543
- const storedActor = actor ? turnActor(actor) : void 0;
9544
9779
  const slackActorId = actor?.userId;
9545
9780
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
9546
9781
  thread,
@@ -9676,8 +9911,7 @@ function createReplyToThread(deps) {
9676
9911
  provenance: [
9677
9912
  ...projection.provenance,
9678
9913
  ...missing.map((pair) => pair.provenance)
9679
- ],
9680
- ttlMs: THREAD_STATE_TTL_MS4
9914
+ ]
9681
9915
  });
9682
9916
  return true;
9683
9917
  } finally {
@@ -9854,37 +10088,6 @@ function createReplyToThread(deps) {
9854
10088
  "Failed to record running turn summary"
9855
10089
  );
9856
10090
  });
9857
- void initConversationContext(conversationId, {
9858
- channelName,
9859
- originSurface: "slack",
9860
- originActor: storedActor,
9861
- startedAtMs: turnStartedAtMs
9862
- }).catch((error) => {
9863
- logException(
9864
- error,
9865
- "conversation_details_context_init_failed",
9866
- turnTraceContext,
9867
- { "app.agent.turn.state": "running" },
9868
- "Failed to init conversation context at turn start"
9869
- );
9870
- });
9871
- const existingAssistantTitle = preparedState.artifacts.assistantTitle?.trim();
9872
- if (existingAssistantTitle) {
9873
- void setConversationTitle(conversationId, {
9874
- displayTitle: existingAssistantTitle,
9875
- ...preparedState.artifacts.assistantTitleSourceMessageId ? {
9876
- titleSourceMessageId: preparedState.artifacts.assistantTitleSourceMessageId
9877
- } : {}
9878
- }).catch((error) => {
9879
- logException(
9880
- error,
9881
- "conversation_details_title_refresh_failed",
9882
- turnTraceContext,
9883
- { "app.agent.turn.state": "running" },
9884
- "Failed to refresh conversation title from artifacts"
9885
- );
9886
- });
9887
- }
9888
10091
  }
9889
10092
  setTags({
9890
10093
  conversationId
@@ -9967,8 +10170,7 @@ function createReplyToThread(deps) {
9967
10170
  try {
9968
10171
  const loadedPiMessages = await loadPiMessagesForTurn({
9969
10172
  conversationId,
9970
- activeTurnId,
9971
- fallback: preparedState.conversation.piMessages
10173
+ activeTurnId
9972
10174
  });
9973
10175
  let piMessages = loadedPiMessages.piMessages;
9974
10176
  if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
@@ -10043,17 +10245,19 @@ function createReplyToThread(deps) {
10043
10245
  };
10044
10246
  if (conversationId && titleUpdateResult.title) {
10045
10247
  try {
10046
- await setConversationTitle(conversationId, {
10047
- displayTitle: titleUpdateResult.title,
10048
- titleSourceMessageId: titleUpdateResult.sourceMessageId
10248
+ await getConversationStore().recordActivity({
10249
+ activityAtMs: message.metadata.dateSent.getTime(),
10250
+ conversationId,
10251
+ nowMs: Date.now(),
10252
+ title: titleUpdateResult.title
10049
10253
  });
10050
10254
  } catch (error) {
10051
10255
  logException(
10052
10256
  error,
10053
- "conversation_details_title_set_failed",
10257
+ "conversation_title_persist_failed",
10054
10258
  turnTraceContext,
10055
10259
  {},
10056
- "Failed to set conversation title in details record"
10260
+ "Failed to persist generated conversation title"
10057
10261
  );
10058
10262
  }
10059
10263
  }
@@ -10369,6 +10573,7 @@ function createReplyToThread(deps) {
10369
10573
  conversationId,
10370
10574
  durationMs: reply.diagnostics.durationMs,
10371
10575
  usage: reply.diagnostics.usage,
10576
+ reasoningLevel: reply.diagnostics.thinkingLevel,
10372
10577
  destination,
10373
10578
  destinationVisibility,
10374
10579
  source,
@@ -10403,11 +10608,15 @@ function createReplyToThread(deps) {
10403
10608
  traceId: getActiveTraceId()
10404
10609
  });
10405
10610
  }
10406
- await persistThreadStateWithRetry(thread, {
10407
- ...completedState
10408
- });
10611
+ await persistWithRetry(
10612
+ () => persistConversationMessages({
10613
+ conversation: completedState.conversation,
10614
+ conversationId
10615
+ })
10616
+ );
10617
+ await persistThreadRuntimeStateWithRetry(thread, completedState);
10409
10618
  if (completedState.artifacts && (assistantTitleArtifacts.assistantTitle !== void 0 || assistantTitleArtifacts.assistantTitleSourceMessageId !== void 0) && (completedState.artifacts.assistantTitle !== assistantTitleArtifacts.assistantTitle || completedState.artifacts.assistantTitleSourceMessageId !== assistantTitleArtifacts.assistantTitleSourceMessageId)) {
10410
- await persistThreadStateWithRetry(thread, {
10619
+ await persistThreadRuntimeStateWithRetry(thread, {
10411
10620
  artifacts: latestArtifacts
10412
10621
  });
10413
10622
  }
@@ -10755,6 +10964,8 @@ function createPrepareTurnState(deps) {
10755
10964
  ) : void 0;
10756
10965
  const artifacts = coerceThreadArtifactsState(existingState);
10757
10966
  const conversation = coerceThreadConversationState(existingState);
10967
+ const conversationId = args.context.threadId ?? args.context.runId;
10968
+ await hydrateConversationMessages({ conversation, conversationId });
10758
10969
  const channelConfiguration = getChannelConfigurationService(args.thread);
10759
10970
  const configuration = await channelConfiguration.resolveValues();
10760
10971
  await seedConversationBackfill(args.thread, conversation, {
@@ -10794,12 +11005,16 @@ function createPrepareTurnState(deps) {
10794
11005
  threadTs: getThreadTs(args.context.threadId)
10795
11006
  });
10796
11007
  }
11008
+ await persistConversationMessages({ conversation, conversationId });
10797
11009
  await deps.compactConversationIfNeeded(conversation, {
10798
11010
  threadId: args.context.threadId,
10799
11011
  channelId: args.context.channelId,
10800
11012
  actorId: args.context.actorId,
10801
11013
  runId: args.context.runId
10802
11014
  });
11015
+ if (conversationId) {
11016
+ await persistConversationCompactions({ conversation, conversationId });
11017
+ }
10803
11018
  const conversationContext = buildConversationContext(conversation, {
10804
11019
  excludeMessageId: userMessageId
10805
11020
  });
@@ -10994,14 +11209,15 @@ function createJuniorSlackAdapter(config) {
10994
11209
 
10995
11210
  // src/chat/runtime/agent-continue-runner.ts
10996
11211
  var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
10997
- function sleep2(ms) {
10998
- return new Promise((resolve) => setTimeout(resolve, ms));
10999
- }
11000
11212
  async function persistCompletedReplyState2(args) {
11001
11213
  const currentState = await getPersistedThreadState(
11002
11214
  args.sessionRecord.conversationId
11003
11215
  );
11004
11216
  const conversation = coerceThreadConversationState(currentState);
11217
+ await hydrateConversationMessages({
11218
+ conversation,
11219
+ conversationId: args.sessionRecord.conversationId
11220
+ });
11005
11221
  const artifacts = coerceThreadArtifactsState(currentState);
11006
11222
  const userMessage2 = getTurnUserMessage(
11007
11223
  conversation,
@@ -11044,6 +11260,10 @@ async function persistFailedReplyState2(sessionRecord) {
11044
11260
  sessionRecord.conversationId
11045
11261
  );
11046
11262
  const conversation = coerceThreadConversationState(currentState);
11263
+ await hydrateConversationMessages({
11264
+ conversation,
11265
+ conversationId: sessionRecord.conversationId
11266
+ });
11047
11267
  clearPendingAuth(conversation, sessionRecord.sessionId);
11048
11268
  markTurnFailed({
11049
11269
  conversation,
@@ -11145,6 +11365,10 @@ async function continueSlackAgentRun(payload, options) {
11145
11365
  payload.conversationId
11146
11366
  );
11147
11367
  const conversation = coerceThreadConversationState(currentState);
11368
+ await hydrateConversationMessages({
11369
+ conversation,
11370
+ conversationId: payload.conversationId
11371
+ });
11148
11372
  const artifacts = coerceThreadArtifactsState(currentState);
11149
11373
  const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
11150
11374
  if (!userMessage2?.author?.userId) {
@@ -11208,7 +11432,11 @@ async function continueSlackAgentRun(payload, options) {
11208
11432
  replyContext: {
11209
11433
  input: {
11210
11434
  conversationContext,
11211
- piMessages: conversation.piMessages,
11435
+ // Pi history is SQL-authoritative: the resumed run reads its
11436
+ // session record first and falls back to the step projection.
11437
+ piMessages: await loadProjection({
11438
+ conversationId: payload.conversationId
11439
+ }),
11212
11440
  ...getTurnUserReplyAttachmentContext(userMessage2)
11213
11441
  },
11214
11442
  routing: {
@@ -11307,6 +11535,10 @@ async function failStrandedSessionWithFallback(args) {
11307
11535
  });
11308
11536
  const currentState = await getPersistedThreadState(args.conversationId);
11309
11537
  const conversation = coerceThreadConversationState(currentState);
11538
+ await hydrateConversationMessages({
11539
+ conversation,
11540
+ conversationId: args.conversationId
11541
+ });
11310
11542
  markTurnFailed({
11311
11543
  conversation,
11312
11544
  nowMs: Date.now(),
@@ -11476,7 +11708,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options) {
11476
11708
  },
11477
11709
  "Agent continuation lock was busy; retrying"
11478
11710
  );
11479
- await sleep2(delayMs);
11711
+ await sleep(delayMs);
11480
11712
  }
11481
11713
  }
11482
11714
  return true;
@@ -11900,12 +12132,12 @@ async function createApp(options) {
11900
12132
  app.get("/", () => GET());
11901
12133
  app.get("/health", () => GET());
11902
12134
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
11903
- return GET3(c.req.raw, c.req.param("provider"), waitUntil, {
12135
+ return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
11904
12136
  agentRunner
11905
12137
  });
11906
12138
  });
11907
12139
  app.get("/api/oauth/callback/:provider", (c) => {
11908
- return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
12140
+ return GET5(c.req.raw, c.req.param("provider"), waitUntil, {
11909
12141
  agentRunner
11910
12142
  });
11911
12143
  });
@@ -11940,6 +12172,9 @@ async function createApp(options) {
11940
12172
  app.get("/api/internal/heartbeat", (c) => {
11941
12173
  return GET2(c.req.raw, waitUntil);
11942
12174
  });
12175
+ app.get("/api/internal/retention", (c) => {
12176
+ return GET3(c.req.raw);
12177
+ });
11943
12178
  app.post("/api/webhooks/slack", (c) => {
11944
12179
  return POST3(c.req.raw, waitUntil, slackWebhookServices);
11945
12180
  });