@sentry/junior 0.93.0 → 0.95.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 (122) hide show
  1. package/dist/{agent-hooks-ZG7IDOIN.js → agent-hooks-FTQ5EGPO.js} +9 -10
  2. package/dist/api/people/list.js +7 -7
  3. package/dist/api/people/profile.js +7 -7
  4. package/dist/api/people/shared.d.ts +2 -2
  5. package/dist/api-reference.d.ts +1 -1
  6. package/dist/app.js +474 -231
  7. package/dist/{catalog-runtime-NZHMSMYC.js → catalog-runtime-2D7IVKUT.js} +3 -3
  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/egress/credentialed.d.ts +1 -1
  28. package/dist/chat/identities/sql.d.ts +1 -1
  29. package/dist/chat/logging.d.ts +2 -1
  30. package/dist/chat/resource-events/store.d.ts +2 -1
  31. package/dist/chat/runtime/reply-executor.d.ts +8 -0
  32. package/dist/chat/runtime/thread-state.d.ts +2 -0
  33. package/dist/chat/sandbox/egress/schemas.d.ts +4 -0
  34. package/dist/chat/sandbox/egress/session.d.ts +2 -2
  35. package/dist/chat/sandbox/resources.d.ts +4 -0
  36. package/dist/chat/services/mcp-auth-orchestration.d.ts +8 -0
  37. package/dist/chat/services/plugin-auth-orchestration.d.ts +12 -0
  38. package/dist/chat/services/turn-session-record.d.ts +14 -2
  39. package/dist/chat/sleep.d.ts +2 -0
  40. package/dist/chat/state/conversation.d.ts +7 -4
  41. package/dist/chat/state/session-log.d.ts +23 -1
  42. package/dist/chat/state/turn-session.d.ts +6 -0
  43. package/dist/chat/tool-support/private-trace-result.d.ts +4 -0
  44. package/dist/chat/tool-support/zod-tool.d.ts +1 -0
  45. package/dist/chat/tools/advisor/tool.d.ts +9 -4
  46. package/dist/chat/tools/definition.d.ts +2 -0
  47. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
  48. package/dist/chat/tools/system-time.d.ts +8 -0
  49. package/dist/chat/xml.d.ts +2 -0
  50. package/dist/{chunk-NYWPISSO.js → chunk-2XXEKR44.js} +1 -1
  51. package/dist/{chunk-QGEWC27H.js → chunk-5XMNOD3S.js} +5 -7
  52. package/dist/{chunk-NFXSLTYG.js → chunk-6ONME5IG.js} +864 -62
  53. package/dist/{chunk-LFLTJQVR.js → chunk-7FBGKXPE.js} +4 -0
  54. package/dist/{chunk-MC4ZZE4Q.js → chunk-AYP4ERVZ.js} +1 -1
  55. package/dist/{chunk-GHGPTPBL.js → chunk-B7PUBQQM.js} +1 -1
  56. package/dist/chunk-CM5EZD5F.js +17 -0
  57. package/dist/chunk-CRRBJ3JP.js +1037 -0
  58. package/dist/{chunk-G3E7SCME.js → chunk-CZEX6KIQ.js} +23 -0
  59. package/dist/{chunk-TNUUATT7.js → chunk-E3E2XNCN.js} +2 -2
  60. package/dist/{chunk-LNCREA5Z.js → chunk-EKNJM5IG.js} +3 -127
  61. package/dist/{chunk-NAPITCZ6.js → chunk-GUE7HTNR.js} +251 -6
  62. package/dist/{chunk-BTH37NCU.js → chunk-GVOUCI2O.js} +1 -1
  63. package/dist/{chunk-YYBX2BD5.js → chunk-GY34VGP5.js} +1 -1
  64. package/dist/{chunk-JJUJ4CO2.js → chunk-HCXXB35T.js} +1489 -360
  65. package/dist/{chunk-YTNPU7I2.js → chunk-HDTO3A74.js} +2 -2
  66. package/dist/{chunk-YJ4OPVNA.js → chunk-HZNJLKKI.js} +1 -1
  67. package/dist/{chunk-HDNWZMUN.js → chunk-KDPCGVIT.js} +116 -51
  68. package/dist/chunk-N4RNJ32C.js +48 -0
  69. package/dist/{chunk-UUB3D2XI.js → chunk-NSCHU54X.js} +8 -10
  70. package/dist/{chunk-PFXC67GJ.js → chunk-RAIB6WSG.js} +6 -80
  71. package/dist/chunk-WAGQ377B.js +724 -0
  72. package/dist/{chunk-6H4PP63X.js → chunk-WCMQJE4F.js} +24 -14
  73. package/dist/{chunk-S6QKIGRM.js → chunk-WPOJ443W.js} +15 -25
  74. package/dist/{chunk-MAVOJ54A.js → chunk-XX4N4E6C.js} +25 -388
  75. package/dist/cli/chat.js +24 -23
  76. package/dist/cli/check.js +4 -4
  77. package/dist/cli/plugins.js +11 -12
  78. package/dist/cli/snapshot-warmup.js +7 -8
  79. package/dist/cli/upgrade.js +78 -17
  80. package/dist/db/schema/agent-steps.d.ts +134 -0
  81. package/dist/db/schema/conversation-messages.d.ts +153 -0
  82. package/dist/{chat/conversations/sql → db}/schema/conversations.d.ts +34 -0
  83. package/dist/{chat/conversations/sql → db}/schema.d.ts +319 -8
  84. package/dist/db-W55B74OP.js +24 -0
  85. package/dist/deployment.d.ts +2 -0
  86. package/dist/handlers/retention.d.ts +6 -0
  87. package/dist/instrumentation.js +16 -3
  88. package/dist/legacy-import-ZPLGLAFD.js +17 -0
  89. package/dist/nitro.js +14 -4
  90. package/dist/reporting/conversations/activity.d.ts +40 -0
  91. package/dist/reporting/conversations/context.d.ts +8 -0
  92. package/dist/reporting/conversations/details.d.ts +6 -0
  93. package/dist/reporting/conversations/index.d.ts +4 -0
  94. package/dist/reporting/conversations/shared.d.ts +9 -0
  95. package/dist/reporting/conversations/statistics.d.ts +4 -0
  96. package/dist/reporting/conversations/summaries.d.ts +17 -0
  97. package/dist/reporting/conversations/transcript.d.ts +28 -0
  98. package/dist/reporting/conversations/types.d.ts +178 -0
  99. package/dist/reporting/conversations.d.ts +1 -178
  100. package/dist/reporting.d.ts +4 -3
  101. package/dist/reporting.js +603 -827
  102. package/dist/{runner-XAZPJNAQ.js → runner-OOSBDYRK.js} +35 -41
  103. package/dist/{validation-KYAWETI4.js → validation-QCGRFRFZ.js} +4 -4
  104. package/migrations/README.md +32 -0
  105. package/migrations/meta/0000_snapshot.json +978 -0
  106. package/migrations/meta/_journal.json +13 -0
  107. package/package.json +9 -6
  108. package/dist/chat/sql/schema.d.ts +0 -1049
  109. package/dist/chat/state/conversation-details.d.ts +0 -47
  110. package/dist/chunk-7STN2ZSK.js +0 -250
  111. package/dist/chunk-LE5ANBUZ.js +0 -1040
  112. package/dist/chunk-QL66APYJ.js +0 -1377
  113. package/dist/db-YDN6AFJH.js +0 -18
  114. /package/dist/{chat/sql → db}/db.d.ts +0 -0
  115. /package/dist/{chat/sql → db}/executor.d.ts +0 -0
  116. /package/dist/{chat/sql → db}/neon.d.ts +0 -0
  117. /package/dist/{chat/sql → db}/postgres.d.ts +0 -0
  118. /package/dist/{chat/conversations/sql → db}/schema/destinations.d.ts +0 -0
  119. /package/dist/{chat/conversations/sql → db}/schema/identities.d.ts +0 -0
  120. /package/dist/{chat/conversations/sql → db}/schema/migrations.d.ts +0 -0
  121. /package/dist/{chat/conversations/sql → db}/schema/timestamps.d.ts +0 -0
  122. /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-XX4N4E6C.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-5XMNOD3S.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-EKNJM5IG.js";
64
50
  import {
65
51
  validatePluginEgressCredentialHooks,
66
52
  validatePluginRegistrations
67
- } from "./chunk-MC4ZZE4Q.js";
53
+ } from "./chunk-AYP4ERVZ.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-JJUJ4CO2.js";
98
+ splitSlackReplyText,
99
+ startOAuthFlow,
100
+ truncateStatusText
101
+ } from "./chunk-HCXXB35T.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-KDPCGVIT.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-WCMQJE4F.js";
135
+ import "./chunk-CM5EZD5F.js";
136
+ import "./chunk-N4RNJ32C.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-WAGQ377B.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-CRRBJ3JP.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-NSCHU54X.js";
182
185
  import {
183
186
  createPluginLogger,
184
187
  createPluginState
185
- } from "./chunk-TNUUATT7.js";
186
- import "./chunk-G3E7SCME.js";
188
+ } from "./chunk-E3E2XNCN.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-6ONME5IG.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-GUE7HTNR.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-HDTO3A74.js";
231
242
  import {
232
243
  buildOAuthTokenRequest,
233
244
  hasRequiredOAuthScope,
234
245
  parseOAuthTokenResponse,
235
246
  pluginCatalogRuntime
236
- } from "./chunk-BTH37NCU.js";
247
+ } from "./chunk-GVOUCI2O.js";
237
248
  import {
238
249
  buildTurnFailureResponse,
239
250
  createActor,
@@ -253,10 +264,9 @@ 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-WPOJ443W.js";
260
270
  import {
261
271
  toOptionalString
262
272
  } from "./chunk-OB42YVAE.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);
@@ -2184,6 +2180,7 @@ async function ingestResourceEvent(input, options) {
2184
2180
  });
2185
2181
  let enqueued = 0;
2186
2182
  const errors = [];
2183
+ const waitDeadlineMs = Date.now() + 1e4;
2187
2184
  for (const subscription of subscriptions) {
2188
2185
  try {
2189
2186
  const delivered = await deliverResourceEventSubscription({
@@ -2194,6 +2191,7 @@ async function ingestResourceEvent(input, options) {
2194
2191
  nowMs,
2195
2192
  state: options.state,
2196
2193
  subscription,
2194
+ waitDeadlineMs,
2197
2195
  deliver: async (current) => {
2198
2196
  const result = await enqueueResourceEventNotification({
2199
2197
  event,
@@ -3042,8 +3040,210 @@ async function GET2(request, waitUntil, options = {}) {
3042
3040
  return new Response("Accepted", { status: 202 });
3043
3041
  }
3044
3042
 
3045
- // src/handlers/mcp-oauth-callback.ts
3046
- import { THREAD_STATE_TTL_MS } from "chat";
3043
+ // src/handlers/retention.ts
3044
+ import { timingSafeEqual as timingSafeEqual4 } from "crypto";
3045
+
3046
+ // src/chat/conversations/sql/purge.ts
3047
+ import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
3048
+ async function conversationTreeIds(executor, rootConversationId) {
3049
+ const all = /* @__PURE__ */ new Set([rootConversationId]);
3050
+ let frontier = [rootConversationId];
3051
+ while (frontier.length > 0) {
3052
+ const children = await executor.db().select({ id: juniorConversations.conversationId }).from(juniorConversations).where(inArray(juniorConversations.parentConversationId, frontier));
3053
+ frontier = [];
3054
+ for (const child of children) {
3055
+ if (!all.has(child.id)) {
3056
+ all.add(child.id);
3057
+ frontier.push(child.id);
3058
+ }
3059
+ }
3060
+ }
3061
+ return [...all];
3062
+ }
3063
+ async function selectExpiredRoots(executor, args) {
3064
+ const publicCutoff = new Date(args.nowMs - args.publicWindowMs).toISOString();
3065
+ const privateCutoff = new Date(
3066
+ args.nowMs - args.privateWindowMs
3067
+ ).toISOString();
3068
+ const cutoff = sql`case when ${juniorDestinations.visibility} = 'public' then ${publicCutoff}::timestamptz else ${privateCutoff}::timestamptz end`;
3069
+ const hasTreeWork = sql`exists (
3070
+ with recursive conversation_tree(conversation_id) as (
3071
+ select ${juniorConversations.conversationId}
3072
+ union all
3073
+ select child.conversation_id
3074
+ from junior_conversations child
3075
+ join conversation_tree parent on child.parent_conversation_id = parent.conversation_id
3076
+ )
3077
+ select 1
3078
+ from conversation_tree tree
3079
+ where exists (
3080
+ select 1 from junior_agent_steps steps
3081
+ where steps.conversation_id = tree.conversation_id
3082
+ )
3083
+ or exists (
3084
+ select 1 from junior_conversation_messages messages
3085
+ where messages.conversation_id = tree.conversation_id
3086
+ )
3087
+ or (
3088
+ ${juniorDestinations.visibility} is distinct from 'public'
3089
+ and exists (
3090
+ select 1 from junior_conversations metadata
3091
+ where metadata.conversation_id = tree.conversation_id
3092
+ and (
3093
+ metadata.title is not null
3094
+ or metadata.channel_name is not null
3095
+ or metadata.actor_json is not null
3096
+ )
3097
+ )
3098
+ )
3099
+ )`;
3100
+ const rows = await executor.db().select({
3101
+ conversationId: juniorConversations.conversationId,
3102
+ visibility: juniorDestinations.visibility
3103
+ }).from(juniorConversations).leftJoin(
3104
+ juniorDestinations,
3105
+ eq(juniorDestinations.id, juniorConversations.destinationId)
3106
+ ).where(
3107
+ and(
3108
+ isNull(juniorConversations.parentConversationId),
3109
+ sql`${juniorConversations.lastActivityAt} < ${cutoff}`,
3110
+ hasTreeWork
3111
+ )
3112
+ ).orderBy(
3113
+ asc(juniorConversations.lastActivityAt),
3114
+ asc(juniorConversations.conversationId)
3115
+ ).limit(Math.max(0, args.limit));
3116
+ return rows.map((row) => ({
3117
+ conversationId: row.conversationId,
3118
+ visibility: row.visibility
3119
+ }));
3120
+ }
3121
+ async function purgeConversationTree(executor, args) {
3122
+ return await executor.transaction(async () => {
3123
+ const roots = await executor.db().select({
3124
+ destinationId: juniorConversations.destinationId,
3125
+ lastActivityAt: juniorConversations.lastActivityAt,
3126
+ parentConversationId: juniorConversations.parentConversationId
3127
+ }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.rootConversationId)).for("update");
3128
+ const root = roots[0];
3129
+ if (!root || args.retention && root.parentConversationId !== null) {
3130
+ return { purged: false, conversations: 0 };
3131
+ }
3132
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, root.destinationId)) : [];
3133
+ const isPublic = destinations[0]?.visibility === "public";
3134
+ if (args.retention) {
3135
+ const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
3136
+ if (root.lastActivityAt.getTime() >= args.nowMs - windowMs) {
3137
+ return { purged: false, conversations: 0 };
3138
+ }
3139
+ }
3140
+ const ids = await conversationTreeIds(executor, args.rootConversationId);
3141
+ await executor.db().delete(juniorAgentSteps).where(inArray(juniorAgentSteps.conversationId, ids));
3142
+ await executor.db().delete(juniorConversationMessages).where(inArray(juniorConversationMessages.conversationId, ids));
3143
+ await executor.db().update(juniorConversations).set({
3144
+ transcriptPurgedAt: new Date(args.nowMs),
3145
+ ...(args.retention ? !isPublic : args.scrubMetadata) ? { title: null, channelName: null, actor: null } : {}
3146
+ }).where(inArray(juniorConversations.conversationId, ids));
3147
+ return { purged: true, conversations: ids.length };
3148
+ });
3149
+ }
3150
+
3151
+ // src/chat/conversations/retention.ts
3152
+ var DAY_MS = 24 * 60 * 60 * 1e3;
3153
+ var CONTENT_RETENTION_MS = {
3154
+ public: 90 * DAY_MS,
3155
+ private: 14 * DAY_MS
3156
+ };
3157
+ var RETENTION_BATCH_LIMIT = 200;
3158
+ async function runRetentionPurge(executor, args) {
3159
+ const limit = args.limit ?? RETENTION_BATCH_LIMIT;
3160
+ const startedAtMs = Date.now();
3161
+ const roots = await selectExpiredRoots(executor, {
3162
+ nowMs: args.nowMs,
3163
+ publicWindowMs: CONTENT_RETENTION_MS.public,
3164
+ privateWindowMs: CONTENT_RETENTION_MS.private,
3165
+ limit
3166
+ });
3167
+ let purged = 0;
3168
+ let failed = 0;
3169
+ let conversations = 0;
3170
+ for (const root of roots) {
3171
+ try {
3172
+ const result = await purgeConversationTree(executor, {
3173
+ rootConversationId: root.conversationId,
3174
+ nowMs: args.nowMs,
3175
+ retention: {
3176
+ publicWindowMs: CONTENT_RETENTION_MS.public,
3177
+ privateWindowMs: CONTENT_RETENTION_MS.private
3178
+ }
3179
+ });
3180
+ if (result.purged) {
3181
+ purged += 1;
3182
+ conversations += result.conversations;
3183
+ }
3184
+ } catch (error) {
3185
+ failed += 1;
3186
+ logException(
3187
+ error,
3188
+ "retention_purge_tree_failed",
3189
+ { conversationId: root.conversationId },
3190
+ {},
3191
+ "Retention purge failed for one conversation tree"
3192
+ );
3193
+ }
3194
+ }
3195
+ logInfo(
3196
+ "retention_purge_completed",
3197
+ {},
3198
+ {
3199
+ "app.retention.scanned": roots.length,
3200
+ "app.retention.purged": purged,
3201
+ "app.retention.failed": failed,
3202
+ "app.retention.conversations": conversations,
3203
+ "app.retention.duration_ms": Date.now() - startedAtMs
3204
+ },
3205
+ "Retention purge batch completed"
3206
+ );
3207
+ return { scanned: roots.length, purged, failed, conversations };
3208
+ }
3209
+
3210
+ // src/handlers/retention.ts
3211
+ function getRetentionSecret() {
3212
+ return process.env.JUNIOR_SCHEDULER_SECRET?.trim() || process.env.CRON_SECRET?.trim();
3213
+ }
3214
+ function verifyRetentionRequest(request) {
3215
+ const secret = getRetentionSecret();
3216
+ if (!secret) {
3217
+ return false;
3218
+ }
3219
+ const authorization = request.headers.get("authorization")?.trim();
3220
+ if (!authorization?.startsWith("Bearer ")) {
3221
+ return false;
3222
+ }
3223
+ const actual = Buffer.from(authorization.slice("Bearer ".length));
3224
+ const expected = Buffer.from(secret);
3225
+ return actual.length === expected.length && timingSafeEqual4(actual, expected);
3226
+ }
3227
+ async function GET3(request) {
3228
+ if (!verifyRetentionRequest(request)) {
3229
+ return new Response("Unauthorized", { status: 401 });
3230
+ }
3231
+ try {
3232
+ const result = await runRetentionPurge(getSqlExecutor(), {
3233
+ nowMs: Date.now()
3234
+ });
3235
+ return Response.json(result, { status: 200 });
3236
+ } catch (error) {
3237
+ logException(
3238
+ error,
3239
+ "retention_run_failed",
3240
+ {},
3241
+ {},
3242
+ "Retention purge run failed"
3243
+ );
3244
+ return new Response("Retention purge failed", { status: 500 });
3245
+ }
3246
+ }
3047
3247
 
3048
3248
  // src/chat/runtime/turn-user-message.ts
3049
3249
  function getTurnUserMessage(conversation, sessionId) {
@@ -3916,24 +4116,28 @@ async function resumeSlackTurn(args) {
3916
4116
  }
3917
4117
  const activeReplyContext = runArgs.replyContext;
3918
4118
  if (!activeReplyContext) {
3919
- throw new Error(
3920
- "Resumed turn requires replyContext.routing.actor.userId"
3921
- );
3922
- }
3923
- const resumeActor = activeReplyContext.routing.actor;
3924
- if (!isUserActor(resumeActor)) {
3925
- throw new Error(
3926
- "Resumed turn requires replyContext.routing.actor.userId"
3927
- );
4119
+ throw new Error("Resumed turn requires replyContext");
3928
4120
  }
3929
4121
  const credentialContext = activeReplyContext.routing.credentialContext;
3930
4122
  if (!credentialContext) {
3931
4123
  throw new Error("Resumed turn requires replyContext.credentialContext");
3932
4124
  }
3933
- if (!("type" in credentialContext.actor) || credentialContext.actor.userId !== resumeActor.userId) {
3934
- throw new Error(
3935
- "Resumed turn credential actor must match replyContext.routing.actor.userId"
3936
- );
4125
+ const routingActor = activeReplyContext.routing.actor;
4126
+ let resumeActor;
4127
+ if ("type" in credentialContext.actor) {
4128
+ if (!isUserActor(routingActor) || credentialContext.actor.userId !== routingActor.userId) {
4129
+ throw new Error(
4130
+ "Resumed turn credential actor must match replyContext.routing.actor.userId"
4131
+ );
4132
+ }
4133
+ resumeActor = routingActor;
4134
+ } else {
4135
+ if (routingActor && (routingActor.platform !== "system" || routingActor.name !== credentialContext.actor.name)) {
4136
+ throw new Error(
4137
+ "Resumed turn system credential actor must match replyContext.routing.actor"
4138
+ );
4139
+ }
4140
+ resumeActor = credentialContext.actor;
3937
4141
  }
3938
4142
  if (runArgs.messageTs) {
3939
4143
  processingReaction = await startSlackProcessingReactionForMessage({
@@ -3975,7 +4179,7 @@ async function resumeSlackTurn(args) {
3975
4179
  deferredPauseKind = "auth";
3976
4180
  deferredAuthInfo = {
3977
4181
  providerDisplayName: outcome.providerDisplayName,
3978
- actorId: resumeActor.userId
4182
+ actorId: isUserActor(resumeActor) ? resumeActor.userId : void 0
3979
4183
  };
3980
4184
  deferredPauseHandler = async () => {
3981
4185
  await onAuthPause({
@@ -4027,7 +4231,7 @@ async function resumeSlackTurn(args) {
4027
4231
  currentUsage: reply.diagnostics.usage,
4028
4232
  destination: replyContext.routing.destination,
4029
4233
  source: replyContext.routing.source,
4030
- actor: replyContext.routing.actor,
4234
+ actor: resumeActor,
4031
4235
  surface: "slack",
4032
4236
  logContext: {
4033
4237
  threadId: replyContext.routing.correlation.threadId,
@@ -4180,6 +4384,10 @@ function completeAuthPauseTurn(args) {
4180
4384
  async function persistAuthPauseTurnState(args) {
4181
4385
  const currentState = await getPersistedThreadState(args.threadStateId);
4182
4386
  const conversation = coerceThreadConversationState(currentState);
4387
+ await hydrateConversationMessages({
4388
+ conversation,
4389
+ conversationId: args.threadStateId
4390
+ });
4183
4391
  completeAuthPauseTurn({
4184
4392
  conversation,
4185
4393
  sessionId: args.sessionId
@@ -4288,6 +4496,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
4288
4496
  const threadId = `slack:${channelId}:${threadTs}`;
4289
4497
  const currentState = await getPersistedThreadState(threadId);
4290
4498
  const conversation = coerceThreadConversationState(currentState);
4499
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4291
4500
  const artifacts = coerceThreadArtifactsState(currentState);
4292
4501
  const userMessage2 = getTurnUserMessage(conversation, sessionId);
4293
4502
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4326,6 +4535,7 @@ async function persistFailedReplyState(channelId, threadTs, sessionId, expectedV
4326
4535
  const threadId = `slack:${channelId}:${threadTs}`;
4327
4536
  const currentState = await getPersistedThreadState(threadId);
4328
4537
  const conversation = coerceThreadConversationState(currentState);
4538
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4329
4539
  clearPendingAuth(conversation, sessionId);
4330
4540
  markTurnFailed({
4331
4541
  conversation,
@@ -4357,6 +4567,7 @@ async function resumeAuthorizedMcpTurn(args) {
4357
4567
  const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4358
4568
  const currentState = await getPersistedThreadState(threadId);
4359
4569
  const conversation = coerceThreadConversationState(currentState);
4570
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4360
4571
  const pendingAuth = getConversationPendingAuth({
4361
4572
  conversation,
4362
4573
  kind: "mcp",
@@ -4393,6 +4604,10 @@ async function resumeAuthorizedMcpTurn(args) {
4393
4604
  beforeStart: async () => {
4394
4605
  const lockedState = await getPersistedThreadState(threadId);
4395
4606
  const lockedConversation = coerceThreadConversationState(lockedState);
4607
+ await hydrateConversationMessages({
4608
+ conversation: lockedConversation,
4609
+ conversationId: threadId
4610
+ });
4396
4611
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4397
4612
  const lockedPendingAuth = getConversationPendingAuth({
4398
4613
  conversation: lockedConversation,
@@ -4476,8 +4691,7 @@ async function resumeAuthorizedMcpTurn(args) {
4476
4691
  authorizationId: mcpAuthorizationId({
4477
4692
  provider,
4478
4693
  sessionId: lockedSessionId
4479
- }),
4480
- ttlMs: THREAD_STATE_TTL_MS
4694
+ })
4481
4695
  });
4482
4696
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
4483
4697
  return {
@@ -4486,7 +4700,9 @@ async function resumeAuthorizedMcpTurn(args) {
4486
4700
  replyContext: {
4487
4701
  input: {
4488
4702
  conversationContext: lockedConversationContext,
4489
- piMessages: lockedConversation.piMessages,
4703
+ // Pi history is SQL-authoritative: the resumed run reads its
4704
+ // session record first and falls back to the step projection.
4705
+ piMessages: await loadProjection({ conversationId: threadId }),
4490
4706
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
4491
4707
  },
4492
4708
  routing: {
@@ -4585,7 +4801,7 @@ async function resumeAuthorizedMcpTurn(args) {
4585
4801
  }
4586
4802
  });
4587
4803
  }
4588
- async function GET3(request, provider, waitUntil, options) {
4804
+ async function GET4(request, provider, waitUntil, options) {
4589
4805
  const url = new URL(request.url);
4590
4806
  const state = url.searchParams.get("state")?.trim();
4591
4807
  const code = url.searchParams.get("code")?.trim();
@@ -4632,9 +4848,6 @@ async function GET3(request, provider, waitUntil, options) {
4632
4848
  }
4633
4849
  }
4634
4850
 
4635
- // src/handlers/oauth-callback.ts
4636
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
4637
-
4638
4851
  // src/chat/slack/app-home.ts
4639
4852
  import fs from "fs";
4640
4853
  import path from "path";
@@ -4797,6 +5010,10 @@ function htmlErrorResponse(title, message, status) {
4797
5010
  async function persistCompletedOAuthReplyState(args) {
4798
5011
  const currentState = await getPersistedThreadState(args.conversationId);
4799
5012
  const conversation = coerceThreadConversationState(currentState);
5013
+ await hydrateConversationMessages({
5014
+ conversation,
5015
+ conversationId: args.conversationId
5016
+ });
4800
5017
  const artifacts = coerceThreadArtifactsState(currentState);
4801
5018
  const userMessage2 = getTurnUserMessage(conversation, args.sessionId);
4802
5019
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4837,6 +5054,10 @@ async function failSessionRecordBestEffort2(args) {
4837
5054
  async function persistFailedOAuthReplyState(args) {
4838
5055
  const currentState = await getPersistedThreadState(args.conversationId);
4839
5056
  const conversation = coerceThreadConversationState(currentState);
5057
+ await hydrateConversationMessages({
5058
+ conversation,
5059
+ conversationId: args.conversationId
5060
+ });
4840
5061
  clearPendingAuth(conversation, args.sessionId);
4841
5062
  markTurnFailed({
4842
5063
  conversation,
@@ -4868,6 +5089,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4868
5089
  stored.resumeConversationId
4869
5090
  );
4870
5091
  const conversation = coerceThreadConversationState(currentState);
5092
+ await hydrateConversationMessages({
5093
+ conversation,
5094
+ conversationId: stored.resumeConversationId
5095
+ });
4871
5096
  const pendingAuth = getConversationPendingAuth({
4872
5097
  conversation,
4873
5098
  kind: "plugin",
@@ -4923,6 +5148,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4923
5148
  stored.resumeConversationId
4924
5149
  );
4925
5150
  const lockedConversation = coerceThreadConversationState(lockedState);
5151
+ await hydrateConversationMessages({
5152
+ conversation: lockedConversation,
5153
+ conversationId: stored.resumeConversationId
5154
+ });
4926
5155
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4927
5156
  const lockedPendingAuth = getConversationPendingAuth({
4928
5157
  conversation: lockedConversation,
@@ -5007,8 +5236,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5007
5236
  authorizationId: pluginAuthorizationId({
5008
5237
  provider: stored.provider,
5009
5238
  sessionId: lockedSessionId
5010
- }),
5011
- ttlMs: THREAD_STATE_TTL_MS2
5239
+ })
5012
5240
  });
5013
5241
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
5014
5242
  return {
@@ -5017,7 +5245,11 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5017
5245
  replyContext: {
5018
5246
  input: {
5019
5247
  conversationContext: lockedConversationContext,
5020
- piMessages: lockedConversation.piMessages,
5248
+ // Pi history is SQL-authoritative: the resumed run reads its
5249
+ // session record first and falls back to the step projection.
5250
+ piMessages: await loadProjection({
5251
+ conversationId: stored.resumeConversationId
5252
+ }),
5021
5253
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
5022
5254
  },
5023
5255
  routing: {
@@ -5120,6 +5352,7 @@ async function resumePendingOAuthMessage(stored, options) {
5120
5352
  const conversation = coerceThreadConversationState(
5121
5353
  await getPersistedThreadState(threadId)
5122
5354
  );
5355
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
5123
5356
  const latestUserMessage = [...conversation.messages].reverse().find((message) => message.role === "user");
5124
5357
  const conversationContext = buildConversationContext(conversation, {
5125
5358
  excludeMessageId: latestUserMessage?.id
@@ -5140,7 +5373,8 @@ async function resumePendingOAuthMessage(stored, options) {
5140
5373
  replyContext: {
5141
5374
  input: {
5142
5375
  conversationContext,
5143
- piMessages: conversation.piMessages
5376
+ // Pi history is SQL-authoritative via the step-store projection.
5377
+ piMessages: await loadProjection({ conversationId: threadId })
5144
5378
  },
5145
5379
  routing: {
5146
5380
  credentialContext: {
@@ -5174,7 +5408,7 @@ async function resumePendingOAuthMessage(stored, options) {
5174
5408
  }
5175
5409
  });
5176
5410
  }
5177
- async function GET4(request, provider, waitUntil, options) {
5411
+ async function GET5(request, provider, waitUntil, options) {
5178
5412
  const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
5179
5413
  if (!providerConfig) {
5180
5414
  return htmlErrorResponse(
@@ -8301,7 +8535,6 @@ function createSlackTurnRuntime(deps) {
8301
8535
  }
8302
8536
 
8303
8537
  // src/chat/services/context-compaction.ts
8304
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
8305
8538
  import {
8306
8539
  estimateContextTokens,
8307
8540
  estimateTokens
@@ -8359,6 +8592,10 @@ function userMessage(text) {
8359
8592
  timestamp: Date.now()
8360
8593
  };
8361
8594
  }
8595
+ function piMessageTimestamp(message) {
8596
+ const timestamp = message.timestamp;
8597
+ return typeof timestamp === "number" ? timestamp : Date.now();
8598
+ }
8362
8599
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
8363
8600
  const selected = [];
8364
8601
  let remaining = maxTokens;
@@ -8541,6 +8778,7 @@ async function maybeCompactWithDeps(args, deps) {
8541
8778
  });
8542
8779
  }
8543
8780
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
8781
+ const stepStore = getAgentStepStore();
8544
8782
  const sourceProjection = await loadProjectionWithProvenance({
8545
8783
  conversationId: args.conversationId
8546
8784
  });
@@ -8552,14 +8790,17 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
8552
8790
  userMessage(`${COMPACTION_SUMMARY_PREFIX}
8553
8791
  ${summary}`)
8554
8792
  ];
8555
- await commitMessages({
8556
- conversationId: args.conversationId,
8557
- messages: replacement,
8558
- provenance: buildReplacementProvenance({
8559
- retained,
8560
- sourceProvenance: sourceProjection.provenance
8561
- }),
8562
- ttlMs: THREAD_STATE_TTL_MS3
8793
+ const replacementProvenance = buildReplacementProvenance({
8794
+ retained,
8795
+ sourceProvenance: sourceProjection.provenance
8796
+ });
8797
+ await stepStore.startEpoch(args.conversationId, {
8798
+ reason: "compaction",
8799
+ messages: replacement.map((message, index) => ({
8800
+ message,
8801
+ createdAtMs: piMessageTimestamp(message),
8802
+ provenance: replacementProvenance[index]
8803
+ }))
8563
8804
  });
8564
8805
  updateConversationStats(args.conversation);
8565
8806
  setSpanAttributes({
@@ -9179,7 +9420,6 @@ function createJuniorRuntimeServices(overrides = {}) {
9179
9420
  }
9180
9421
 
9181
9422
  // src/chat/runtime/reply-executor.ts
9182
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
9183
9423
  import { createSlackSource } from "@sentry/junior-plugin-api";
9184
9424
 
9185
9425
  // src/chat/slack/message.ts
@@ -9307,8 +9547,8 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
9307
9547
  }
9308
9548
 
9309
9549
  // src/chat/runtime/reply-executor.ts
9310
- async function persistThreadStateWithRetry(thread, patch) {
9311
- await persistWithRetry(() => persistThreadState(thread, patch));
9550
+ async function persistThreadRuntimeStateWithRetry(thread, patch) {
9551
+ await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
9312
9552
  }
9313
9553
  function collectCanvasUrls(artifacts) {
9314
9554
  return new Set(
@@ -9318,9 +9558,6 @@ function collectCanvasUrls(artifacts) {
9318
9558
  ].filter((url) => typeof url === "string" && url !== "")
9319
9559
  );
9320
9560
  }
9321
- function turnActor(actor) {
9322
- return toStoredSlackActor(actor);
9323
- }
9324
9561
  function parkedInputKey(message) {
9325
9562
  if (message.role !== "user") {
9326
9563
  return void 0;
@@ -9433,9 +9670,8 @@ function collectTurnAttachments(message, queuedMessages) {
9433
9670
  ];
9434
9671
  }
9435
9672
  async function loadPiMessagesForTurn(args) {
9436
- const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
9437
9673
  if (!args.conversationId) {
9438
- return { piMessages: fallback };
9674
+ return {};
9439
9675
  }
9440
9676
  if (args.activeTurnId) {
9441
9677
  const sessionRecord = await getAgentTurnSessionRecord(
@@ -9459,7 +9695,7 @@ async function loadPiMessagesForTurn(args) {
9459
9695
  piMessages: projection
9460
9696
  };
9461
9697
  }
9462
- return { piMessages: fallback };
9698
+ return {};
9463
9699
  }
9464
9700
  function createReplyToThread(deps) {
9465
9701
  return async function replyToThread(thread, message, options) {
@@ -9536,7 +9772,6 @@ function createReplyToThread(deps) {
9536
9772
  deps.services.lookupSlackUser
9537
9773
  ) : void 0;
9538
9774
  const executionActor = "type" in credentialContext.actor ? actor : credentialContext.actor;
9539
- const storedActor = actor ? turnActor(actor) : void 0;
9540
9775
  const slackActorId = actor?.userId;
9541
9776
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
9542
9777
  thread,
@@ -9672,8 +9907,7 @@ function createReplyToThread(deps) {
9672
9907
  provenance: [
9673
9908
  ...projection.provenance,
9674
9909
  ...missing.map((pair) => pair.provenance)
9675
- ],
9676
- ttlMs: THREAD_STATE_TTL_MS4
9910
+ ]
9677
9911
  });
9678
9912
  return true;
9679
9913
  } finally {
@@ -9836,7 +10070,7 @@ function createReplyToThread(deps) {
9836
10070
  startedAtMs: turnStartedAtMs,
9837
10071
  state: "running",
9838
10072
  surface: "slack",
9839
- actor,
10073
+ actor: executionActor,
9840
10074
  destination,
9841
10075
  destinationVisibility,
9842
10076
  source,
@@ -9850,37 +10084,6 @@ function createReplyToThread(deps) {
9850
10084
  "Failed to record running turn summary"
9851
10085
  );
9852
10086
  });
9853
- void initConversationContext(conversationId, {
9854
- channelName,
9855
- originSurface: "slack",
9856
- originActor: storedActor,
9857
- startedAtMs: turnStartedAtMs
9858
- }).catch((error) => {
9859
- logException(
9860
- error,
9861
- "conversation_details_context_init_failed",
9862
- turnTraceContext,
9863
- { "app.agent.turn.state": "running" },
9864
- "Failed to init conversation context at turn start"
9865
- );
9866
- });
9867
- const existingAssistantTitle = preparedState.artifacts.assistantTitle?.trim();
9868
- if (existingAssistantTitle) {
9869
- void setConversationTitle(conversationId, {
9870
- displayTitle: existingAssistantTitle,
9871
- ...preparedState.artifacts.assistantTitleSourceMessageId ? {
9872
- titleSourceMessageId: preparedState.artifacts.assistantTitleSourceMessageId
9873
- } : {}
9874
- }).catch((error) => {
9875
- logException(
9876
- error,
9877
- "conversation_details_title_refresh_failed",
9878
- turnTraceContext,
9879
- { "app.agent.turn.state": "running" },
9880
- "Failed to refresh conversation title from artifacts"
9881
- );
9882
- });
9883
- }
9884
10087
  }
9885
10088
  setTags({
9886
10089
  conversationId
@@ -9963,8 +10166,7 @@ function createReplyToThread(deps) {
9963
10166
  try {
9964
10167
  const loadedPiMessages = await loadPiMessagesForTurn({
9965
10168
  conversationId,
9966
- activeTurnId,
9967
- fallback: preparedState.conversation.piMessages
10169
+ activeTurnId
9968
10170
  });
9969
10171
  let piMessages = loadedPiMessages.piMessages;
9970
10172
  if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
@@ -10039,17 +10241,19 @@ function createReplyToThread(deps) {
10039
10241
  };
10040
10242
  if (conversationId && titleUpdateResult.title) {
10041
10243
  try {
10042
- await setConversationTitle(conversationId, {
10043
- displayTitle: titleUpdateResult.title,
10044
- titleSourceMessageId: titleUpdateResult.sourceMessageId
10244
+ await getConversationStore().recordActivity({
10245
+ activityAtMs: message.metadata.dateSent.getTime(),
10246
+ conversationId,
10247
+ nowMs: Date.now(),
10248
+ title: titleUpdateResult.title
10045
10249
  });
10046
10250
  } catch (error) {
10047
10251
  logException(
10048
10252
  error,
10049
- "conversation_details_title_set_failed",
10253
+ "conversation_title_persist_failed",
10050
10254
  turnTraceContext,
10051
10255
  {},
10052
- "Failed to set conversation title in details record"
10256
+ "Failed to persist generated conversation title"
10053
10257
  );
10054
10258
  }
10055
10259
  }
@@ -10365,6 +10569,7 @@ function createReplyToThread(deps) {
10365
10569
  conversationId,
10366
10570
  durationMs: reply.diagnostics.durationMs,
10367
10571
  usage: reply.diagnostics.usage,
10572
+ reasoningLevel: reply.diagnostics.thinkingLevel,
10368
10573
  destination,
10369
10574
  destinationVisibility,
10370
10575
  source,
@@ -10399,11 +10604,15 @@ function createReplyToThread(deps) {
10399
10604
  traceId: getActiveTraceId()
10400
10605
  });
10401
10606
  }
10402
- await persistThreadStateWithRetry(thread, {
10403
- ...completedState
10404
- });
10607
+ await persistWithRetry(
10608
+ () => persistConversationMessages({
10609
+ conversation: completedState.conversation,
10610
+ conversationId
10611
+ })
10612
+ );
10613
+ await persistThreadRuntimeStateWithRetry(thread, completedState);
10405
10614
  if (completedState.artifacts && (assistantTitleArtifacts.assistantTitle !== void 0 || assistantTitleArtifacts.assistantTitleSourceMessageId !== void 0) && (completedState.artifacts.assistantTitle !== assistantTitleArtifacts.assistantTitle || completedState.artifacts.assistantTitleSourceMessageId !== assistantTitleArtifacts.assistantTitleSourceMessageId)) {
10406
- await persistThreadStateWithRetry(thread, {
10615
+ await persistThreadRuntimeStateWithRetry(thread, {
10407
10616
  artifacts: latestArtifacts
10408
10617
  });
10409
10618
  }
@@ -10751,6 +10960,8 @@ function createPrepareTurnState(deps) {
10751
10960
  ) : void 0;
10752
10961
  const artifacts = coerceThreadArtifactsState(existingState);
10753
10962
  const conversation = coerceThreadConversationState(existingState);
10963
+ const conversationId = args.context.threadId ?? args.context.runId;
10964
+ await hydrateConversationMessages({ conversation, conversationId });
10754
10965
  const channelConfiguration = getChannelConfigurationService(args.thread);
10755
10966
  const configuration = await channelConfiguration.resolveValues();
10756
10967
  await seedConversationBackfill(args.thread, conversation, {
@@ -10790,12 +11001,16 @@ function createPrepareTurnState(deps) {
10790
11001
  threadTs: getThreadTs(args.context.threadId)
10791
11002
  });
10792
11003
  }
11004
+ await persistConversationMessages({ conversation, conversationId });
10793
11005
  await deps.compactConversationIfNeeded(conversation, {
10794
11006
  threadId: args.context.threadId,
10795
11007
  channelId: args.context.channelId,
10796
11008
  actorId: args.context.actorId,
10797
11009
  runId: args.context.runId
10798
11010
  });
11011
+ if (conversationId) {
11012
+ await persistConversationCompactions({ conversation, conversationId });
11013
+ }
10799
11014
  const conversationContext = buildConversationContext(conversation, {
10800
11015
  excludeMessageId: userMessageId
10801
11016
  });
@@ -10990,14 +11205,15 @@ function createJuniorSlackAdapter(config) {
10990
11205
 
10991
11206
  // src/chat/runtime/agent-continue-runner.ts
10992
11207
  var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
10993
- function sleep2(ms) {
10994
- return new Promise((resolve) => setTimeout(resolve, ms));
10995
- }
10996
11208
  async function persistCompletedReplyState2(args) {
10997
11209
  const currentState = await getPersistedThreadState(
10998
11210
  args.sessionRecord.conversationId
10999
11211
  );
11000
11212
  const conversation = coerceThreadConversationState(currentState);
11213
+ await hydrateConversationMessages({
11214
+ conversation,
11215
+ conversationId: args.sessionRecord.conversationId
11216
+ });
11001
11217
  const artifacts = coerceThreadArtifactsState(currentState);
11002
11218
  const userMessage2 = getTurnUserMessage(
11003
11219
  conversation,
@@ -11040,6 +11256,10 @@ async function persistFailedReplyState2(sessionRecord) {
11040
11256
  sessionRecord.conversationId
11041
11257
  );
11042
11258
  const conversation = coerceThreadConversationState(currentState);
11259
+ await hydrateConversationMessages({
11260
+ conversation,
11261
+ conversationId: sessionRecord.conversationId
11262
+ });
11043
11263
  clearPendingAuth(conversation, sessionRecord.sessionId);
11044
11264
  markTurnFailed({
11045
11265
  conversation,
@@ -11141,6 +11361,10 @@ async function continueSlackAgentRun(payload, options) {
11141
11361
  payload.conversationId
11142
11362
  );
11143
11363
  const conversation = coerceThreadConversationState(currentState);
11364
+ await hydrateConversationMessages({
11365
+ conversation,
11366
+ conversationId: payload.conversationId
11367
+ });
11144
11368
  const artifacts = coerceThreadArtifactsState(currentState);
11145
11369
  const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
11146
11370
  if (!userMessage2?.author?.userId) {
@@ -11162,19 +11386,32 @@ async function continueSlackAgentRun(payload, options) {
11162
11386
  payload.destination,
11163
11387
  "Slack continuation"
11164
11388
  );
11165
- const actor = await resolveContinuationActor({
11166
- conversationId: payload.conversationId,
11167
- sessionRecordActor: activeSessionRecord.actor,
11168
- teamId: destination.teamId,
11169
- userId: userMessage2.author.userId
11170
- });
11171
- if (!actor) {
11172
- await failStrandedSessionWithFallback({
11389
+ const systemActor = activeSessionRecord.actor?.platform === "system" ? activeSessionRecord.actor : void 0;
11390
+ let actor;
11391
+ let credentialContext;
11392
+ if (systemActor) {
11393
+ credentialContext = { actor: systemActor };
11394
+ } else {
11395
+ actor = await resolveContinuationActor({
11173
11396
  conversationId: payload.conversationId,
11174
- errorMessage: "Stored Slack actor missing for continuation",
11175
- sessionRecord: activeSessionRecord
11397
+ sessionRecordActor: activeSessionRecord.actor,
11398
+ teamId: destination.teamId,
11399
+ userId: userMessage2.author.userId
11176
11400
  });
11177
- return false;
11401
+ if (!actor) {
11402
+ await failStrandedSessionWithFallback({
11403
+ conversationId: payload.conversationId,
11404
+ errorMessage: "Stored Slack actor missing for continuation",
11405
+ sessionRecord: activeSessionRecord
11406
+ });
11407
+ return false;
11408
+ }
11409
+ credentialContext = {
11410
+ actor: {
11411
+ type: "user",
11412
+ userId: actor.userId
11413
+ }
11414
+ };
11178
11415
  }
11179
11416
  if (!activeSessionRecord.source) {
11180
11417
  await failAgentTurnSessionRecord({
@@ -11191,17 +11428,16 @@ async function continueSlackAgentRun(payload, options) {
11191
11428
  replyContext: {
11192
11429
  input: {
11193
11430
  conversationContext,
11194
- piMessages: conversation.piMessages,
11431
+ // Pi history is SQL-authoritative: the resumed run reads its
11432
+ // session record first and falls back to the step projection.
11433
+ piMessages: await loadProjection({
11434
+ conversationId: payload.conversationId
11435
+ }),
11195
11436
  ...getTurnUserReplyAttachmentContext(userMessage2)
11196
11437
  },
11197
11438
  routing: {
11198
- credentialContext: {
11199
- actor: {
11200
- type: "user",
11201
- userId: actor.userId
11202
- }
11203
- },
11204
- actor,
11439
+ credentialContext,
11440
+ ...actor ? { actor } : {},
11205
11441
  destination: payload.destination,
11206
11442
  source: activeSessionRecord.source,
11207
11443
  correlation: {
@@ -11209,7 +11445,7 @@ async function continueSlackAgentRun(payload, options) {
11209
11445
  turnId: payload.sessionId,
11210
11446
  channelId: thread.channelId,
11211
11447
  threadTs: thread.threadTs,
11212
- actorId: actor.userId
11448
+ ...actor ? { actorId: actor.userId } : {}
11213
11449
  },
11214
11450
  toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId
11215
11451
  },
@@ -11295,6 +11531,10 @@ async function failStrandedSessionWithFallback(args) {
11295
11531
  });
11296
11532
  const currentState = await getPersistedThreadState(args.conversationId);
11297
11533
  const conversation = coerceThreadConversationState(currentState);
11534
+ await hydrateConversationMessages({
11535
+ conversation,
11536
+ conversationId: args.conversationId
11537
+ });
11298
11538
  markTurnFailed({
11299
11539
  conversation,
11300
11540
  nowMs: Date.now(),
@@ -11464,7 +11704,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options) {
11464
11704
  },
11465
11705
  "Agent continuation lock was busy; retrying"
11466
11706
  );
11467
- await sleep2(delayMs);
11707
+ await sleep(delayMs);
11468
11708
  }
11469
11709
  }
11470
11710
  return true;
@@ -11888,12 +12128,12 @@ async function createApp(options) {
11888
12128
  app.get("/", () => GET());
11889
12129
  app.get("/health", () => GET());
11890
12130
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
11891
- return GET3(c.req.raw, c.req.param("provider"), waitUntil, {
12131
+ return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
11892
12132
  agentRunner
11893
12133
  });
11894
12134
  });
11895
12135
  app.get("/api/oauth/callback/:provider", (c) => {
11896
- return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
12136
+ return GET5(c.req.raw, c.req.param("provider"), waitUntil, {
11897
12137
  agentRunner
11898
12138
  });
11899
12139
  });
@@ -11928,6 +12168,9 @@ async function createApp(options) {
11928
12168
  app.get("/api/internal/heartbeat", (c) => {
11929
12169
  return GET2(c.req.raw, waitUntil);
11930
12170
  });
12171
+ app.get("/api/internal/retention", (c) => {
12172
+ return GET3(c.req.raw);
12173
+ });
11931
12174
  app.post("/api/webhooks/slack", (c) => {
11932
12175
  return POST3(c.req.raw, waitUntil, slackWebhookServices);
11933
12176
  });