@sentry/junior 0.106.0 → 0.107.1

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 (132) hide show
  1. package/dist/{agent-hooks-KR5BCWOK.js → agent-hooks-ICPIJAFY.js} +9 -7
  2. package/dist/api/conversations/detail.d.ts +1 -1
  3. package/dist/api/conversations/events.d.ts +14 -0
  4. package/dist/api/conversations/schema.d.ts +196 -463
  5. package/dist/api/schema.d.ts +2 -7
  6. package/dist/api/schema.js +10 -8
  7. package/dist/api.js +377 -810
  8. package/dist/app.js +3586 -3614
  9. package/dist/{catalog-runtime-5CVM2DOF.js → catalog-runtime-FOFNPR3P.js} +3 -2
  10. package/dist/chat/agent/prompt.d.ts +1 -1
  11. package/dist/chat/agent/request.d.ts +28 -29
  12. package/dist/chat/agent/resume.d.ts +7 -7
  13. package/dist/chat/agent/session.d.ts +5 -6
  14. package/dist/chat/agent/tools.d.ts +3 -3
  15. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  16. package/dist/chat/config.d.ts +2 -2
  17. package/dist/chat/conversations/history.d.ts +582 -167
  18. package/dist/chat/conversations/message-projection.d.ts +6 -0
  19. package/dist/chat/conversations/message-summaries.d.ts +9 -0
  20. package/dist/chat/conversations/messages.d.ts +13 -27
  21. package/dist/chat/conversations/projection.d.ts +30 -53
  22. package/dist/chat/conversations/provenance.d.ts +42 -0
  23. package/dist/chat/conversations/search.d.ts +2 -2
  24. package/dist/chat/conversations/sql/conversation-row.d.ts +1 -1
  25. package/dist/chat/conversations/sql/event-lock.d.ts +3 -0
  26. package/dist/chat/conversations/sql/history.d.ts +3 -3
  27. package/dist/chat/conversations/sql/privacy.d.ts +27 -0
  28. package/dist/chat/conversations/sql/purge.d.ts +2 -10
  29. package/dist/chat/conversations/sql/store.d.ts +0 -5
  30. package/dist/chat/conversations/store.d.ts +8 -13
  31. package/dist/chat/conversations/turn-lifecycle.d.ts +41 -0
  32. package/dist/chat/db.d.ts +4 -10
  33. package/dist/chat/model-profile.d.ts +10 -0
  34. package/dist/chat/oauth-flow.d.ts +0 -5
  35. package/dist/chat/pi/conversation-events.d.ts +33 -0
  36. package/dist/chat/pi/sql-model-usage.d.ts +10 -0
  37. package/dist/chat/plugins/agent-hooks.d.ts +4 -2
  38. package/dist/chat/resource-events/ingest.d.ts +0 -13
  39. package/dist/chat/runtime/delivered-turn-state.d.ts +1 -1
  40. package/dist/chat/runtime/generated-artifacts.d.ts +1 -1
  41. package/dist/chat/runtime/reply-executor.d.ts +2 -0
  42. package/dist/chat/runtime/slack-resume.d.ts +23 -28
  43. package/dist/chat/runtime/slack-runtime.d.ts +2 -0
  44. package/dist/chat/runtime/turn.d.ts +14 -1
  45. package/dist/chat/services/context-compaction-marker.d.ts +0 -1
  46. package/dist/chat/services/context-compaction.d.ts +1 -0
  47. package/dist/chat/services/conversation-memory.d.ts +9 -0
  48. package/dist/chat/services/plugin-auth-orchestration.d.ts +1 -4
  49. package/dist/chat/services/subscribed-decision.d.ts +1 -1
  50. package/dist/chat/services/turn-failure-response.d.ts +10 -3
  51. package/dist/chat/services/turn-result.d.ts +7 -12
  52. package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
  53. package/dist/chat/services/turn-session-record.d.ts +15 -16
  54. package/dist/chat/slack/adapter.d.ts +2 -2
  55. package/dist/chat/slack/errors.d.ts +2 -0
  56. package/dist/chat/slack/outbound.d.ts +0 -1
  57. package/dist/chat/slack/reply.d.ts +2 -8
  58. package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
  59. package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
  60. package/dist/chat/state/conversation.d.ts +4 -4
  61. package/dist/chat/state/turn-id.d.ts +2 -0
  62. package/dist/chat/state/turn-session-keys.d.ts +6 -0
  63. package/dist/chat/state/turn-session.d.ts +11 -9
  64. package/dist/chat/task-execution/state.d.ts +6 -1
  65. package/dist/chat/task-execution/store.d.ts +1 -1
  66. package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
  67. package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
  68. package/dist/chat/tools/handoff/tool.d.ts +2 -2
  69. package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
  70. package/dist/chat/tools/types.d.ts +6 -9
  71. package/dist/chat/tools/web/image-generate.d.ts +4 -6
  72. package/dist/{chunk-DDRQ4U7I.js → chunk-4YF7Z6IA.js} +390 -270
  73. package/dist/{chunk-AZ5OMSN5.js → chunk-AUUOHQAT.js} +2 -2
  74. package/dist/chunk-B2Z2H66D.js +250 -0
  75. package/dist/{chunk-4W4O4DCV.js → chunk-CQ7KSO2B.js} +75 -45
  76. package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
  77. package/dist/{chunk-GPPUTY5M.js → chunk-EDLNHZH3.js} +861 -891
  78. package/dist/{chunk-R5BFYK2O.js → chunk-IGHMVDWI.js} +134 -18
  79. package/dist/chunk-MU6HHZEN.js +99 -0
  80. package/dist/{chunk-XTKJPSMF.js → chunk-NVOTGWYX.js} +458 -684
  81. package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
  82. package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
  83. package/dist/{chunk-ZMRLBBD5.js → chunk-RMVOAJRL.js} +30 -13
  84. package/dist/{chunk-3NYUCBLE.js → chunk-SPUAJVVH.js} +6 -4
  85. package/dist/{chunk-4UYIRWDW.js → chunk-SS67LUOK.js} +41 -290
  86. package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
  87. package/dist/{chunk-3MPPVPGZ.js → chunk-VH6KWKG2.js} +2 -2
  88. package/dist/chunk-VIXFG2VC.js +182 -0
  89. package/dist/{chunk-BKM4UXUD.js → chunk-YFQ7CQDE.js} +669 -18
  90. package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
  91. package/dist/{chunk-3OUSJPR7.js → chunk-YNP2ATQX.js} +6 -4
  92. package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
  93. package/dist/cli/chat.js +21 -20
  94. package/dist/cli/check.js +4 -3
  95. package/dist/cli/plugins.js +11 -9
  96. package/dist/cli/snapshot-warmup.js +7 -5
  97. package/dist/cli/upgrade.js +2041 -105
  98. package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
  99. package/dist/db/schema.d.ts +28 -159
  100. package/dist/{db-S33LFWG2.js → db-DIGO4TGW.js} +6 -8
  101. package/dist/{runner-HA5XAL6K.js → runner-ACR2HAIC.js} +209 -101
  102. package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
  103. package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
  104. package/migrations/0005_conversation_events.sql +122 -0
  105. package/migrations/README.md +23 -10
  106. package/migrations/meta/0005_snapshot.json +948 -0
  107. package/migrations/meta/_journal.json +7 -0
  108. package/package.json +5 -5
  109. package/dist/api/conversations/activity.d.ts +0 -40
  110. package/dist/api/conversations/detail-projection.d.ts +0 -11
  111. package/dist/api/conversations/detail.query.d.ts +0 -3
  112. package/dist/api/conversations/subagent.d.ts +0 -10
  113. package/dist/api/conversations/transcript.d.ts +0 -28
  114. package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
  115. package/dist/chat/conversations/legacy-import.d.ts +0 -38
  116. package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
  117. package/dist/chat/conversations/sql/messages.d.ts +0 -4
  118. package/dist/chat/conversations/visible-compactions.d.ts +0 -11
  119. package/dist/chat/conversations/visible-messages.d.ts +0 -47
  120. package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
  121. package/dist/chat/state/session-log.d.ts +0 -384
  122. package/dist/chunk-P4P7O5WG.js +0 -1072
  123. package/dist/chunk-XIMUQ4IU.js +0 -147
  124. package/dist/db/schema/conversation-messages.d.ts +0 -153
  125. package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
  126. package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
  127. package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
  128. package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
  129. package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
  130. package/dist/handlers/github-webhook/resource.d.ts +0 -10
  131. package/dist/handlers/github-webhook.d.ts +0 -15
  132. package/dist/legacy-import-TMJISRKO.js +0 -18
@@ -1,64 +1,73 @@
1
1
  import {
2
+ ConversationTurnLifecycleService,
2
3
  buildDeliveredTurnStatePatch,
3
- finalizeFailedTurnReply,
4
+ finalizeFailedTurnReplyWithEvent,
4
5
  processPluginTask,
5
6
  scheduleSessionCompletedPluginTasks
6
- } from "./chunk-4W4O4DCV.js";
7
+ } from "./chunk-CQ7KSO2B.js";
7
8
  import "./chunk-KNFROR7R.js";
8
9
  import {
10
+ buildConversationContext,
9
11
  coerceThreadArtifactsState,
10
12
  completeDeliveredTurn,
11
13
  getPersistedSandboxState,
12
14
  getPersistedThreadState,
13
- markTurnFailed,
14
- persistThreadStateById,
15
- startActiveTurn
16
- } from "./chunk-BKM4UXUD.js";
17
- import {
18
- coerceThreadConversationState
19
- } from "./chunk-XIMUQ4IU.js";
20
- import {
21
- commitMessages,
22
- loadProjection
23
- } from "./chunk-DDRQ4U7I.js";
24
- import {
25
- buildConversationContext,
26
15
  hydrateConversationMessages,
27
16
  markConversationMessage,
17
+ markTurnFailed,
28
18
  normalizeConversationText,
19
+ persistConversationMessages,
20
+ persistThreadStateById,
21
+ persistWithRetry,
22
+ recordDeliveredAssistantMessage,
23
+ startActiveTurn,
29
24
  updateConversationStats,
30
25
  upsertConversationMessage
31
- } from "./chunk-P4P7O5WG.js";
26
+ } from "./chunk-YFQ7CQDE.js";
27
+ import {
28
+ coerceThreadConversationState,
29
+ loadProjection
30
+ } from "./chunk-4YF7Z6IA.js";
31
+ import "./chunk-MU6HHZEN.js";
32
32
  import "./chunk-PDO5BLNM.js";
33
- import "./chunk-ZMRLBBD5.js";
34
- import "./chunk-AZ5OMSN5.js";
33
+ import "./chunk-RMVOAJRL.js";
34
+ import "./chunk-AUUOHQAT.js";
35
35
  import "./chunk-G3E7SCME.js";
36
- import "./chunk-3MPPVPGZ.js";
37
- import "./chunk-XTKJPSMF.js";
36
+ import "./chunk-B2Z2H66D.js";
37
+ import "./chunk-VH6KWKG2.js";
38
38
  import {
39
- sleep
40
- } from "./chunk-4ZNGQH7C.js";
39
+ getConversationEventStore
40
+ } from "./chunk-NVOTGWYX.js";
41
+ import "./chunk-4ZNGQH7C.js";
41
42
  import {
42
43
  stripRuntimeTurnContext,
43
44
  trimTrailingAssistantMessages
44
- } from "./chunk-4UYIRWDW.js";
45
- import "./chunk-FXFQNHRU.js";
45
+ } from "./chunk-SS67LUOK.js";
46
+ import "./chunk-VFUK3X5B.js";
46
47
  import "./chunk-RT5BUHR5.js";
47
48
  import "./chunk-B7PUBQQM.js";
49
+ import "./chunk-VIXFG2VC.js";
48
50
  import {
49
51
  logException
50
- } from "./chunk-AEWZ7F65.js";
52
+ } from "./chunk-O24R4DCK.js";
51
53
  import "./chunk-OB42YVAE.js";
52
54
  import "./chunk-6GWA276C.js";
53
55
  import "./chunk-7FBGKXPE.js";
54
56
  import "./chunk-MLKGABMK.js";
55
57
 
56
58
  // src/chat/local/runner.ts
59
+ import { randomUUID } from "crypto";
57
60
  import {
58
61
  createLocalSource,
59
62
  localDestinationSchema
60
63
  } from "@sentry/junior-plugin-api";
61
- var DELIVERED_STATE_PERSIST_ATTEMPTS = 3;
64
+ var SENTRY_EVENT_ID_PATTERN = /^[a-f0-9]{32}$/i;
65
+ var LOCAL_FAILURE_EVENT_NAMES = {
66
+ agent_run_failed: "local_agent_run_failed",
67
+ delivery_failed: "local_reply_delivery_failed",
68
+ model_execution_failed: "local_model_execution_failed",
69
+ persistence_failed: "local_turn_persistence_failed"
70
+ };
62
71
  function localDestination(conversationId) {
63
72
  const parsed = localDestinationSchema.safeParse({
64
73
  platform: "local",
@@ -69,16 +78,18 @@ function localDestination(conversationId) {
69
78
  }
70
79
  return parsed.data;
71
80
  }
72
- function localTurnId(sequence) {
73
- return `local-turn-${sequence}`;
74
- }
75
- function localReply(reply) {
76
- return {
77
- text: reply.text
78
- };
81
+ function localTurnId() {
82
+ return `local-turn-${randomUUID()}`;
79
83
  }
80
- function nextUserMessageSequence(conversation) {
81
- return conversation.messages.filter((message) => message.role === "user").length + 1;
84
+ function captureLocalBoundaryFailure(args) {
85
+ const eventId = args.capture(
86
+ args.error,
87
+ LOCAL_FAILURE_EVENT_NAMES[args.failureCode],
88
+ { conversationId: args.conversationId, runId: args.turnId },
89
+ { "app.ai.failure_code": args.failureCode },
90
+ "Local agent turn failed at its owning runtime boundary"
91
+ );
92
+ return eventId && SENTRY_EVENT_ID_PATTERN.test(eventId) ? eventId : void 0;
82
93
  }
83
94
  async function loadLocalPiMessages(args) {
84
95
  const projection = await loadProjection({
@@ -89,21 +100,6 @@ async function loadLocalPiMessages(args) {
89
100
  }
90
101
  return stripRuntimeTurnContext(trimTrailingAssistantMessages(projection));
91
102
  }
92
- async function persistDeliveredLocalTurnState(conversationId, patch) {
93
- let lastError;
94
- for (let attempt = 1; attempt <= DELIVERED_STATE_PERSIST_ATTEMPTS; attempt += 1) {
95
- try {
96
- await persistThreadStateById(conversationId, patch);
97
- return;
98
- } catch (error) {
99
- lastError = error;
100
- if (attempt < DELIVERED_STATE_PERSIST_ATTEMPTS) {
101
- await sleep(attempt * 100);
102
- }
103
- }
104
- }
105
- throw lastError;
106
- }
107
103
  async function runLocalAgentTurn(input, deps) {
108
104
  const text = input.message.trim();
109
105
  if (!text) {
@@ -114,6 +110,7 @@ async function runLocalAgentTurn(input, deps) {
114
110
  }
115
111
  const destination = localDestination(input.conversationId);
116
112
  const source = createLocalSource(destination.conversationId);
113
+ const lifecycle = deps.turnLifecycle ?? new ConversationTurnLifecycleService(getConversationEventStore());
117
114
  const now = deps.now ?? (() => Date.now());
118
115
  const persisted = await getPersistedThreadState(input.conversationId);
119
116
  const conversation = coerceThreadConversationState(persisted);
@@ -126,8 +123,7 @@ async function runLocalAgentTurn(input, deps) {
126
123
  const initialArtifacts = artifacts;
127
124
  const initialSandboxId = sandboxId;
128
125
  const initialSandboxDependencyProfileHash = sandboxDependencyProfileHash;
129
- const sequence = nextUserMessageSequence(conversation);
130
- const turnId = localTurnId(sequence);
126
+ const turnId = localTurnId();
131
127
  const userMessageId = `${turnId}:user`;
132
128
  const startedAtMs = now();
133
129
  upsertConversationMessage(conversation, {
@@ -145,27 +141,75 @@ async function runLocalAgentTurn(input, deps) {
145
141
  replied: false
146
142
  }
147
143
  });
144
+ await persistConversationMessages({
145
+ conversation,
146
+ conversationId: input.conversationId
147
+ });
148
+ await lifecycle.start({
149
+ conversationId: input.conversationId,
150
+ createdAtMs: now(),
151
+ inputMessageIds: [userMessageId],
152
+ surface: "internal",
153
+ turnId
154
+ });
148
155
  startActiveTurn({
149
156
  conversation,
150
157
  nextTurnId: turnId,
151
158
  updateConversationStats
152
159
  });
153
- await persistThreadStateById(input.conversationId, { conversation });
154
160
  let reply;
155
161
  let completedState;
156
- let piMessagesBeforeRun;
162
+ let failureCode = "persistence_failed";
163
+ let modelFailureEventId;
164
+ let modelFailureCaptureAttempted = false;
157
165
  const localActor = {
158
166
  fullName: "Local CLI",
159
167
  platform: "local",
160
168
  userId: "local-cli",
161
169
  userName: "local"
162
170
  };
171
+ let assistantMessageDelivered = false;
172
+ const deliverAssistantMessage = async (message) => {
173
+ if (!message.text.trim()) {
174
+ return;
175
+ }
176
+ failureCode = "delivery_failed";
177
+ await deps.deliverReply({ text: message.text });
178
+ assistantMessageDelivered = true;
179
+ recordDeliveredAssistantMessage({
180
+ conversation,
181
+ sessionId: turnId,
182
+ text: message.text,
183
+ userMessageId
184
+ });
185
+ try {
186
+ await persistWithRetry(
187
+ () => persistConversationMessages({
188
+ conversation,
189
+ conversationId: input.conversationId
190
+ })
191
+ );
192
+ } catch (error) {
193
+ logException(
194
+ new Error("Accepted assistant message persistence failed"),
195
+ "local_assistant_message_post_delivery_persist_failed",
196
+ { conversationId: input.conversationId },
197
+ { "error.type": error instanceof Error ? error.name : typeof error },
198
+ "Failed to persist an accepted local assistant message"
199
+ );
200
+ }
201
+ failureCode = "agent_run_failed";
202
+ };
163
203
  try {
164
- const piMessages = await loadLocalPiMessages({
204
+ await persistThreadStateById(input.conversationId, { conversation });
205
+ const piMessages = await (deps.loadPiMessages ?? loadLocalPiMessages)({
165
206
  conversationId: input.conversationId
166
207
  });
167
- piMessagesBeforeRun = piMessages;
208
+ failureCode = "agent_run_failed";
168
209
  const outcome = await deps.agentRunner.run({
210
+ conversationId: input.conversationId,
211
+ turnId,
212
+ runId: turnId,
169
213
  input: {
170
214
  messageText: text,
171
215
  conversationContext: buildConversationContext(conversation, {
@@ -180,12 +224,7 @@ async function runLocalAgentTurn(input, deps) {
180
224
  destination,
181
225
  source,
182
226
  actor: localActor,
183
- surface: "internal",
184
- correlation: {
185
- conversationId: input.conversationId,
186
- turnId,
187
- runId: turnId
188
- }
227
+ surface: "internal"
189
228
  },
190
229
  policy: {
191
230
  authorizationFlowMode: "disabled"
@@ -201,7 +240,6 @@ async function runLocalAgentTurn(input, deps) {
201
240
  onStatus: async (status) => {
202
241
  await deps.onStatus?.(status.text);
203
242
  },
204
- onTextDelta: deps.onTextDelta,
205
243
  onToolInvocation: async (invocation) => {
206
244
  await deps.onToolInvocation?.(invocation);
207
245
  },
@@ -209,6 +247,9 @@ async function runLocalAgentTurn(input, deps) {
209
247
  await deps.onToolResult?.(result);
210
248
  }
211
249
  },
250
+ delivery: {
251
+ onAssistantMessage: deliverAssistantMessage
252
+ },
212
253
  durability: {
213
254
  onArtifactStateUpdated: async (nextArtifacts) => {
214
255
  artifacts = nextArtifacts;
@@ -235,11 +276,17 @@ async function runLocalAgentTurn(input, deps) {
235
276
  throw new Error(`Local agent run ended with ${outcome.status}`);
236
277
  }
237
278
  reply = outcome.result;
238
- reply = finalizeFailedTurnReply({
279
+ modelFailureCaptureAttempted = reply.diagnostics.outcome !== "success";
280
+ const finalized = finalizeFailedTurnReplyWithEvent({
239
281
  reply,
240
- logException,
282
+ logException: deps.logException ?? logException,
241
283
  context: { conversationId: input.conversationId }
242
284
  });
285
+ reply = finalized.reply;
286
+ modelFailureEventId = finalized.eventId;
287
+ if (reply.diagnostics.outcome !== "success") {
288
+ await deliverAssistantMessage({ text: reply.text });
289
+ }
243
290
  completedState = buildDeliveredTurnStatePatch({
244
291
  artifacts,
245
292
  conversation,
@@ -247,52 +294,113 @@ async function runLocalAgentTurn(input, deps) {
247
294
  sessionId: turnId,
248
295
  userMessageId
249
296
  });
250
- await deps.deliverReply(localReply(reply));
251
297
  } catch (error) {
252
- if (reply) {
253
- await commitMessages({
298
+ const failureEventId = modelFailureCaptureAttempted && failureCode === "agent_run_failed" ? modelFailureEventId : captureLocalBoundaryFailure({
299
+ capture: deps.logException ?? logException,
300
+ conversationId: input.conversationId,
301
+ error,
302
+ failureCode,
303
+ turnId
304
+ });
305
+ try {
306
+ markTurnFailed({
307
+ conversation,
308
+ nowMs: now(),
309
+ sessionId: turnId,
310
+ userMessageId,
311
+ markConversationMessage,
312
+ updateConversationStats
313
+ });
314
+ await persistThreadStateById(input.conversationId, {
315
+ artifacts: initialArtifacts,
316
+ conversation,
317
+ sandboxId: initialSandboxId ?? "",
318
+ sandboxDependencyProfileHash: initialSandboxDependencyProfileHash ?? ""
319
+ });
320
+ } catch (persistenceError) {
321
+ const persistenceEventId = captureLocalBoundaryFailure({
322
+ capture: deps.logException ?? logException,
254
323
  conversationId: input.conversationId,
255
- modelId: reply.diagnostics.modelId,
256
- messages: piMessagesBeforeRun ?? []
324
+ error: persistenceError,
325
+ failureCode: "persistence_failed",
326
+ turnId
257
327
  });
328
+ await lifecycle.fail({
329
+ conversationId: input.conversationId,
330
+ createdAtMs: now(),
331
+ ...persistenceEventId ? { eventId: persistenceEventId } : {},
332
+ failureCode: "persistence_failed",
333
+ turnId
334
+ });
335
+ throw new AggregateError(
336
+ [error, persistenceError],
337
+ "Local turn failure state could not be persisted"
338
+ );
258
339
  }
259
- markTurnFailed({
260
- conversation,
261
- nowMs: now(),
262
- sessionId: turnId,
263
- userMessageId,
264
- markConversationMessage,
265
- updateConversationStats
340
+ await lifecycle.fail({
341
+ conversationId: input.conversationId,
342
+ createdAtMs: now(),
343
+ ...failureEventId ? { eventId: failureEventId } : {},
344
+ failureCode,
345
+ turnId
266
346
  });
347
+ throw error;
348
+ }
349
+ try {
267
350
  await persistThreadStateById(input.conversationId, {
268
- artifacts: initialArtifacts,
269
- conversation,
270
- sandboxId: initialSandboxId ?? "",
271
- sandboxDependencyProfileHash: initialSandboxDependencyProfileHash ?? ""
351
+ artifacts: completedState.artifacts ?? artifacts,
352
+ conversation: completedState.conversation,
353
+ sandboxId: reply.sandboxId ?? sandboxId,
354
+ sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash ?? sandboxDependencyProfileHash
355
+ });
356
+ if (reply.piMessages?.length) {
357
+ await (deps.completeDeliveredTurn ?? completeDeliveredTurn)({
358
+ conversationId: input.conversationId,
359
+ sessionId: turnId,
360
+ sliceId: 1,
361
+ messages: reply.piMessages,
362
+ modelId: reply.diagnostics.modelId,
363
+ durationMs: reply.diagnostics.durationMs,
364
+ usage: reply.diagnostics.usage,
365
+ reasoningLevel: reply.diagnostics.reasoningLevel,
366
+ destination,
367
+ source,
368
+ actor: localActor,
369
+ surface: "internal",
370
+ logContext: {}
371
+ });
372
+ }
373
+ } catch (error) {
374
+ const persistenceEventId = captureLocalBoundaryFailure({
375
+ capture: deps.logException ?? logException,
376
+ conversationId: input.conversationId,
377
+ error,
378
+ failureCode: "persistence_failed",
379
+ turnId
380
+ });
381
+ await lifecycle.fail({
382
+ conversationId: input.conversationId,
383
+ createdAtMs: now(),
384
+ ...persistenceEventId ? { eventId: persistenceEventId } : {},
385
+ failureCode: "persistence_failed",
386
+ turnId
272
387
  });
273
388
  throw error;
274
389
  }
275
- await persistDeliveredLocalTurnState(input.conversationId, {
276
- artifacts: completedState.artifacts ?? artifacts,
277
- conversation: completedState.conversation,
278
- sandboxId: reply.sandboxId ?? sandboxId,
279
- sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash ?? sandboxDependencyProfileHash
280
- });
281
- if (reply.piMessages?.length) {
282
- await completeDeliveredTurn({
390
+ if (reply.diagnostics.outcome === "success") {
391
+ await lifecycle.complete({
283
392
  conversationId: input.conversationId,
284
- sessionId: turnId,
285
- sliceId: 1,
286
- messages: reply.piMessages,
287
- modelId: reply.diagnostics.modelId,
288
- durationMs: reply.diagnostics.durationMs,
289
- usage: reply.diagnostics.usage,
290
- reasoningLevel: reply.diagnostics.reasoningLevel,
291
- destination,
292
- source,
293
- actor: localActor,
294
- surface: "internal",
295
- logContext: {}
393
+ createdAtMs: now(),
394
+ outcome: assistantMessageDelivered ? "success" : "no_reply",
395
+ turnId
396
+ });
397
+ } else {
398
+ await lifecycle.fail({
399
+ conversationId: input.conversationId,
400
+ createdAtMs: now(),
401
+ ...modelFailureEventId ? { eventId: modelFailureEventId } : {},
402
+ failureCode: "model_execution_failed",
403
+ turnId
296
404
  });
297
405
  }
298
406
  if (reply.diagnostics.outcome === "success") {
@@ -6,9 +6,10 @@ import {
6
6
  parseSkillInvocation,
7
7
  resetSkillDiscoveryCache,
8
8
  stripFrontmatter
9
- } from "./chunk-WR7H7VLJ.js";
10
- import "./chunk-77LHNQK4.js";
11
- import "./chunk-AEWZ7F65.js";
9
+ } from "./chunk-YT72TOEK.js";
10
+ import "./chunk-YIPD3L6L.js";
11
+ import "./chunk-VIXFG2VC.js";
12
+ import "./chunk-O24R4DCK.js";
12
13
  import "./chunk-OB42YVAE.js";
13
14
  import "./chunk-6GWA276C.js";
14
15
  import "./chunk-B3IT5END.js";
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  validatePluginEgressCredentialHooks,
3
3
  validatePluginRegistrations
4
- } from "./chunk-MJSJAHBW.js";
5
- import "./chunk-77LHNQK4.js";
6
- import "./chunk-AEWZ7F65.js";
4
+ } from "./chunk-DY7TRYB5.js";
5
+ import "./chunk-YIPD3L6L.js";
6
+ import "./chunk-VIXFG2VC.js";
7
+ import "./chunk-O24R4DCK.js";
7
8
  import "./chunk-OB42YVAE.js";
8
9
  import "./chunk-6GWA276C.js";
9
10
  import "./chunk-B3IT5END.js";
@@ -0,0 +1,122 @@
1
+ ALTER TABLE "junior_agent_steps" RENAME TO "junior_conversation_events";--> statement-breakpoint
2
+ DO $$
3
+ BEGIN
4
+ IF EXISTS (
5
+ SELECT 1
6
+ FROM pg_constraint
7
+ WHERE conrelid = 'junior_conversation_events'::regclass
8
+ AND conname = 'junior_agent_steps_conversation_id_seq_pk'
9
+ ) THEN
10
+ ALTER TABLE "junior_conversation_events"
11
+ RENAME CONSTRAINT "junior_agent_steps_conversation_id_seq_pk"
12
+ TO "junior_conversation_events_conversation_id_seq_pk";
13
+ ELSIF EXISTS (
14
+ SELECT 1
15
+ FROM pg_constraint
16
+ WHERE conrelid = 'junior_conversation_events'::regclass
17
+ AND conname = 'junior_agent_steps_pkey'
18
+ ) THEN
19
+ ALTER TABLE "junior_conversation_events"
20
+ RENAME CONSTRAINT "junior_agent_steps_pkey"
21
+ TO "junior_conversation_events_conversation_id_seq_pk";
22
+ ELSE
23
+ RAISE EXCEPTION 'junior_agent_steps primary key constraint is missing';
24
+ END IF;
25
+ END
26
+ $$;--> statement-breakpoint
27
+ DO $$
28
+ BEGIN
29
+ IF EXISTS (
30
+ SELECT 1
31
+ FROM pg_constraint
32
+ WHERE conrelid = 'junior_conversation_events'::regclass
33
+ AND conname = 'junior_agent_steps_conversation_id_junior_conversations_conversation_id_fk'
34
+ ) THEN
35
+ ALTER TABLE "junior_conversation_events"
36
+ RENAME CONSTRAINT "junior_agent_steps_conversation_id_junior_conversations_conversation_id_fk"
37
+ TO "junior_conversation_events_conversation_id_junior_conversations_conversation_id_fk";
38
+ ELSIF EXISTS (
39
+ SELECT 1
40
+ FROM pg_constraint
41
+ WHERE conrelid = 'junior_conversation_events'::regclass
42
+ AND conname = 'junior_agent_steps_conversation_id_fkey'
43
+ ) THEN
44
+ ALTER TABLE "junior_conversation_events"
45
+ RENAME CONSTRAINT "junior_agent_steps_conversation_id_fkey"
46
+ TO "junior_conversation_events_conversation_id_junior_conversations_conversation_id_fk";
47
+ ELSE
48
+ RAISE EXCEPTION 'junior_agent_steps conversation foreign key constraint is missing';
49
+ END IF;
50
+ END
51
+ $$;--> statement-breakpoint
52
+ ALTER INDEX "junior_agent_steps_epoch_idx" RENAME TO "junior_conversation_events_history_version_idx";--> statement-breakpoint
53
+ ALTER TABLE "junior_conversation_events" RENAME COLUMN "context_epoch" TO "history_version";--> statement-breakpoint
54
+ ALTER TABLE "junior_conversation_events" ADD COLUMN "schema_version" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
55
+ ALTER TABLE "junior_conversation_events" DROP COLUMN "role";--> statement-breakpoint
56
+ ALTER TABLE "junior_conversation_events" ADD COLUMN "idempotency_key" text;--> statement-breakpoint
57
+ CREATE UNIQUE INDEX "junior_conversation_events_idempotency_idx" ON "junior_conversation_events" USING btree ("conversation_id","idempotency_key");--> statement-breakpoint
58
+ CREATE INDEX "junior_conversation_events_type_idx" ON "junior_conversation_events" USING btree ("conversation_id","type","seq");--> statement-breakpoint
59
+ CREATE INDEX "junior_conversation_events_message_search_idx" ON "junior_conversation_events" USING gin (to_tsvector('english', "payload"->>'text')) WHERE "type" = 'message';--> statement-breakpoint
60
+ UPDATE "junior_conversation_events"
61
+ SET
62
+ "schema_version" = 1,
63
+ "type" = 'agent_step',
64
+ "payload" = CASE
65
+ WHEN jsonb_typeof("payload") = 'object'
66
+ THEN "payload" - 'schemaVersion'
67
+ ELSE "payload"
68
+ END
69
+ WHERE "type" = 'pi_message';--> statement-breakpoint
70
+ UPDATE "junior_conversation_events"
71
+ SET "payload" = "payload" - 'historyMode'
72
+ WHERE "type" = 'subagent_started'
73
+ AND jsonb_typeof("payload") = 'object'
74
+ AND "payload" ? 'historyMode';--> statement-breakpoint
75
+ UPDATE "junior_conversation_events"
76
+ SET "payload" = "payload" - 'args'
77
+ WHERE "type" = 'tool_execution_started'
78
+ AND jsonb_typeof("payload") = 'object'
79
+ AND "payload" ? 'args';--> statement-breakpoint
80
+ UPDATE "junior_conversation_events"
81
+ SET "type" = 'messages_summarized'
82
+ WHERE "type" = 'visible_context_compacted';--> statement-breakpoint
83
+ UPDATE "junior_conversation_events" AS snapshot
84
+ SET "payload" =
85
+ (snapshot."payload" - 'compactions') ||
86
+ jsonb_build_object(
87
+ 'historyFromSeq',
88
+ coalesce((
89
+ SELECT max(recorded."seq") + 1
90
+ FROM "junior_conversation_events" AS recorded
91
+ WHERE recorded."conversation_id" = snapshot."conversation_id"
92
+ AND recorded."seq" < snapshot."seq"
93
+ AND recorded."type" = 'message'
94
+ AND EXISTS (
95
+ SELECT 1
96
+ FROM jsonb_array_elements(snapshot."payload"->'compactions') AS compaction,
97
+ jsonb_array_elements_text(compaction->'coveredMessageIds') AS covered_id(value)
98
+ WHERE covered_id.value = recorded."payload"->>'messageId'
99
+ )
100
+ ), 0),
101
+ 'compactions',
102
+ coalesce((
103
+ SELECT jsonb_agg(
104
+ (CASE
105
+ WHEN EXISTS (
106
+ SELECT 1
107
+ FROM "junior_conversation_events" AS recorded
108
+ WHERE recorded."conversation_id" = snapshot."conversation_id"
109
+ AND recorded."type" = 'message'
110
+ )
111
+ THEN compaction - 'coveredMessageIds'
112
+ ELSE compaction
113
+ END) ||
114
+ jsonb_build_object(
115
+ 'coveredMessageCount',
116
+ jsonb_array_length(compaction->'coveredMessageIds')
117
+ )
118
+ )
119
+ FROM jsonb_array_elements(snapshot."payload"->'compactions') AS compaction
120
+ ), '[]'::jsonb)
121
+ )
122
+ WHERE snapshot."type" = 'messages_summarized';--> statement-breakpoint
@@ -1,14 +1,27 @@
1
- # SQL migrations for `@sentry/junior`
1
+ # SQL migrations
2
2
 
3
- This is a standard Drizzle migration folder. `drizzle-kit generate` owns each
4
- SQL file, snapshot, and journal entry; `junior upgrade` applies the folder with
5
- Drizzle ORM's migrator before any data backfills run.
3
+ `src/db/schema.ts` is the Drizzle schema entrypoint. This directory is the
4
+ append-only history used to bring an existing database to that schema.
6
5
 
7
- 1. Edit the schema under `src/db/schema/`.
6
+ `junior upgrade` runs the ordered migration list in `src/cli/upgrade.ts`. Core
7
+ SQL runs under a migration lock and is recorded in
8
+ `drizzle.__drizzle_junior_core`, so reruns do not reapply it. Register backfills
9
+ after the schema migration they require.
10
+
11
+ ## Add a migration
12
+
13
+ 1. Change the owning table definition under `src/db/schema/`.
8
14
  2. Run `pnpm --filter @sentry/junior db:generate --name <migration_name>`.
9
- 3. Commit the generated SQL file and `meta/` changes together.
15
+ 3. Review the generated SQL and commit it with `meta/_journal.json` and its
16
+ snapshot.
17
+
18
+ - Prefer SQL for schema changes and deterministic transformations of rows in
19
+ the same database.
20
+ - Use an application data migration only for external data or work that must be
21
+ decoded in application code; keep it bounded and rerunnable.
22
+ - Never edit, rename, reorder, or delete an applied SQL migration or its
23
+ metadata. Add a new migration to correct it.
10
24
 
11
- The `0000_initial.sql` baseline represents the schema already deployed by the
12
- pre-Drizzle Junior migration runner. During upgrade, existing installations
13
- adopt that baseline once; new installations execute it normally. All later
14
- migrations are applied by Drizzle in journal order.
25
+ Migration loading, locking, and legacy baseline adoption live in
26
+ `src/chat/conversations/sql/migrations.ts`. Their integration coverage lives in
27
+ `tests/integration/conversation-sql.test.ts`.