@sentry/junior 0.71.3 → 0.72.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 (56) hide show
  1. package/bin/junior.mjs +10 -0
  2. package/dist/api-reference.d.ts +2 -0
  3. package/dist/app.d.ts +5 -5
  4. package/dist/app.js +1039 -1971
  5. package/dist/chat/agent-dispatch/heartbeat.d.ts +0 -6
  6. package/dist/chat/mcp/errors.d.ts +3 -0
  7. package/dist/chat/mcp/tool-manager.d.ts +5 -1
  8. package/dist/chat/plugins/agent-hooks.d.ts +3 -2
  9. package/dist/chat/requester.d.ts +60 -0
  10. package/dist/chat/respond.d.ts +2 -6
  11. package/dist/chat/runtime/agent-continue-runner.d.ts +25 -0
  12. package/dist/chat/runtime/reply-executor.d.ts +4 -4
  13. package/dist/chat/runtime/turn.d.ts +2 -2
  14. package/dist/chat/services/agent-continue.d.ts +27 -0
  15. package/dist/chat/services/message-actor-identity.d.ts +12 -4
  16. package/dist/chat/services/turn-session-record.d.ts +10 -7
  17. package/dist/chat/slack/user.d.ts +4 -4
  18. package/dist/chat/state/adapter.d.ts +2 -0
  19. package/dist/chat/state/conversation-details.d.ts +4 -3
  20. package/dist/chat/state/session-log.d.ts +43 -0
  21. package/dist/chat/state/turn-session.d.ts +7 -10
  22. package/dist/chat/task-execution/slack-work.d.ts +5 -5
  23. package/dist/chat/task-execution/store.d.ts +83 -48
  24. package/dist/chat/task-execution/worker.d.ts +3 -3
  25. package/dist/chat/tools/definition.d.ts +3 -0
  26. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -1
  27. package/dist/chat/tools/types.d.ts +2 -5
  28. package/dist/{chunk-R62YWUNO.js → chunk-3FYPXHPL.js} +10 -28
  29. package/dist/chunk-4JXCSGSA.js +212 -0
  30. package/dist/{chunk-GT67ZWZQ.js → chunk-55XEZFGD.js} +5 -3
  31. package/dist/{chunk-BBXYXOJW.js → chunk-6GEYPE6T.js} +18 -523
  32. package/dist/chunk-G3E7SCME.js +28 -0
  33. package/dist/{chunk-UXG6TU2U.js → chunk-GB3AL54K.js} +8 -93
  34. package/dist/chunk-HNMUVGSR.js +1119 -0
  35. package/dist/{chunk-XE2VFQQN.js → chunk-ICKIDP7G.js} +1 -1
  36. package/dist/chunk-KVZL5NZS.js +519 -0
  37. package/dist/chunk-PP7AGSBU.js +185 -0
  38. package/dist/{chunk-B5HKWWQB.js → chunk-VLIO6RQR.js} +8 -6
  39. package/dist/{chunk-HOGQL2H6.js → chunk-VSNA5KAB.js} +177 -101
  40. package/dist/{chunk-76YMBKW7.js → chunk-XC33FJZN.js} +4 -12
  41. package/dist/{chunk-JS4HURDT.js → chunk-ZJQPA67D.js} +25 -25
  42. package/dist/cli/check.js +10 -8
  43. package/dist/cli/run.js +9 -1
  44. package/dist/cli/snapshot-warmup.js +10 -7
  45. package/dist/cli/upgrade.js +599 -0
  46. package/dist/nitro.d.ts +1 -1
  47. package/dist/nitro.js +5 -4
  48. package/dist/plugins.d.ts +1 -1
  49. package/dist/reporting/conversations.d.ts +116 -0
  50. package/dist/reporting.d.ts +24 -129
  51. package/dist/reporting.js +310 -158
  52. package/package.json +3 -3
  53. package/dist/chat/runtime/timeout-resume-runner.d.ts +0 -19
  54. package/dist/chat/services/requester-identity.d.ts +0 -19
  55. package/dist/chat/services/timeout-resume.d.ts +0 -23
  56. package/dist/handlers/turn-resume.d.ts +0 -4
package/dist/cli/run.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import "../chunk-2KG3PWR4.js";
2
2
 
3
3
  // src/cli/run.ts
4
- var CLI_USAGE = "usage: junior init <dir>\n junior snapshot create\n junior check [dir]";
4
+ var CLI_USAGE = "usage: junior init <dir>\n junior snapshot create\n junior check [dir]\n junior upgrade";
5
5
  var DEFAULT_IO = {
6
6
  error: console.error
7
7
  };
@@ -31,6 +31,14 @@ async function runCli(argv, handlers, io = DEFAULT_IO) {
31
31
  await handlers.runCheck(subcommand);
32
32
  return 0;
33
33
  }
34
+ if (command === "upgrade") {
35
+ if (subcommand || rest.length > 0) {
36
+ io.error(CLI_USAGE);
37
+ return 1;
38
+ }
39
+ await handlers.runUpgrade();
40
+ return 0;
41
+ }
34
42
  io.error(CLI_USAGE);
35
43
  return 1;
36
44
  }
@@ -1,16 +1,19 @@
1
1
  import {
2
2
  resolveRuntimeDependencySnapshot
3
- } from "../chunk-B5HKWWQB.js";
4
- import {
5
- disconnectStateAdapter
6
- } from "../chunk-R62YWUNO.js";
3
+ } from "../chunk-VLIO6RQR.js";
4
+ import "../chunk-G3E7SCME.js";
7
5
  import {
8
6
  getPluginProviders,
9
7
  getPluginRuntimeDependencies,
10
8
  getPluginRuntimePostinstall
11
- } from "../chunk-UXG6TU2U.js";
12
- import "../chunk-JS4HURDT.js";
13
- import "../chunk-BBXYXOJW.js";
9
+ } from "../chunk-GB3AL54K.js";
10
+ import "../chunk-KVZL5NZS.js";
11
+ import {
12
+ disconnectStateAdapter
13
+ } from "../chunk-3FYPXHPL.js";
14
+ import "../chunk-ZJQPA67D.js";
15
+ import "../chunk-PP7AGSBU.js";
16
+ import "../chunk-6GEYPE6T.js";
14
17
  import "../chunk-Z3YD6NHK.js";
15
18
  import "../chunk-2KG3PWR4.js";
16
19
 
@@ -0,0 +1,599 @@
1
+ import {
2
+ coerceThreadConversationState
3
+ } from "../chunk-4JXCSGSA.js";
4
+ import {
5
+ JUNIOR_THREAD_STATE_TTL_MS,
6
+ getConversation,
7
+ requestConversationWork
8
+ } from "../chunk-HNMUVGSR.js";
9
+ import {
10
+ parseDestination,
11
+ sameDestination
12
+ } from "../chunk-XC33FJZN.js";
13
+ import {
14
+ disconnectStateAdapter,
15
+ getConnectedStateContext
16
+ } from "../chunk-3FYPXHPL.js";
17
+ import {
18
+ getChatConfig
19
+ } from "../chunk-ZJQPA67D.js";
20
+ import "../chunk-PP7AGSBU.js";
21
+ import {
22
+ isRecord,
23
+ toOptionalNumber,
24
+ toOptionalString
25
+ } from "../chunk-6GEYPE6T.js";
26
+ import "../chunk-Z3YD6NHK.js";
27
+ import "../chunk-2KG3PWR4.js";
28
+
29
+ // src/cli/upgrade/migrations/redis-conversation-state.ts
30
+ var CONVERSATION_PREFIX = "junior:conversation";
31
+ var CONVERSATION_SCHEMA_VERSION = 1;
32
+ var CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH = 1e4;
33
+ var CONVERSATION_BY_ACTIVITY_INDEX_KEY = `${CONVERSATION_PREFIX}:by-activity`;
34
+ var CONVERSATION_ACTIVE_INDEX_KEY = `${CONVERSATION_PREFIX}:active`;
35
+ var LEGACY_CONVERSATION_WORK_PREFIX = "junior:conversation-work";
36
+ var LEGACY_CONVERSATION_WORK_SCHEMA_VERSION = 1;
37
+ var LEGACY_CONVERSATION_WORK_INDEX_KEY = `${LEGACY_CONVERSATION_WORK_PREFIX}:index`;
38
+ var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
39
+ var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
40
+ var THREAD_STATE_PREFIX = "thread-state";
41
+ function conversationKey(conversationId) {
42
+ return `${CONVERSATION_PREFIX}:${conversationId}`;
43
+ }
44
+ function legacyConversationWorkKey(conversationId) {
45
+ return `${LEGACY_CONVERSATION_WORK_PREFIX}:state:${conversationId}`;
46
+ }
47
+ function threadStateKey(conversationId) {
48
+ return `${THREAD_STATE_PREFIX}:${conversationId}`;
49
+ }
50
+ function uniqueStrings(values) {
51
+ return [...new Set(values)];
52
+ }
53
+ function uniqueStringValues(value) {
54
+ if (!Array.isArray(value)) {
55
+ return [];
56
+ }
57
+ return uniqueStrings(
58
+ value.map((value2) => typeof value2 === "string" ? value2 : void 0).filter((value2) => Boolean(value2))
59
+ );
60
+ }
61
+ function normalizeSource(value) {
62
+ if (value === "api" || value === "internal" || value === "plugin" || value === "scheduler" || value === "slack") {
63
+ return value;
64
+ }
65
+ return void 0;
66
+ }
67
+ function normalizeMetadata(value) {
68
+ if (!isRecord(value)) {
69
+ return void 0;
70
+ }
71
+ return value;
72
+ }
73
+ function normalizeInput(value) {
74
+ if (!isRecord(value)) {
75
+ return void 0;
76
+ }
77
+ const text = toOptionalString(value.text);
78
+ if (!text) {
79
+ return void 0;
80
+ }
81
+ return {
82
+ text,
83
+ authorId: toOptionalString(value.authorId),
84
+ attachments: Array.isArray(value.attachments) ? [...value.attachments] : void 0,
85
+ metadata: normalizeMetadata(value.metadata)
86
+ };
87
+ }
88
+ function normalizeMessage(value) {
89
+ if (!isRecord(value)) {
90
+ return void 0;
91
+ }
92
+ const conversationId = toOptionalString(value.conversationId);
93
+ const inboundMessageId = toOptionalString(value.inboundMessageId);
94
+ const source = normalizeSource(value.source);
95
+ const destination = parseDestination(value.destination);
96
+ const createdAtMs = toOptionalNumber(value.createdAtMs);
97
+ const receivedAtMs = toOptionalNumber(value.receivedAtMs);
98
+ const input = normalizeInput(value.input);
99
+ if (!conversationId || !destination || !inboundMessageId || !source || typeof createdAtMs !== "number" || typeof receivedAtMs !== "number" || !input) {
100
+ return void 0;
101
+ }
102
+ return {
103
+ conversationId,
104
+ destination,
105
+ inboundMessageId,
106
+ source,
107
+ createdAtMs,
108
+ receivedAtMs,
109
+ input,
110
+ injectedAtMs: toOptionalNumber(value.injectedAtMs)
111
+ };
112
+ }
113
+ function normalizeLegacyLease(value) {
114
+ if (!isRecord(value)) {
115
+ return void 0;
116
+ }
117
+ const token = toOptionalString(value.leaseToken);
118
+ const acquiredAtMs = toOptionalNumber(value.acquiredAtMs);
119
+ const lastCheckInAtMs = toOptionalNumber(value.lastCheckInAtMs);
120
+ const expiresAtMs = toOptionalNumber(value.leaseExpiresAtMs);
121
+ if (!token || typeof acquiredAtMs !== "number" || typeof lastCheckInAtMs !== "number" || typeof expiresAtMs !== "number") {
122
+ return void 0;
123
+ }
124
+ return {
125
+ token,
126
+ acquiredAtMs,
127
+ lastCheckInAtMs,
128
+ expiresAtMs
129
+ };
130
+ }
131
+ function compareMessages(left, right) {
132
+ return left.createdAtMs - right.createdAtMs || left.receivedAtMs - right.receivedAtMs || left.inboundMessageId.localeCompare(right.inboundMessageId);
133
+ }
134
+ function normalizeLegacyConversation(conversationId, value) {
135
+ if (!isRecord(value) || value.schemaVersion !== LEGACY_CONVERSATION_WORK_SCHEMA_VERSION) {
136
+ return void 0;
137
+ }
138
+ const storedConversationId = toOptionalString(value.conversationId);
139
+ const destination = parseDestination(value.destination);
140
+ const updatedAtMs = toOptionalNumber(value.updatedAtMs);
141
+ if (storedConversationId !== conversationId || !destination || typeof updatedAtMs !== "number") {
142
+ return void 0;
143
+ }
144
+ const normalizedMessages = Array.isArray(value.messages) ? value.messages.map(normalizeMessage).filter((message) => Boolean(message)) : [];
145
+ if (normalizedMessages.some(
146
+ (message) => message.conversationId === conversationId && !sameDestination(message.destination, destination)
147
+ )) {
148
+ return void 0;
149
+ }
150
+ const messages = normalizedMessages.filter((message) => message.conversationId === conversationId).sort(compareMessages);
151
+ const pendingMessages = messages.filter(
152
+ (message) => message.injectedAtMs === void 0
153
+ );
154
+ const lease = normalizeLegacyLease(value.lease);
155
+ const needsRun = value.needsRun === true || pendingMessages.length > 0;
156
+ const status = lease ? value.needsRun === true ? "awaiting_resume" : "running" : needsRun ? "pending" : "idle";
157
+ const messageTimes = messages.flatMap((message) => [
158
+ message.createdAtMs,
159
+ message.receivedAtMs
160
+ ]);
161
+ const createdAtMs = messageTimes.length > 0 ? Math.min(...messageTimes) : updatedAtMs;
162
+ const lastActivityAtMs = messageTimes.length > 0 ? Math.max(...messageTimes) : updatedAtMs;
163
+ return {
164
+ schemaVersion: CONVERSATION_SCHEMA_VERSION,
165
+ conversationId,
166
+ createdAtMs,
167
+ destination,
168
+ lastActivityAtMs,
169
+ source: messages[0]?.source,
170
+ updatedAtMs,
171
+ execution: {
172
+ status,
173
+ inboundMessageIds: uniqueStrings(
174
+ messages.map((message) => message.inboundMessageId)
175
+ ),
176
+ pendingCount: pendingMessages.length,
177
+ pendingMessages,
178
+ ...lease ? { lease } : {},
179
+ lastEnqueuedAtMs: toOptionalNumber(value.lastEnqueuedAtMs),
180
+ updatedAtMs
181
+ }
182
+ };
183
+ }
184
+ function mergeLegacyConversation(existing, legacy) {
185
+ if (existing.destination && legacy.destination && !sameDestination(existing.destination, legacy.destination)) {
186
+ throw new Error(
187
+ `Legacy conversation work destination does not match conversation ${existing.conversationId}`
188
+ );
189
+ }
190
+ const knownInboundIds = new Set(existing.execution.inboundMessageIds);
191
+ const pendingMessages = [
192
+ ...existing.execution.pendingMessages,
193
+ ...legacy.execution.pendingMessages.filter(
194
+ (message) => !knownInboundIds.has(message.inboundMessageId)
195
+ )
196
+ ].sort(compareMessages);
197
+ const legacyIsRunnable = legacy.execution.status !== "idle";
198
+ const existingIsIdle = existing.execution.status === "idle";
199
+ const legacyLease = existingIsIdle && legacy.execution.lease ? { lease: legacy.execution.lease } : {};
200
+ const legacyRunId = existingIsIdle && legacy.execution.runId ? { runId: legacy.execution.runId } : {};
201
+ const legacyCheckpoint = existing.execution.lastCheckpointAtMs === void 0 && legacy.execution.lastCheckpointAtMs !== void 0 ? { lastCheckpointAtMs: legacy.execution.lastCheckpointAtMs } : {};
202
+ const legacyEnqueue = existing.execution.lastEnqueuedAtMs === void 0 && legacy.execution.lastEnqueuedAtMs !== void 0 ? { lastEnqueuedAtMs: legacy.execution.lastEnqueuedAtMs } : {};
203
+ const executionUpdatedAtMs = Math.max(
204
+ existing.execution.updatedAtMs ?? existing.updatedAtMs,
205
+ legacy.execution.updatedAtMs ?? legacy.updatedAtMs
206
+ );
207
+ const status = existingIsIdle && legacyIsRunnable ? legacy.execution.lease ? legacy.execution.status : "pending" : pendingMessages.length > 0 && existingIsIdle ? "pending" : existing.execution.status;
208
+ return {
209
+ ...existing,
210
+ destination: existing.destination ?? legacy.destination,
211
+ source: existing.source ?? legacy.source,
212
+ createdAtMs: Math.min(existing.createdAtMs, legacy.createdAtMs),
213
+ lastActivityAtMs: Math.max(
214
+ existing.lastActivityAtMs,
215
+ legacy.lastActivityAtMs
216
+ ),
217
+ updatedAtMs: Math.max(existing.updatedAtMs, legacy.updatedAtMs),
218
+ execution: {
219
+ ...existing.execution,
220
+ ...legacyLease,
221
+ ...legacyRunId,
222
+ ...legacyCheckpoint,
223
+ ...legacyEnqueue,
224
+ status,
225
+ inboundMessageIds: uniqueStrings([
226
+ ...existing.execution.inboundMessageIds,
227
+ ...legacy.execution.inboundMessageIds
228
+ ]),
229
+ pendingCount: pendingMessages.length,
230
+ pendingMessages,
231
+ updatedAtMs: executionUpdatedAtMs
232
+ }
233
+ };
234
+ }
235
+ function compareIndexDescending(left, right) {
236
+ return right.score - left.score || right.conversationId.localeCompare(left.conversationId);
237
+ }
238
+ function compareIndexAscending(left, right) {
239
+ return left.score - right.score || left.conversationId.localeCompare(right.conversationId);
240
+ }
241
+ function normalizeIndexEntry(value) {
242
+ if (!isRecord(value)) {
243
+ return void 0;
244
+ }
245
+ const conversationId = toOptionalString(value.conversationId);
246
+ const score = toOptionalNumber(value.score);
247
+ if (!conversationId || typeof score !== "number") {
248
+ return void 0;
249
+ }
250
+ return { conversationId, score };
251
+ }
252
+ function uniqueIndexEntries(value) {
253
+ if (!Array.isArray(value)) {
254
+ return [];
255
+ }
256
+ const entries = /* @__PURE__ */ new Map();
257
+ for (const item of value) {
258
+ const entry = normalizeIndexEntry(item);
259
+ if (!entry) {
260
+ continue;
261
+ }
262
+ const existing = entries.get(entry.conversationId);
263
+ if (!existing || entry.score > existing.score) {
264
+ entries.set(entry.conversationId, entry);
265
+ }
266
+ }
267
+ return [...entries.values()];
268
+ }
269
+ function normalizeAwaitingContinuationSummary(value) {
270
+ if (!isRecord(value)) {
271
+ return void 0;
272
+ }
273
+ const conversationId = toOptionalString(value.conversationId);
274
+ const sessionId = toOptionalString(value.sessionId);
275
+ const state = value.state;
276
+ const resumeReason = value.resumeReason;
277
+ const destination = parseDestination(value.destination);
278
+ const updatedAtMs = toOptionalNumber(value.updatedAtMs);
279
+ if (!conversationId || !sessionId || state !== "awaiting_resume" || resumeReason !== "timeout" && resumeReason !== "yield" || !destination || typeof updatedAtMs !== "number") {
280
+ return void 0;
281
+ }
282
+ return {
283
+ conversationId,
284
+ destination,
285
+ resumeReason,
286
+ sessionId,
287
+ state,
288
+ updatedAtMs
289
+ };
290
+ }
291
+ function uniqueAwaitingContinuationSummaries(values) {
292
+ const summaries = /* @__PURE__ */ new Map();
293
+ for (const value of [...values].reverse()) {
294
+ const summary = normalizeAwaitingContinuationSummary(value);
295
+ if (!summary) {
296
+ continue;
297
+ }
298
+ const key = `${summary.conversationId}:${summary.sessionId}`;
299
+ if (!summaries.has(key)) {
300
+ summaries.set(key, summary);
301
+ }
302
+ }
303
+ return [...summaries.values()];
304
+ }
305
+ function redisIndexKey(indexKey) {
306
+ const prefix = getChatConfig().state.keyPrefix;
307
+ return [...prefix ? [prefix] : [], indexKey].join(":");
308
+ }
309
+ async function upsertEmulatedIndexEntry(args) {
310
+ const existing = uniqueIndexEntries(
311
+ await args.stateAdapter.get(args.indexKey)
312
+ );
313
+ const next = [
314
+ ...existing.filter((entry) => entry.conversationId !== args.conversationId),
315
+ { conversationId: args.conversationId, score: args.score }
316
+ ];
317
+ const retained = args.indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY ? next.sort(compareIndexDescending).slice(0, CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH) : next.sort(compareIndexAscending);
318
+ await args.stateAdapter.set(
319
+ args.indexKey,
320
+ retained,
321
+ JUNIOR_THREAD_STATE_TTL_MS
322
+ );
323
+ }
324
+ async function removeEmulatedIndexEntry(args) {
325
+ const existing = uniqueIndexEntries(
326
+ await args.stateAdapter.get(args.indexKey)
327
+ );
328
+ const next = existing.filter(
329
+ (entry) => entry.conversationId !== args.conversationId
330
+ );
331
+ if (next.length === existing.length) {
332
+ return;
333
+ }
334
+ await args.stateAdapter.set(args.indexKey, next, JUNIOR_THREAD_STATE_TTL_MS);
335
+ }
336
+ async function upsertRedisIndexEntry(args) {
337
+ const key = redisIndexKey(args.indexKey);
338
+ if (args.indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY) {
339
+ const upsertBoundedActivityScript = `
340
+ redis.call("ZADD", KEYS[1], ARGV[1], ARGV[2])
341
+ redis.call("PEXPIRE", KEYS[1], ARGV[3])
342
+ local extra = redis.call("ZCARD", KEYS[1]) - tonumber(ARGV[4])
343
+ if extra > 0 then
344
+ redis.call("ZREMRANGEBYRANK", KEYS[1], 0, extra - 1)
345
+ end
346
+ return 1
347
+ `;
348
+ await args.client.sendCommand([
349
+ "EVAL",
350
+ upsertBoundedActivityScript,
351
+ "1",
352
+ key,
353
+ String(args.score),
354
+ args.conversationId,
355
+ String(JUNIOR_THREAD_STATE_TTL_MS),
356
+ String(CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH)
357
+ ]);
358
+ return;
359
+ }
360
+ await args.client.sendCommand([
361
+ "ZADD",
362
+ key,
363
+ String(args.score),
364
+ args.conversationId
365
+ ]);
366
+ await args.client.sendCommand([
367
+ "PEXPIRE",
368
+ key,
369
+ String(JUNIOR_THREAD_STATE_TTL_MS)
370
+ ]);
371
+ }
372
+ async function removeRedisIndexEntry(args) {
373
+ await args.client.sendCommand([
374
+ "ZREM",
375
+ redisIndexKey(args.indexKey),
376
+ args.conversationId
377
+ ]);
378
+ }
379
+ async function upsertConversationIndexes(args) {
380
+ const redisClient = args.redisStateAdapter?.getClient();
381
+ const upsert = redisClient ? (indexKey, score) => upsertRedisIndexEntry({
382
+ client: redisClient,
383
+ conversationId: args.conversation.conversationId,
384
+ indexKey,
385
+ score
386
+ }) : (indexKey, score) => upsertEmulatedIndexEntry({
387
+ stateAdapter: args.stateAdapter,
388
+ conversationId: args.conversation.conversationId,
389
+ indexKey,
390
+ score
391
+ });
392
+ const remove = redisClient ? (indexKey) => removeRedisIndexEntry({
393
+ client: redisClient,
394
+ conversationId: args.conversation.conversationId,
395
+ indexKey
396
+ }) : (indexKey) => removeEmulatedIndexEntry({
397
+ stateAdapter: args.stateAdapter,
398
+ conversationId: args.conversation.conversationId,
399
+ indexKey
400
+ });
401
+ await upsert(
402
+ CONVERSATION_BY_ACTIVITY_INDEX_KEY,
403
+ args.conversation.lastActivityAtMs
404
+ );
405
+ if (args.conversation.execution.status === "idle") {
406
+ await remove(CONVERSATION_ACTIVE_INDEX_KEY);
407
+ return;
408
+ }
409
+ await upsert(
410
+ CONVERSATION_ACTIVE_INDEX_KEY,
411
+ args.conversation.execution.updatedAtMs ?? args.conversation.updatedAtMs
412
+ );
413
+ }
414
+ async function removeLegacyIndexEntry(args) {
415
+ const existing = uniqueStringValues(
416
+ await args.stateAdapter.get(LEGACY_CONVERSATION_WORK_INDEX_KEY)
417
+ );
418
+ const next = existing.filter((id) => id !== args.conversationId);
419
+ if (next.length === existing.length) {
420
+ return;
421
+ }
422
+ if (next.length === 0) {
423
+ await args.stateAdapter.delete(LEGACY_CONVERSATION_WORK_INDEX_KEY);
424
+ return;
425
+ }
426
+ await args.stateAdapter.set(
427
+ LEGACY_CONVERSATION_WORK_INDEX_KEY,
428
+ next,
429
+ JUNIOR_THREAD_STATE_TTL_MS
430
+ );
431
+ }
432
+ async function migrateLegacyConversationWorkRedisState(context) {
433
+ const legacyIds = uniqueStringValues(
434
+ await context.stateAdapter.get(LEGACY_CONVERSATION_WORK_INDEX_KEY)
435
+ );
436
+ const result = {
437
+ existing: 0,
438
+ migrated: 0,
439
+ missing: 0,
440
+ scanned: legacyIds.length
441
+ };
442
+ for (const conversationId of legacyIds) {
443
+ const legacyKey = legacyConversationWorkKey(conversationId);
444
+ const raw = await context.stateAdapter.get(legacyKey);
445
+ if (raw == null) {
446
+ result.missing += 1;
447
+ await removeLegacyIndexEntry({
448
+ conversationId,
449
+ stateAdapter: context.stateAdapter
450
+ });
451
+ continue;
452
+ }
453
+ const conversation = normalizeLegacyConversation(conversationId, raw);
454
+ if (!conversation) {
455
+ throw new Error(
456
+ `Legacy conversation work state is invalid for ${conversationId}`
457
+ );
458
+ }
459
+ const existingConversation = await getConversation({
460
+ conversationId,
461
+ state: context.stateAdapter
462
+ });
463
+ if (existingConversation) {
464
+ const mergedConversation = mergeLegacyConversation(
465
+ existingConversation,
466
+ conversation
467
+ );
468
+ await context.stateAdapter.set(
469
+ conversationKey(conversationId),
470
+ mergedConversation,
471
+ JUNIOR_THREAD_STATE_TTL_MS
472
+ );
473
+ await upsertConversationIndexes({
474
+ conversation: mergedConversation,
475
+ redisStateAdapter: context.redisStateAdapter,
476
+ stateAdapter: context.stateAdapter
477
+ });
478
+ result.existing += 1;
479
+ await context.stateAdapter.delete(legacyKey);
480
+ await removeLegacyIndexEntry({
481
+ conversationId,
482
+ stateAdapter: context.stateAdapter
483
+ });
484
+ continue;
485
+ }
486
+ await context.stateAdapter.set(
487
+ conversationKey(conversationId),
488
+ conversation,
489
+ JUNIOR_THREAD_STATE_TTL_MS
490
+ );
491
+ await upsertConversationIndexes({
492
+ conversation,
493
+ redisStateAdapter: context.redisStateAdapter,
494
+ stateAdapter: context.stateAdapter
495
+ });
496
+ await context.stateAdapter.delete(legacyKey);
497
+ await removeLegacyIndexEntry({
498
+ conversationId,
499
+ stateAdapter: context.stateAdapter
500
+ });
501
+ result.migrated += 1;
502
+ }
503
+ return result;
504
+ }
505
+ async function isActiveContinuationSummary(context, summary) {
506
+ const rawState = await context.stateAdapter.get(
507
+ threadStateKey(summary.conversationId)
508
+ ) ?? {};
509
+ const conversation = coerceThreadConversationState(rawState);
510
+ return conversation.processing.activeTurnId === summary.sessionId;
511
+ }
512
+ async function seedAwaitingContinuationConversationWork(context, result) {
513
+ const summaries = uniqueAwaitingContinuationSummaries(
514
+ await context.stateAdapter.getList(AGENT_TURN_SESSION_INDEX_KEY)
515
+ );
516
+ result.scanned += summaries.length;
517
+ for (const summary of summaries) {
518
+ if (!await isActiveContinuationSummary(context, summary)) {
519
+ continue;
520
+ }
521
+ const existingConversation = await getConversation({
522
+ conversationId: summary.conversationId,
523
+ state: context.stateAdapter
524
+ });
525
+ if (existingConversation?.destination && !sameDestination(existingConversation.destination, summary.destination)) {
526
+ throw new Error(
527
+ `Awaiting continuation destination does not match conversation ${summary.conversationId}`
528
+ );
529
+ }
530
+ if (existingConversation && existingConversation.execution.status !== "idle") {
531
+ continue;
532
+ }
533
+ await requestConversationWork({
534
+ conversationId: summary.conversationId,
535
+ destination: summary.destination,
536
+ nowMs: Math.max(
537
+ summary.updatedAtMs,
538
+ existingConversation?.updatedAtMs ?? 0
539
+ ),
540
+ state: context.stateAdapter
541
+ });
542
+ if (existingConversation) {
543
+ result.existing += 1;
544
+ } else {
545
+ result.migrated += 1;
546
+ }
547
+ }
548
+ }
549
+ async function migrateRedisConversationState(context) {
550
+ const result = await migrateLegacyConversationWorkRedisState(context);
551
+ await seedAwaitingContinuationConversationWork(context, result);
552
+ return result;
553
+ }
554
+ var redisConversationStateMigration = {
555
+ // TODO(after 2026-07-01): remove after deployed installs have had a release
556
+ // window to move legacy conversation-work Redis state forward.
557
+ name: "migrate-redis-conversation-state",
558
+ run: migrateRedisConversationState
559
+ };
560
+
561
+ // src/cli/upgrade.ts
562
+ var DEFAULT_IO = {
563
+ info: console.log
564
+ };
565
+ var MIGRATIONS = [redisConversationStateMigration];
566
+ function formatMigrationResult(result) {
567
+ return [
568
+ `scanned=${result.scanned}`,
569
+ `migrated=${result.migrated}`,
570
+ `existing=${result.existing}`,
571
+ `missing=${result.missing}`
572
+ ].join(" ");
573
+ }
574
+ async function runUpgradeMigrations(context) {
575
+ const results = [];
576
+ for (const migration of MIGRATIONS) {
577
+ context.io.info(`Running migration ${migration.name}...`);
578
+ const result = await migration.run(context);
579
+ context.io.info(
580
+ `Finished migration ${migration.name}: ${formatMigrationResult(result)}`
581
+ );
582
+ results.push(result);
583
+ }
584
+ return results;
585
+ }
586
+ async function runUpgrade(io = DEFAULT_IO) {
587
+ try {
588
+ const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
589
+ io.info("Running Junior upgrade migrations...");
590
+ await runUpgradeMigrations({ io, redisStateAdapter, stateAdapter });
591
+ io.info("Junior upgrade complete.");
592
+ } finally {
593
+ await disconnectStateAdapter();
594
+ }
595
+ }
596
+ export {
597
+ runUpgrade,
598
+ runUpgradeMigrations
599
+ };
package/dist/nitro.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type JuniorPluginSet } from "@/plugins";
1
+ import { type JuniorPluginSet } from "./plugins";
2
2
  export interface JuniorPluginModuleReference {
3
3
  /** Runtime-safe module that exports a `defineJuniorPlugins(...)` set. */
4
4
  module: string;
package/dist/nitro.js CHANGED
@@ -2,19 +2,20 @@ import {
2
2
  pluginCatalogConfigFromPluginSet,
3
3
  pluginHookRegistrationsFromPluginSet,
4
4
  resolveConversationWorkQueueTopic
5
- } from "./chunk-XE2VFQQN.js";
6
- import "./chunk-76YMBKW7.js";
5
+ } from "./chunk-ICKIDP7G.js";
7
6
  import {
8
7
  JUNIOR_CONVERSATION_WORK_CALLBACK_ROUTE,
9
8
  JUNIOR_HEARTBEAT_CRON_SCHEDULE,
10
9
  JUNIOR_HEARTBEAT_ROUTE
11
10
  } from "./chunk-6YY4Q3D4.js";
12
- import "./chunk-JS4HURDT.js";
11
+ import "./chunk-XC33FJZN.js";
13
12
  import {
14
13
  discoverInstalledPluginPackageContent,
15
14
  isValidPackageName,
16
15
  resolvePackageDir
17
- } from "./chunk-BBXYXOJW.js";
16
+ } from "./chunk-KVZL5NZS.js";
17
+ import "./chunk-ZJQPA67D.js";
18
+ import "./chunk-6GEYPE6T.js";
18
19
  import "./chunk-Z3YD6NHK.js";
19
20
  import "./chunk-2KG3PWR4.js";
20
21
 
package/dist/plugins.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { JuniorPluginRegistration } from "@sentry/junior-plugin-api";
2
- import type { PluginCatalogConfig, PluginManifestConfig } from "@/chat/plugins/types";
2
+ import type { PluginCatalogConfig, PluginManifestConfig } from "./chat/plugins/types";
3
3
  export type JuniorPluginInput = JuniorPluginRegistration | string;
4
4
  export interface JuniorPluginSetOptions {
5
5
  /** Install-level manifest overrides applied before validation. */