@sentry/junior 0.94.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 (119) 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 +427 -196
  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/identities/sql.d.ts +1 -1
  28. package/dist/chat/logging.d.ts +2 -1
  29. package/dist/chat/resource-events/store.d.ts +2 -1
  30. package/dist/chat/runtime/reply-executor.d.ts +8 -0
  31. package/dist/chat/runtime/thread-state.d.ts +2 -0
  32. package/dist/chat/sandbox/resources.d.ts +4 -0
  33. package/dist/chat/services/mcp-auth-orchestration.d.ts +8 -0
  34. package/dist/chat/services/plugin-auth-orchestration.d.ts +12 -0
  35. package/dist/chat/services/turn-session-record.d.ts +14 -2
  36. package/dist/chat/sleep.d.ts +2 -0
  37. package/dist/chat/state/conversation.d.ts +7 -4
  38. package/dist/chat/state/session-log.d.ts +23 -1
  39. package/dist/chat/state/turn-session.d.ts +6 -0
  40. package/dist/chat/tool-support/private-trace-result.d.ts +4 -0
  41. package/dist/chat/tool-support/zod-tool.d.ts +1 -0
  42. package/dist/chat/tools/advisor/tool.d.ts +9 -4
  43. package/dist/chat/tools/definition.d.ts +2 -0
  44. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
  45. package/dist/chat/tools/system-time.d.ts +8 -0
  46. package/dist/chat/xml.d.ts +2 -0
  47. package/dist/{chunk-NYWPISSO.js → chunk-2XXEKR44.js} +1 -1
  48. package/dist/{chunk-QGEWC27H.js → chunk-5XMNOD3S.js} +5 -7
  49. package/dist/{chunk-NFXSLTYG.js → chunk-6ONME5IG.js} +864 -62
  50. package/dist/{chunk-LFLTJQVR.js → chunk-7FBGKXPE.js} +4 -0
  51. package/dist/{chunk-MC4ZZE4Q.js → chunk-AYP4ERVZ.js} +1 -1
  52. package/dist/{chunk-GHGPTPBL.js → chunk-B7PUBQQM.js} +1 -1
  53. package/dist/chunk-CM5EZD5F.js +17 -0
  54. package/dist/chunk-CRRBJ3JP.js +1037 -0
  55. package/dist/{chunk-G3E7SCME.js → chunk-CZEX6KIQ.js} +23 -0
  56. package/dist/{chunk-TNUUATT7.js → chunk-E3E2XNCN.js} +2 -2
  57. package/dist/{chunk-LNCREA5Z.js → chunk-EKNJM5IG.js} +3 -127
  58. package/dist/{chunk-NAPITCZ6.js → chunk-GUE7HTNR.js} +251 -6
  59. package/dist/{chunk-BTH37NCU.js → chunk-GVOUCI2O.js} +1 -1
  60. package/dist/{chunk-YYBX2BD5.js → chunk-GY34VGP5.js} +1 -1
  61. package/dist/{chunk-KRMANDHI.js → chunk-HCXXB35T.js} +1481 -349
  62. package/dist/{chunk-YTNPU7I2.js → chunk-HDTO3A74.js} +2 -2
  63. package/dist/{chunk-YJ4OPVNA.js → chunk-HZNJLKKI.js} +1 -1
  64. package/dist/{chunk-HDNWZMUN.js → chunk-KDPCGVIT.js} +116 -51
  65. package/dist/chunk-N4RNJ32C.js +48 -0
  66. package/dist/{chunk-UUB3D2XI.js → chunk-NSCHU54X.js} +8 -10
  67. package/dist/{chunk-PFXC67GJ.js → chunk-RAIB6WSG.js} +6 -80
  68. package/dist/chunk-WAGQ377B.js +724 -0
  69. package/dist/{chunk-6H4PP63X.js → chunk-WCMQJE4F.js} +24 -14
  70. package/dist/{chunk-S6QKIGRM.js → chunk-WPOJ443W.js} +15 -25
  71. package/dist/{chunk-MAVOJ54A.js → chunk-XX4N4E6C.js} +25 -388
  72. package/dist/cli/chat.js +24 -23
  73. package/dist/cli/check.js +4 -4
  74. package/dist/cli/plugins.js +11 -12
  75. package/dist/cli/snapshot-warmup.js +7 -8
  76. package/dist/cli/upgrade.js +78 -17
  77. package/dist/db/schema/agent-steps.d.ts +134 -0
  78. package/dist/db/schema/conversation-messages.d.ts +153 -0
  79. package/dist/{chat/conversations/sql → db}/schema/conversations.d.ts +34 -0
  80. package/dist/{chat/conversations/sql → db}/schema.d.ts +319 -8
  81. package/dist/db-W55B74OP.js +24 -0
  82. package/dist/deployment.d.ts +2 -0
  83. package/dist/handlers/retention.d.ts +6 -0
  84. package/dist/instrumentation.js +16 -3
  85. package/dist/legacy-import-ZPLGLAFD.js +17 -0
  86. package/dist/nitro.js +14 -4
  87. package/dist/reporting/conversations/activity.d.ts +40 -0
  88. package/dist/reporting/conversations/context.d.ts +8 -0
  89. package/dist/reporting/conversations/details.d.ts +6 -0
  90. package/dist/reporting/conversations/index.d.ts +4 -0
  91. package/dist/reporting/conversations/shared.d.ts +9 -0
  92. package/dist/reporting/conversations/statistics.d.ts +4 -0
  93. package/dist/reporting/conversations/summaries.d.ts +17 -0
  94. package/dist/reporting/conversations/transcript.d.ts +28 -0
  95. package/dist/reporting/conversations/types.d.ts +178 -0
  96. package/dist/reporting/conversations.d.ts +1 -178
  97. package/dist/reporting.d.ts +4 -3
  98. package/dist/reporting.js +603 -827
  99. package/dist/{runner-XAZPJNAQ.js → runner-OOSBDYRK.js} +35 -41
  100. package/dist/{validation-KYAWETI4.js → validation-QCGRFRFZ.js} +4 -4
  101. package/migrations/README.md +32 -0
  102. package/migrations/meta/0000_snapshot.json +978 -0
  103. package/migrations/meta/_journal.json +13 -0
  104. package/package.json +10 -7
  105. package/dist/chat/sql/schema.d.ts +0 -1049
  106. package/dist/chat/state/conversation-details.d.ts +0 -47
  107. package/dist/chunk-7STN2ZSK.js +0 -250
  108. package/dist/chunk-LE5ANBUZ.js +0 -1040
  109. package/dist/chunk-QL66APYJ.js +0 -1377
  110. package/dist/db-YDN6AFJH.js +0 -18
  111. /package/dist/{chat/sql → db}/db.d.ts +0 -0
  112. /package/dist/{chat/sql → db}/executor.d.ts +0 -0
  113. /package/dist/{chat/sql → db}/neon.d.ts +0 -0
  114. /package/dist/{chat/sql → db}/postgres.d.ts +0 -0
  115. /package/dist/{chat/conversations/sql → db}/schema/destinations.d.ts +0 -0
  116. /package/dist/{chat/conversations/sql → db}/schema/identities.d.ts +0 -0
  117. /package/dist/{chat/conversations/sql → db}/schema/migrations.d.ts +0 -0
  118. /package/dist/{chat/conversations/sql → db}/schema/timestamps.d.ts +0 -0
  119. /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-KRMANDHI.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) {
@@ -4184,6 +4384,10 @@ function completeAuthPauseTurn(args) {
4184
4384
  async function persistAuthPauseTurnState(args) {
4185
4385
  const currentState = await getPersistedThreadState(args.threadStateId);
4186
4386
  const conversation = coerceThreadConversationState(currentState);
4387
+ await hydrateConversationMessages({
4388
+ conversation,
4389
+ conversationId: args.threadStateId
4390
+ });
4187
4391
  completeAuthPauseTurn({
4188
4392
  conversation,
4189
4393
  sessionId: args.sessionId
@@ -4292,6 +4496,7 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
4292
4496
  const threadId = `slack:${channelId}:${threadTs}`;
4293
4497
  const currentState = await getPersistedThreadState(threadId);
4294
4498
  const conversation = coerceThreadConversationState(currentState);
4499
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4295
4500
  const artifacts = coerceThreadArtifactsState(currentState);
4296
4501
  const userMessage2 = getTurnUserMessage(conversation, sessionId);
4297
4502
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4330,6 +4535,7 @@ async function persistFailedReplyState(channelId, threadTs, sessionId, expectedV
4330
4535
  const threadId = `slack:${channelId}:${threadTs}`;
4331
4536
  const currentState = await getPersistedThreadState(threadId);
4332
4537
  const conversation = coerceThreadConversationState(currentState);
4538
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4333
4539
  clearPendingAuth(conversation, sessionId);
4334
4540
  markTurnFailed({
4335
4541
  conversation,
@@ -4361,6 +4567,7 @@ async function resumeAuthorizedMcpTurn(args) {
4361
4567
  const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
4362
4568
  const currentState = await getPersistedThreadState(threadId);
4363
4569
  const conversation = coerceThreadConversationState(currentState);
4570
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
4364
4571
  const pendingAuth = getConversationPendingAuth({
4365
4572
  conversation,
4366
4573
  kind: "mcp",
@@ -4397,6 +4604,10 @@ async function resumeAuthorizedMcpTurn(args) {
4397
4604
  beforeStart: async () => {
4398
4605
  const lockedState = await getPersistedThreadState(threadId);
4399
4606
  const lockedConversation = coerceThreadConversationState(lockedState);
4607
+ await hydrateConversationMessages({
4608
+ conversation: lockedConversation,
4609
+ conversationId: threadId
4610
+ });
4400
4611
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4401
4612
  const lockedPendingAuth = getConversationPendingAuth({
4402
4613
  conversation: lockedConversation,
@@ -4480,8 +4691,7 @@ async function resumeAuthorizedMcpTurn(args) {
4480
4691
  authorizationId: mcpAuthorizationId({
4481
4692
  provider,
4482
4693
  sessionId: lockedSessionId
4483
- }),
4484
- ttlMs: THREAD_STATE_TTL_MS
4694
+ })
4485
4695
  });
4486
4696
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
4487
4697
  return {
@@ -4490,7 +4700,9 @@ async function resumeAuthorizedMcpTurn(args) {
4490
4700
  replyContext: {
4491
4701
  input: {
4492
4702
  conversationContext: lockedConversationContext,
4493
- 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 }),
4494
4706
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
4495
4707
  },
4496
4708
  routing: {
@@ -4589,7 +4801,7 @@ async function resumeAuthorizedMcpTurn(args) {
4589
4801
  }
4590
4802
  });
4591
4803
  }
4592
- async function GET3(request, provider, waitUntil, options) {
4804
+ async function GET4(request, provider, waitUntil, options) {
4593
4805
  const url = new URL(request.url);
4594
4806
  const state = url.searchParams.get("state")?.trim();
4595
4807
  const code = url.searchParams.get("code")?.trim();
@@ -4636,9 +4848,6 @@ async function GET3(request, provider, waitUntil, options) {
4636
4848
  }
4637
4849
  }
4638
4850
 
4639
- // src/handlers/oauth-callback.ts
4640
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
4641
-
4642
4851
  // src/chat/slack/app-home.ts
4643
4852
  import fs from "fs";
4644
4853
  import path from "path";
@@ -4801,6 +5010,10 @@ function htmlErrorResponse(title, message, status) {
4801
5010
  async function persistCompletedOAuthReplyState(args) {
4802
5011
  const currentState = await getPersistedThreadState(args.conversationId);
4803
5012
  const conversation = coerceThreadConversationState(currentState);
5013
+ await hydrateConversationMessages({
5014
+ conversation,
5015
+ conversationId: args.conversationId
5016
+ });
4804
5017
  const artifacts = coerceThreadArtifactsState(currentState);
4805
5018
  const userMessage2 = getTurnUserMessage(conversation, args.sessionId);
4806
5019
  const statePatch = buildDeliveredTurnStatePatch({
@@ -4841,6 +5054,10 @@ async function failSessionRecordBestEffort2(args) {
4841
5054
  async function persistFailedOAuthReplyState(args) {
4842
5055
  const currentState = await getPersistedThreadState(args.conversationId);
4843
5056
  const conversation = coerceThreadConversationState(currentState);
5057
+ await hydrateConversationMessages({
5058
+ conversation,
5059
+ conversationId: args.conversationId
5060
+ });
4844
5061
  clearPendingAuth(conversation, args.sessionId);
4845
5062
  markTurnFailed({
4846
5063
  conversation,
@@ -4872,6 +5089,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4872
5089
  stored.resumeConversationId
4873
5090
  );
4874
5091
  const conversation = coerceThreadConversationState(currentState);
5092
+ await hydrateConversationMessages({
5093
+ conversation,
5094
+ conversationId: stored.resumeConversationId
5095
+ });
4875
5096
  const pendingAuth = getConversationPendingAuth({
4876
5097
  conversation,
4877
5098
  kind: "plugin",
@@ -4927,6 +5148,10 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4927
5148
  stored.resumeConversationId
4928
5149
  );
4929
5150
  const lockedConversation = coerceThreadConversationState(lockedState);
5151
+ await hydrateConversationMessages({
5152
+ conversation: lockedConversation,
5153
+ conversationId: stored.resumeConversationId
5154
+ });
4930
5155
  const lockedArtifacts = coerceThreadArtifactsState(lockedState);
4931
5156
  const lockedPendingAuth = getConversationPendingAuth({
4932
5157
  conversation: lockedConversation,
@@ -5011,8 +5236,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5011
5236
  authorizationId: pluginAuthorizationId({
5012
5237
  provider: stored.provider,
5013
5238
  sessionId: lockedSessionId
5014
- }),
5015
- ttlMs: THREAD_STATE_TTL_MS2
5239
+ })
5016
5240
  });
5017
5241
  const lockedMessageTs = getTurnUserSlackMessageTs(lockedUserMessage);
5018
5242
  return {
@@ -5021,7 +5245,11 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5021
5245
  replyContext: {
5022
5246
  input: {
5023
5247
  conversationContext: lockedConversationContext,
5024
- 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
+ }),
5025
5253
  ...getTurnUserReplyAttachmentContext(lockedUserMessage)
5026
5254
  },
5027
5255
  routing: {
@@ -5124,6 +5352,7 @@ async function resumePendingOAuthMessage(stored, options) {
5124
5352
  const conversation = coerceThreadConversationState(
5125
5353
  await getPersistedThreadState(threadId)
5126
5354
  );
5355
+ await hydrateConversationMessages({ conversation, conversationId: threadId });
5127
5356
  const latestUserMessage = [...conversation.messages].reverse().find((message) => message.role === "user");
5128
5357
  const conversationContext = buildConversationContext(conversation, {
5129
5358
  excludeMessageId: latestUserMessage?.id
@@ -5144,7 +5373,8 @@ async function resumePendingOAuthMessage(stored, options) {
5144
5373
  replyContext: {
5145
5374
  input: {
5146
5375
  conversationContext,
5147
- piMessages: conversation.piMessages
5376
+ // Pi history is SQL-authoritative via the step-store projection.
5377
+ piMessages: await loadProjection({ conversationId: threadId })
5148
5378
  },
5149
5379
  routing: {
5150
5380
  credentialContext: {
@@ -5178,7 +5408,7 @@ async function resumePendingOAuthMessage(stored, options) {
5178
5408
  }
5179
5409
  });
5180
5410
  }
5181
- async function GET4(request, provider, waitUntil, options) {
5411
+ async function GET5(request, provider, waitUntil, options) {
5182
5412
  const providerConfig = pluginCatalogRuntime.getOAuthConfig(provider);
5183
5413
  if (!providerConfig) {
5184
5414
  return htmlErrorResponse(
@@ -8305,7 +8535,6 @@ function createSlackTurnRuntime(deps) {
8305
8535
  }
8306
8536
 
8307
8537
  // src/chat/services/context-compaction.ts
8308
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
8309
8538
  import {
8310
8539
  estimateContextTokens,
8311
8540
  estimateTokens
@@ -8363,6 +8592,10 @@ function userMessage(text) {
8363
8592
  timestamp: Date.now()
8364
8593
  };
8365
8594
  }
8595
+ function piMessageTimestamp(message) {
8596
+ const timestamp = message.timestamp;
8597
+ return typeof timestamp === "number" ? timestamp : Date.now();
8598
+ }
8366
8599
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
8367
8600
  const selected = [];
8368
8601
  let remaining = maxTokens;
@@ -8545,6 +8778,7 @@ async function maybeCompactWithDeps(args, deps) {
8545
8778
  });
8546
8779
  }
8547
8780
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
8781
+ const stepStore = getAgentStepStore();
8548
8782
  const sourceProjection = await loadProjectionWithProvenance({
8549
8783
  conversationId: args.conversationId
8550
8784
  });
@@ -8556,14 +8790,17 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
8556
8790
  userMessage(`${COMPACTION_SUMMARY_PREFIX}
8557
8791
  ${summary}`)
8558
8792
  ];
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
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
+ }))
8567
8804
  });
8568
8805
  updateConversationStats(args.conversation);
8569
8806
  setSpanAttributes({
@@ -9183,7 +9420,6 @@ function createJuniorRuntimeServices(overrides = {}) {
9183
9420
  }
9184
9421
 
9185
9422
  // src/chat/runtime/reply-executor.ts
9186
- import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
9187
9423
  import { createSlackSource } from "@sentry/junior-plugin-api";
9188
9424
 
9189
9425
  // src/chat/slack/message.ts
@@ -9311,8 +9547,8 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
9311
9547
  }
9312
9548
 
9313
9549
  // src/chat/runtime/reply-executor.ts
9314
- async function persistThreadStateWithRetry(thread, patch) {
9315
- await persistWithRetry(() => persistThreadState(thread, patch));
9550
+ async function persistThreadRuntimeStateWithRetry(thread, patch) {
9551
+ await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
9316
9552
  }
9317
9553
  function collectCanvasUrls(artifacts) {
9318
9554
  return new Set(
@@ -9322,9 +9558,6 @@ function collectCanvasUrls(artifacts) {
9322
9558
  ].filter((url) => typeof url === "string" && url !== "")
9323
9559
  );
9324
9560
  }
9325
- function turnActor(actor) {
9326
- return toStoredSlackActor(actor);
9327
- }
9328
9561
  function parkedInputKey(message) {
9329
9562
  if (message.role !== "user") {
9330
9563
  return void 0;
@@ -9437,9 +9670,8 @@ function collectTurnAttachments(message, queuedMessages) {
9437
9670
  ];
9438
9671
  }
9439
9672
  async function loadPiMessagesForTurn(args) {
9440
- const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
9441
9673
  if (!args.conversationId) {
9442
- return { piMessages: fallback };
9674
+ return {};
9443
9675
  }
9444
9676
  if (args.activeTurnId) {
9445
9677
  const sessionRecord = await getAgentTurnSessionRecord(
@@ -9463,7 +9695,7 @@ async function loadPiMessagesForTurn(args) {
9463
9695
  piMessages: projection
9464
9696
  };
9465
9697
  }
9466
- return { piMessages: fallback };
9698
+ return {};
9467
9699
  }
9468
9700
  function createReplyToThread(deps) {
9469
9701
  return async function replyToThread(thread, message, options) {
@@ -9540,7 +9772,6 @@ function createReplyToThread(deps) {
9540
9772
  deps.services.lookupSlackUser
9541
9773
  ) : void 0;
9542
9774
  const executionActor = "type" in credentialContext.actor ? actor : credentialContext.actor;
9543
- const storedActor = actor ? turnActor(actor) : void 0;
9544
9775
  const slackActorId = actor?.userId;
9545
9776
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
9546
9777
  thread,
@@ -9676,8 +9907,7 @@ function createReplyToThread(deps) {
9676
9907
  provenance: [
9677
9908
  ...projection.provenance,
9678
9909
  ...missing.map((pair) => pair.provenance)
9679
- ],
9680
- ttlMs: THREAD_STATE_TTL_MS4
9910
+ ]
9681
9911
  });
9682
9912
  return true;
9683
9913
  } finally {
@@ -9854,37 +10084,6 @@ function createReplyToThread(deps) {
9854
10084
  "Failed to record running turn summary"
9855
10085
  );
9856
10086
  });
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
10087
  }
9889
10088
  setTags({
9890
10089
  conversationId
@@ -9967,8 +10166,7 @@ function createReplyToThread(deps) {
9967
10166
  try {
9968
10167
  const loadedPiMessages = await loadPiMessagesForTurn({
9969
10168
  conversationId,
9970
- activeTurnId,
9971
- fallback: preparedState.conversation.piMessages
10169
+ activeTurnId
9972
10170
  });
9973
10171
  let piMessages = loadedPiMessages.piMessages;
9974
10172
  if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
@@ -10043,17 +10241,19 @@ function createReplyToThread(deps) {
10043
10241
  };
10044
10242
  if (conversationId && titleUpdateResult.title) {
10045
10243
  try {
10046
- await setConversationTitle(conversationId, {
10047
- displayTitle: titleUpdateResult.title,
10048
- titleSourceMessageId: titleUpdateResult.sourceMessageId
10244
+ await getConversationStore().recordActivity({
10245
+ activityAtMs: message.metadata.dateSent.getTime(),
10246
+ conversationId,
10247
+ nowMs: Date.now(),
10248
+ title: titleUpdateResult.title
10049
10249
  });
10050
10250
  } catch (error) {
10051
10251
  logException(
10052
10252
  error,
10053
- "conversation_details_title_set_failed",
10253
+ "conversation_title_persist_failed",
10054
10254
  turnTraceContext,
10055
10255
  {},
10056
- "Failed to set conversation title in details record"
10256
+ "Failed to persist generated conversation title"
10057
10257
  );
10058
10258
  }
10059
10259
  }
@@ -10369,6 +10569,7 @@ function createReplyToThread(deps) {
10369
10569
  conversationId,
10370
10570
  durationMs: reply.diagnostics.durationMs,
10371
10571
  usage: reply.diagnostics.usage,
10572
+ reasoningLevel: reply.diagnostics.thinkingLevel,
10372
10573
  destination,
10373
10574
  destinationVisibility,
10374
10575
  source,
@@ -10403,11 +10604,15 @@ function createReplyToThread(deps) {
10403
10604
  traceId: getActiveTraceId()
10404
10605
  });
10405
10606
  }
10406
- await persistThreadStateWithRetry(thread, {
10407
- ...completedState
10408
- });
10607
+ await persistWithRetry(
10608
+ () => persistConversationMessages({
10609
+ conversation: completedState.conversation,
10610
+ conversationId
10611
+ })
10612
+ );
10613
+ await persistThreadRuntimeStateWithRetry(thread, completedState);
10409
10614
  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, {
10615
+ await persistThreadRuntimeStateWithRetry(thread, {
10411
10616
  artifacts: latestArtifacts
10412
10617
  });
10413
10618
  }
@@ -10755,6 +10960,8 @@ function createPrepareTurnState(deps) {
10755
10960
  ) : void 0;
10756
10961
  const artifacts = coerceThreadArtifactsState(existingState);
10757
10962
  const conversation = coerceThreadConversationState(existingState);
10963
+ const conversationId = args.context.threadId ?? args.context.runId;
10964
+ await hydrateConversationMessages({ conversation, conversationId });
10758
10965
  const channelConfiguration = getChannelConfigurationService(args.thread);
10759
10966
  const configuration = await channelConfiguration.resolveValues();
10760
10967
  await seedConversationBackfill(args.thread, conversation, {
@@ -10794,12 +11001,16 @@ function createPrepareTurnState(deps) {
10794
11001
  threadTs: getThreadTs(args.context.threadId)
10795
11002
  });
10796
11003
  }
11004
+ await persistConversationMessages({ conversation, conversationId });
10797
11005
  await deps.compactConversationIfNeeded(conversation, {
10798
11006
  threadId: args.context.threadId,
10799
11007
  channelId: args.context.channelId,
10800
11008
  actorId: args.context.actorId,
10801
11009
  runId: args.context.runId
10802
11010
  });
11011
+ if (conversationId) {
11012
+ await persistConversationCompactions({ conversation, conversationId });
11013
+ }
10803
11014
  const conversationContext = buildConversationContext(conversation, {
10804
11015
  excludeMessageId: userMessageId
10805
11016
  });
@@ -10994,14 +11205,15 @@ function createJuniorSlackAdapter(config) {
10994
11205
 
10995
11206
  // src/chat/runtime/agent-continue-runner.ts
10996
11207
  var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
10997
- function sleep2(ms) {
10998
- return new Promise((resolve) => setTimeout(resolve, ms));
10999
- }
11000
11208
  async function persistCompletedReplyState2(args) {
11001
11209
  const currentState = await getPersistedThreadState(
11002
11210
  args.sessionRecord.conversationId
11003
11211
  );
11004
11212
  const conversation = coerceThreadConversationState(currentState);
11213
+ await hydrateConversationMessages({
11214
+ conversation,
11215
+ conversationId: args.sessionRecord.conversationId
11216
+ });
11005
11217
  const artifacts = coerceThreadArtifactsState(currentState);
11006
11218
  const userMessage2 = getTurnUserMessage(
11007
11219
  conversation,
@@ -11044,6 +11256,10 @@ async function persistFailedReplyState2(sessionRecord) {
11044
11256
  sessionRecord.conversationId
11045
11257
  );
11046
11258
  const conversation = coerceThreadConversationState(currentState);
11259
+ await hydrateConversationMessages({
11260
+ conversation,
11261
+ conversationId: sessionRecord.conversationId
11262
+ });
11047
11263
  clearPendingAuth(conversation, sessionRecord.sessionId);
11048
11264
  markTurnFailed({
11049
11265
  conversation,
@@ -11145,6 +11361,10 @@ async function continueSlackAgentRun(payload, options) {
11145
11361
  payload.conversationId
11146
11362
  );
11147
11363
  const conversation = coerceThreadConversationState(currentState);
11364
+ await hydrateConversationMessages({
11365
+ conversation,
11366
+ conversationId: payload.conversationId
11367
+ });
11148
11368
  const artifacts = coerceThreadArtifactsState(currentState);
11149
11369
  const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
11150
11370
  if (!userMessage2?.author?.userId) {
@@ -11208,7 +11428,11 @@ async function continueSlackAgentRun(payload, options) {
11208
11428
  replyContext: {
11209
11429
  input: {
11210
11430
  conversationContext,
11211
- 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
+ }),
11212
11436
  ...getTurnUserReplyAttachmentContext(userMessage2)
11213
11437
  },
11214
11438
  routing: {
@@ -11307,6 +11531,10 @@ async function failStrandedSessionWithFallback(args) {
11307
11531
  });
11308
11532
  const currentState = await getPersistedThreadState(args.conversationId);
11309
11533
  const conversation = coerceThreadConversationState(currentState);
11534
+ await hydrateConversationMessages({
11535
+ conversation,
11536
+ conversationId: args.conversationId
11537
+ });
11310
11538
  markTurnFailed({
11311
11539
  conversation,
11312
11540
  nowMs: Date.now(),
@@ -11476,7 +11704,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options) {
11476
11704
  },
11477
11705
  "Agent continuation lock was busy; retrying"
11478
11706
  );
11479
- await sleep2(delayMs);
11707
+ await sleep(delayMs);
11480
11708
  }
11481
11709
  }
11482
11710
  return true;
@@ -11900,12 +12128,12 @@ async function createApp(options) {
11900
12128
  app.get("/", () => GET());
11901
12129
  app.get("/health", () => GET());
11902
12130
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
11903
- return GET3(c.req.raw, c.req.param("provider"), waitUntil, {
12131
+ return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
11904
12132
  agentRunner
11905
12133
  });
11906
12134
  });
11907
12135
  app.get("/api/oauth/callback/:provider", (c) => {
11908
- return GET4(c.req.raw, c.req.param("provider"), waitUntil, {
12136
+ return GET5(c.req.raw, c.req.param("provider"), waitUntil, {
11909
12137
  agentRunner
11910
12138
  });
11911
12139
  });
@@ -11940,6 +12168,9 @@ async function createApp(options) {
11940
12168
  app.get("/api/internal/heartbeat", (c) => {
11941
12169
  return GET2(c.req.raw, waitUntil);
11942
12170
  });
12171
+ app.get("/api/internal/retention", (c) => {
12172
+ return GET3(c.req.raw);
12173
+ });
11943
12174
  app.post("/api/webhooks/slack", (c) => {
11944
12175
  return POST3(c.req.raw, waitUntil, slackWebhookServices);
11945
12176
  });