@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.
- package/bin/junior.mjs +10 -0
- package/dist/api-reference.d.ts +2 -0
- package/dist/app.d.ts +5 -5
- package/dist/app.js +1039 -1971
- package/dist/chat/agent-dispatch/heartbeat.d.ts +0 -6
- package/dist/chat/mcp/errors.d.ts +3 -0
- package/dist/chat/mcp/tool-manager.d.ts +5 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -2
- package/dist/chat/requester.d.ts +60 -0
- package/dist/chat/respond.d.ts +2 -6
- package/dist/chat/runtime/agent-continue-runner.d.ts +25 -0
- package/dist/chat/runtime/reply-executor.d.ts +4 -4
- package/dist/chat/runtime/turn.d.ts +2 -2
- package/dist/chat/services/agent-continue.d.ts +27 -0
- package/dist/chat/services/message-actor-identity.d.ts +12 -4
- package/dist/chat/services/turn-session-record.d.ts +10 -7
- package/dist/chat/slack/user.d.ts +4 -4
- package/dist/chat/state/adapter.d.ts +2 -0
- package/dist/chat/state/conversation-details.d.ts +4 -3
- package/dist/chat/state/session-log.d.ts +43 -0
- package/dist/chat/state/turn-session.d.ts +7 -10
- package/dist/chat/task-execution/slack-work.d.ts +5 -5
- package/dist/chat/task-execution/store.d.ts +83 -48
- package/dist/chat/task-execution/worker.d.ts +3 -3
- package/dist/chat/tools/definition.d.ts +3 -0
- package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -1
- package/dist/chat/tools/types.d.ts +2 -5
- package/dist/{chunk-R62YWUNO.js → chunk-3FYPXHPL.js} +10 -28
- package/dist/chunk-4JXCSGSA.js +212 -0
- package/dist/{chunk-GT67ZWZQ.js → chunk-55XEZFGD.js} +5 -3
- package/dist/{chunk-BBXYXOJW.js → chunk-6GEYPE6T.js} +18 -523
- package/dist/chunk-G3E7SCME.js +28 -0
- package/dist/{chunk-UXG6TU2U.js → chunk-GB3AL54K.js} +8 -93
- package/dist/chunk-HNMUVGSR.js +1119 -0
- package/dist/{chunk-XE2VFQQN.js → chunk-ICKIDP7G.js} +1 -1
- package/dist/chunk-KVZL5NZS.js +519 -0
- package/dist/chunk-PP7AGSBU.js +185 -0
- package/dist/{chunk-B5HKWWQB.js → chunk-VLIO6RQR.js} +8 -6
- package/dist/{chunk-HOGQL2H6.js → chunk-VSNA5KAB.js} +177 -101
- package/dist/{chunk-76YMBKW7.js → chunk-XC33FJZN.js} +4 -12
- package/dist/{chunk-JS4HURDT.js → chunk-ZJQPA67D.js} +25 -25
- package/dist/cli/check.js +10 -8
- package/dist/cli/run.js +9 -1
- package/dist/cli/snapshot-warmup.js +10 -7
- package/dist/cli/upgrade.js +599 -0
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +5 -4
- package/dist/plugins.d.ts +1 -1
- package/dist/reporting/conversations.d.ts +116 -0
- package/dist/reporting.d.ts +24 -129
- package/dist/reporting.js +310 -158
- package/package.json +3 -3
- package/dist/chat/runtime/timeout-resume-runner.d.ts +0 -19
- package/dist/chat/services/requester-identity.d.ts +0 -19
- package/dist/chat/services/timeout-resume.d.ts +0 -23
- 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-
|
|
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-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import
|
|
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
package/dist/nitro.js
CHANGED
|
@@ -2,19 +2,20 @@ import {
|
|
|
2
2
|
pluginCatalogConfigFromPluginSet,
|
|
3
3
|
pluginHookRegistrationsFromPluginSet,
|
|
4
4
|
resolveConversationWorkQueueTopic
|
|
5
|
-
} from "./chunk-
|
|
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-
|
|
11
|
+
import "./chunk-XC33FJZN.js";
|
|
13
12
|
import {
|
|
14
13
|
discoverInstalledPluginPackageContent,
|
|
15
14
|
isValidPackageName,
|
|
16
15
|
resolvePackageDir
|
|
17
|
-
} from "./chunk-
|
|
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 "
|
|
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. */
|