@sentry/junior 0.108.0 → 0.109.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/dist/{agent-hooks-NU5HK3PS.js → agent-hooks-FHHVLNPA.js} +12 -12
- package/dist/api/conversations/events.d.ts +1 -1
- package/dist/api/conversations/schema.d.ts +57 -6
- package/dist/api/schema.js +1 -1
- package/dist/api.js +108 -22
- package/dist/app.d.ts +1 -0
- package/dist/app.js +1481 -420
- package/dist/{catalog-runtime-FOFNPR3P.js → catalog-runtime-DMGJSLV2.js} +6 -4
- package/dist/chat/conversations/sql/store.d.ts +0 -7
- package/dist/chat/log-context.d.ts +40 -0
- package/dist/chat/logging.d.ts +3 -26
- package/dist/chat/runtime/slack-resume.d.ts +2 -2
- package/dist/chat/services/turn-session-record.d.ts +0 -2
- package/dist/chat/slack/reply.d.ts +7 -19
- package/dist/chat/state/conversation.d.ts +1 -0
- package/dist/chat/task-execution/slack-work.d.ts +2 -2
- package/dist/chunk-3SXUV423.js +10 -0
- package/dist/{chunk-3RGQLX2F.js → chunk-72OMGPDS.js} +1010 -26
- package/dist/{chunk-2XXEKR44.js → chunk-7Q6WCISF.js} +1 -1
- package/dist/{chunk-TE4QHJH4.js → chunk-FENLIFJD.js} +72 -591
- package/dist/{chunk-XKB7LGIW.js → chunk-FWJRXN4F.js} +3 -3
- package/dist/{chunk-PUAMO4PI.js → chunk-HHVY4SMP.js} +41 -7
- package/dist/{chunk-B5I5LMSP.js → chunk-HO5NT6OE.js} +5 -7
- package/dist/{chunk-O24R4DCK.js → chunk-ISMWX44P.js} +93 -55
- package/dist/{chunk-7FBGKXPE.js → chunk-IVVGFRQQ.js} +5 -0
- package/dist/{chunk-KPMPQ6AA.js → chunk-IXKSQGIO.js} +90 -100
- package/dist/{chunk-SS67LUOK.js → chunk-JNAVF45E.js} +6 -7
- package/dist/{chunk-DY7TRYB5.js → chunk-MGUMR35T.js} +1 -1
- package/dist/{chunk-A5CO2EHL.js → chunk-MXW4GL53.js} +11 -15
- package/dist/{chunk-RT5BUHR5.js → chunk-O3JMSZLI.js} +1 -1
- package/dist/chunk-OCD3OZRK.js +125 -0
- package/dist/{chunk-H3QYZL7K.js → chunk-OUHNWH46.js} +28 -3
- package/dist/{chunk-VIXFG2VC.js → chunk-PLB7MRCQ.js} +1 -2
- package/dist/{chunk-UD6THJ2I.js → chunk-QY636HZU.js} +114 -12
- package/dist/{chunk-VH6KWKG2.js → chunk-RGX4V75M.js} +3 -3
- package/dist/{chunk-B7PUBQQM.js → chunk-SYAQIR5Z.js} +1 -1
- package/dist/chunk-TMBRFULF.js +471 -0
- package/dist/{chunk-YIPD3L6L.js → chunk-TT6GMXNA.js} +8 -10
- package/dist/{chunk-YT72TOEK.js → chunk-WHMOBKT7.js} +2 -2
- package/dist/cli/chat.js +23 -23
- package/dist/cli/check.js +8 -6
- package/dist/cli/plugins.js +16 -16
- package/dist/cli/snapshot-warmup.js +10 -9
- package/dist/cli/upgrade.js +314 -3505
- package/dist/db/db.d.ts +1 -1
- package/dist/{db-AMRBAT5D.js → db-V3NU72O2.js} +8 -6
- package/dist/instrumentation.js +3 -2
- package/dist/nitro.js +5 -4
- package/dist/{runner-TQH5GAJ4.js → runner-JYBCQBLK.js} +17 -20
- package/dist/{skills-ITNHBARB.js → skills-U6MDAST7.js} +7 -5
- package/dist/{validation-AGQ46ZGV.js → validation-72N3JVRU.js} +7 -5
- package/dist/version.d.ts +2 -0
- package/dist/version.js +7 -0
- package/migrations/0006_drop_legacy_conversation_messages.sql +1 -0
- package/migrations/README.md +12 -9
- package/migrations/meta/0006_snapshot.json +948 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +9 -5
- package/dist/chunk-AHJR2IFF.js +0 -115
- package/dist/chunk-G3E7SCME.js +0 -28
- package/dist/chunk-J3B3FPP2.js +0 -1061
- package/dist/chunk-TWINAEZQ.js +0 -1283
package/dist/app.js
CHANGED
|
@@ -6,31 +6,7 @@ import {
|
|
|
6
6
|
processPluginTask,
|
|
7
7
|
requireTurnFailureEventId,
|
|
8
8
|
scheduleSessionCompletedPluginTasks
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
|
|
12
|
-
CONVERSATION_WORK_STALE_ENQUEUE_MS,
|
|
13
|
-
ackMessages,
|
|
14
|
-
appendInboundMessage,
|
|
15
|
-
checkInConversationWork,
|
|
16
|
-
clearConsumedConversationWake,
|
|
17
|
-
clearExpiredConversationLease,
|
|
18
|
-
completeConversationWork,
|
|
19
|
-
deadLetterAttempt,
|
|
20
|
-
drainConversationMailbox,
|
|
21
|
-
getConversation,
|
|
22
|
-
getConversationWorkState,
|
|
23
|
-
isFinalAttempt,
|
|
24
|
-
isInvalidConversationRecordError,
|
|
25
|
-
listActiveConversationIds,
|
|
26
|
-
markConversationWorkEnqueued,
|
|
27
|
-
recordAttemptFailure,
|
|
28
|
-
releaseConversationWork,
|
|
29
|
-
removeActiveConversation,
|
|
30
|
-
requestConversationContinuation,
|
|
31
|
-
requestConversationWork,
|
|
32
|
-
startConversationWork
|
|
33
|
-
} from "./chunk-TWINAEZQ.js";
|
|
9
|
+
} from "./chunk-MXW4GL53.js";
|
|
34
10
|
import {
|
|
35
11
|
GET,
|
|
36
12
|
buildSentryConversationUrl,
|
|
@@ -38,13 +14,13 @@ import {
|
|
|
38
14
|
resolveRootVisibility,
|
|
39
15
|
resolveSlackChannelTypeFromMessage,
|
|
40
16
|
resolveSlackConversationContext
|
|
41
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FWJRXN4F.js";
|
|
42
18
|
import "./chunk-CEA3A3UA.js";
|
|
43
19
|
import {
|
|
44
20
|
getVercelConversationWorkQueue,
|
|
45
21
|
resolveConversationWorkQueueTopic,
|
|
46
22
|
verifyConversationQueueMessage
|
|
47
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-7Q6WCISF.js";
|
|
48
24
|
import {
|
|
49
25
|
PLUGIN_TASK_QUEUE_TOPIC,
|
|
50
26
|
createVercelQueueClient,
|
|
@@ -53,7 +29,7 @@ import {
|
|
|
53
29
|
import {
|
|
54
30
|
validatePluginEgressCredentialHooks,
|
|
55
31
|
validatePluginRegistrations
|
|
56
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-MGUMR35T.js";
|
|
57
33
|
import {
|
|
58
34
|
defineJuniorPlugins,
|
|
59
35
|
pluginCatalogConfigFromEnv,
|
|
@@ -106,21 +82,28 @@ import {
|
|
|
106
82
|
splitSlackReplyText,
|
|
107
83
|
startOAuthFlow,
|
|
108
84
|
truncateStatusText
|
|
109
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-IXKSQGIO.js";
|
|
110
86
|
import {
|
|
111
87
|
ConversationTurnBoundaryError,
|
|
112
88
|
CooperativeTurnYieldError,
|
|
89
|
+
JUNIOR_THREAD_STATE_TTL_MS,
|
|
113
90
|
TurnInputCommitLostError,
|
|
114
91
|
TurnInputDeferredError,
|
|
115
92
|
TurnSliceLimitExceededError,
|
|
93
|
+
abandonAgentTurnSessionRecord,
|
|
116
94
|
buildConversationContext,
|
|
117
95
|
buildDeterministicAssistantMessageId,
|
|
118
96
|
buildDeterministicTurnId,
|
|
119
97
|
buildTurnLimitResponse,
|
|
120
98
|
clearPendingAuth,
|
|
121
99
|
coerceThreadArtifactsState,
|
|
100
|
+
coerceThreadConversationState,
|
|
101
|
+
commitMessages,
|
|
122
102
|
completeDeliveredTurn,
|
|
123
103
|
createConversationMemoryService,
|
|
104
|
+
failAgentTurnSessionRecord,
|
|
105
|
+
getAgentTurnSessionRecord,
|
|
106
|
+
getAgentTurnSessionRecordForResume,
|
|
124
107
|
getChannelConfigurationService,
|
|
125
108
|
getChannelConfigurationServiceById,
|
|
126
109
|
getConversationMessageSlackTs,
|
|
@@ -135,6 +118,9 @@ import {
|
|
|
135
118
|
isPendingAuthLatestRequest,
|
|
136
119
|
isTurnInputCommitLostError,
|
|
137
120
|
isTurnInputDeferredError,
|
|
121
|
+
listAgentTurnSessionSummariesForConversation,
|
|
122
|
+
loadConversationProjection,
|
|
123
|
+
loadProjection,
|
|
138
124
|
markConversationMessage,
|
|
139
125
|
markTurnClosed,
|
|
140
126
|
markTurnFailed,
|
|
@@ -148,33 +134,23 @@ import {
|
|
|
148
134
|
persistThreadStateById,
|
|
149
135
|
persistWithRetry,
|
|
150
136
|
persistYieldSessionRecord,
|
|
137
|
+
recordAgentTurnSessionSummary,
|
|
138
|
+
recordAuthorizationCompleted,
|
|
151
139
|
recordDeliveredAssistantMessage,
|
|
152
140
|
startActiveTurn,
|
|
153
141
|
turnHasReply,
|
|
154
142
|
updateConversationStats,
|
|
155
143
|
upsertConversationMessage
|
|
156
|
-
} from "./chunk-
|
|
157
|
-
import
|
|
158
|
-
JUNIOR_THREAD_STATE_TTL_MS,
|
|
159
|
-
abandonAgentTurnSessionRecord,
|
|
160
|
-
coerceThreadConversationState,
|
|
161
|
-
commitMessages,
|
|
162
|
-
failAgentTurnSessionRecord,
|
|
163
|
-
getAgentTurnSessionRecord,
|
|
164
|
-
getAgentTurnSessionRecordForResume,
|
|
165
|
-
listAgentTurnSessionSummariesForConversation,
|
|
166
|
-
loadConversationProjection,
|
|
167
|
-
loadProjection,
|
|
168
|
-
recordAgentTurnSessionSummary,
|
|
169
|
-
recordAuthorizationCompleted
|
|
170
|
-
} from "./chunk-J3B3FPP2.js";
|
|
171
|
-
import "./chunk-B5I5LMSP.js";
|
|
144
|
+
} from "./chunk-72OMGPDS.js";
|
|
145
|
+
import "./chunk-HO5NT6OE.js";
|
|
172
146
|
import "./chunk-MU6HHZEN.js";
|
|
173
147
|
import "./chunk-PDO5BLNM.js";
|
|
174
148
|
import "./chunk-UIE3R5XU.js";
|
|
175
149
|
import {
|
|
176
150
|
bindScheduledTaskCredentialSubject,
|
|
177
151
|
bindSlackDirectCredentialSubject,
|
|
152
|
+
createPluginLogger,
|
|
153
|
+
createPluginState,
|
|
178
154
|
getDashboardConversationLink,
|
|
179
155
|
getPluginApiRoutes,
|
|
180
156
|
getPluginRoutes,
|
|
@@ -185,16 +161,12 @@ import {
|
|
|
185
161
|
validatePlugins,
|
|
186
162
|
verifyScheduledTaskCredentialSubject,
|
|
187
163
|
verifySlackDirectCredentialSubject
|
|
188
|
-
} from "./chunk-
|
|
189
|
-
import {
|
|
190
|
-
createPluginLogger,
|
|
191
|
-
createPluginState
|
|
192
|
-
} from "./chunk-AHJR2IFF.js";
|
|
193
|
-
import "./chunk-G3E7SCME.js";
|
|
164
|
+
} from "./chunk-QY636HZU.js";
|
|
194
165
|
import {
|
|
195
166
|
acquireActiveLock,
|
|
167
|
+
getDefaultRedisStateAdapterFor,
|
|
196
168
|
getStateAdapter
|
|
197
|
-
} from "./chunk-
|
|
169
|
+
} from "./chunk-OUHNWH46.js";
|
|
198
170
|
import {
|
|
199
171
|
SlackActionError,
|
|
200
172
|
downloadPrivateSlackFile,
|
|
@@ -205,7 +177,7 @@ import {
|
|
|
205
177
|
normalizeSlackConversationId,
|
|
206
178
|
runWithSlackInstallationToken,
|
|
207
179
|
runWithWorkspaceTeamId
|
|
208
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-RGX4V75M.js";
|
|
209
181
|
import {
|
|
210
182
|
contextProvenance,
|
|
211
183
|
getConversationEventStore,
|
|
@@ -213,10 +185,12 @@ import {
|
|
|
213
185
|
getDb,
|
|
214
186
|
getSqlExecutor,
|
|
215
187
|
instructionProvenanceFor,
|
|
216
|
-
juniorConversationEvents,
|
|
217
|
-
juniorConversations,
|
|
218
188
|
withConversationEventLock
|
|
219
|
-
} from "./chunk-
|
|
189
|
+
} from "./chunk-FENLIFJD.js";
|
|
190
|
+
import {
|
|
191
|
+
juniorConversationEvents,
|
|
192
|
+
juniorConversations
|
|
193
|
+
} from "./chunk-TMBRFULF.js";
|
|
220
194
|
import {
|
|
221
195
|
sleep
|
|
222
196
|
} from "./chunk-4ZNGQH7C.js";
|
|
@@ -240,7 +214,7 @@ import {
|
|
|
240
214
|
standardModelId,
|
|
241
215
|
stripRuntimeTurnContext,
|
|
242
216
|
trimTrailingAssistantMessages
|
|
243
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-JNAVF45E.js";
|
|
244
218
|
import {
|
|
245
219
|
juniorDestinations
|
|
246
220
|
} from "./chunk-VFUK3X5B.js";
|
|
@@ -249,32 +223,35 @@ import {
|
|
|
249
223
|
readSlackRawMessageContext,
|
|
250
224
|
resolveSlackChannelIdFromMessage,
|
|
251
225
|
resolveSlackChannelIdFromThreadId
|
|
252
|
-
} from "./chunk-
|
|
226
|
+
} from "./chunk-O3JMSZLI.js";
|
|
253
227
|
import {
|
|
254
228
|
createSlackDestination,
|
|
255
229
|
destinationKey,
|
|
256
230
|
parseDestination,
|
|
257
231
|
requireSlackDestination,
|
|
258
232
|
sameDestination
|
|
259
|
-
} from "./chunk-
|
|
233
|
+
} from "./chunk-SYAQIR5Z.js";
|
|
260
234
|
import {
|
|
261
235
|
discoverSkills
|
|
262
|
-
} from "./chunk-
|
|
236
|
+
} from "./chunk-WHMOBKT7.js";
|
|
237
|
+
import {
|
|
238
|
+
pluginCatalogRuntime
|
|
239
|
+
} from "./chunk-3SXUV423.js";
|
|
263
240
|
import {
|
|
264
241
|
buildOAuthTokenRequest,
|
|
265
242
|
credentialSubjectSchema,
|
|
266
243
|
hasRequiredOAuthScope,
|
|
267
|
-
parseOAuthTokenResponse
|
|
268
|
-
|
|
269
|
-
} from "./chunk-YIPD3L6L.js";
|
|
244
|
+
parseOAuthTokenResponse
|
|
245
|
+
} from "./chunk-TT6GMXNA.js";
|
|
270
246
|
import {
|
|
271
247
|
createActor,
|
|
272
248
|
createSlackActor,
|
|
273
249
|
createSlackResumeActor,
|
|
274
250
|
isActorUserId,
|
|
275
251
|
isUserActor,
|
|
276
|
-
parseActorUserId
|
|
277
|
-
|
|
252
|
+
parseActorUserId,
|
|
253
|
+
parseStoredSlackActor
|
|
254
|
+
} from "./chunk-PLB7MRCQ.js";
|
|
278
255
|
import {
|
|
279
256
|
buildTurnFailureResponse,
|
|
280
257
|
createChatSdkLogger,
|
|
@@ -290,22 +267,27 @@ import {
|
|
|
290
267
|
setTags,
|
|
291
268
|
withContext,
|
|
292
269
|
withSpan
|
|
293
|
-
} from "./chunk-
|
|
270
|
+
} from "./chunk-ISMWX44P.js";
|
|
294
271
|
import {
|
|
272
|
+
isRecord,
|
|
273
|
+
toOptionalNumber,
|
|
295
274
|
toOptionalString
|
|
296
275
|
} from "./chunk-OB42YVAE.js";
|
|
297
276
|
import {
|
|
298
277
|
continueTrace
|
|
299
278
|
} from "./chunk-6GWA276C.js";
|
|
300
279
|
import "./chunk-B3IT5END.js";
|
|
280
|
+
import {
|
|
281
|
+
homeDir
|
|
282
|
+
} from "./chunk-3V7OFJZC.js";
|
|
301
283
|
import {
|
|
302
284
|
JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
|
|
303
285
|
parseSlackChannelId,
|
|
304
286
|
parseSlackMessageTs
|
|
305
|
-
} from "./chunk-
|
|
287
|
+
} from "./chunk-IVVGFRQQ.js";
|
|
306
288
|
import {
|
|
307
|
-
|
|
308
|
-
} from "./chunk-
|
|
289
|
+
JUNIOR_VERSION
|
|
290
|
+
} from "./chunk-OCD3OZRK.js";
|
|
309
291
|
import "./chunk-MLKGABMK.js";
|
|
310
292
|
|
|
311
293
|
// src/app.ts
|
|
@@ -353,53 +335,26 @@ function buildSlackReplyBlocks(text, footer) {
|
|
|
353
335
|
}
|
|
354
336
|
|
|
355
337
|
// src/chat/slack/reply.ts
|
|
356
|
-
function
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const hasVisibleDelivery = post.text.trim().length > 0;
|
|
375
|
-
if (hasVisibleDelivery) {
|
|
376
|
-
await args.beforePost?.();
|
|
377
|
-
}
|
|
378
|
-
let messageTs;
|
|
379
|
-
try {
|
|
380
|
-
if (post.text.trim().length > 0) {
|
|
381
|
-
const footer = index === lastTextPostIndex ? args.footer : void 0;
|
|
382
|
-
const blocks = buildSlackReplyBlocks(post.text, footer);
|
|
383
|
-
const response = await postSlackMessage({
|
|
384
|
-
channelId: args.channelId,
|
|
385
|
-
threadTs: args.threadTs,
|
|
386
|
-
text: post.text,
|
|
387
|
-
...blocks ? { blocks } : {}
|
|
388
|
-
});
|
|
389
|
-
messageTs = response.ts;
|
|
390
|
-
lastPostedMessageTs = response.ts;
|
|
391
|
-
}
|
|
392
|
-
continue;
|
|
393
|
-
} catch (error) {
|
|
394
|
-
await args.onPostError?.({
|
|
395
|
-
error,
|
|
396
|
-
messageTs,
|
|
397
|
-
stage: post.stage
|
|
398
|
-
});
|
|
399
|
-
throw error;
|
|
400
|
-
}
|
|
338
|
+
async function sendSlackReply(args) {
|
|
339
|
+
const chunks = splitSlackReplyText(args.text);
|
|
340
|
+
const footer = buildSlackReplyFooter({
|
|
341
|
+
conversationId: args.conversationId
|
|
342
|
+
});
|
|
343
|
+
let lastMessageTs;
|
|
344
|
+
for (const [index, text] of chunks.entries()) {
|
|
345
|
+
const blocks = buildSlackReplyBlocks(
|
|
346
|
+
text,
|
|
347
|
+
index === chunks.length - 1 ? footer : void 0
|
|
348
|
+
);
|
|
349
|
+
const response = await postSlackMessage({
|
|
350
|
+
channelId: args.channelId,
|
|
351
|
+
threadTs: args.threadTs,
|
|
352
|
+
text,
|
|
353
|
+
...blocks ? { blocks } : {}
|
|
354
|
+
});
|
|
355
|
+
lastMessageTs = response.ts;
|
|
401
356
|
}
|
|
402
|
-
return
|
|
357
|
+
return lastMessageTs;
|
|
403
358
|
}
|
|
404
359
|
|
|
405
360
|
// src/chat/slack/errors.ts
|
|
@@ -927,8 +882,8 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
927
882
|
const turnId = getDispatchTurnId(dispatch.id);
|
|
928
883
|
const logContext = {
|
|
929
884
|
conversationId,
|
|
930
|
-
|
|
931
|
-
|
|
885
|
+
messageConversationId: conversationId,
|
|
886
|
+
destinationName: dispatch.destination.channelId,
|
|
932
887
|
runId: dispatch.id,
|
|
933
888
|
actorType: dispatch.actor.platform,
|
|
934
889
|
actorId: dispatch.actor.name,
|
|
@@ -1018,15 +973,15 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1018
973
|
let resultMessageTs;
|
|
1019
974
|
let assistantMessageDelivered = turnHasReply(conversation, turnId);
|
|
1020
975
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
1021
|
-
|
|
1022
|
-
if (posts.length === 0) {
|
|
976
|
+
if (!assistantMessage.text.trim()) {
|
|
1023
977
|
return;
|
|
1024
978
|
}
|
|
1025
979
|
failureCode = "delivery_failed";
|
|
1026
980
|
try {
|
|
1027
|
-
resultMessageTs = await
|
|
981
|
+
resultMessageTs = await sendSlackReply({
|
|
1028
982
|
channelId: dispatch.destination.channelId,
|
|
1029
|
-
|
|
983
|
+
conversationId,
|
|
984
|
+
text: assistantMessage.text
|
|
1030
985
|
});
|
|
1031
986
|
} catch (error) {
|
|
1032
987
|
if (isRetryableSlackPostError(error)) {
|
|
@@ -1222,13 +1177,7 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1222
1177
|
destinationVisibility: dispatch.destinationVisibility,
|
|
1223
1178
|
source: dispatch.source,
|
|
1224
1179
|
actor: dispatch.actor,
|
|
1225
|
-
surface: "api"
|
|
1226
|
-
logContext: {
|
|
1227
|
-
threadId: conversationId,
|
|
1228
|
-
channelId: dispatch.destination.channelId,
|
|
1229
|
-
runId: dispatch.id,
|
|
1230
|
-
assistantUserName: botConfig.userName
|
|
1231
|
-
}
|
|
1180
|
+
surface: "api"
|
|
1232
1181
|
});
|
|
1233
1182
|
}
|
|
1234
1183
|
if (statePersisted) {
|
|
@@ -1250,117 +1199,1244 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1250
1199
|
}
|
|
1251
1200
|
lifecycleTerminalized = true;
|
|
1252
1201
|
}
|
|
1253
|
-
dispatch = await markDispatch({
|
|
1254
|
-
dispatch,
|
|
1255
|
-
status: failure ? "failed" : "completed",
|
|
1256
|
-
...failure ? { errorMessage: failure } : {},
|
|
1257
|
-
resultMessageTs
|
|
1202
|
+
dispatch = await markDispatch({
|
|
1203
|
+
dispatch,
|
|
1204
|
+
status: failure ? "failed" : "completed",
|
|
1205
|
+
...failure ? { errorMessage: failure } : {},
|
|
1206
|
+
resultMessageTs
|
|
1207
|
+
});
|
|
1208
|
+
if (!failure) {
|
|
1209
|
+
try {
|
|
1210
|
+
await scheduleCompletedTasks({
|
|
1211
|
+
conversationId,
|
|
1212
|
+
sessionId: turnId
|
|
1213
|
+
});
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
logException(
|
|
1216
|
+
error,
|
|
1217
|
+
"plugin_session_completed_task_schedule_failed",
|
|
1218
|
+
logContext,
|
|
1219
|
+
{},
|
|
1220
|
+
"Plugin session.completed task scheduling failed"
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (error instanceof AuthorizationFlowDisabledError) {
|
|
1226
|
+
if (lifecycleStarted && !lifecycleTerminalized) {
|
|
1227
|
+
await turnLifecycle.fail({
|
|
1228
|
+
conversationId,
|
|
1229
|
+
turnId,
|
|
1230
|
+
createdAtMs: Date.now(),
|
|
1231
|
+
failureCode: "agent_run_failed"
|
|
1232
|
+
});
|
|
1233
|
+
lifecycleTerminalized = true;
|
|
1234
|
+
}
|
|
1235
|
+
await markDispatch({
|
|
1236
|
+
dispatch,
|
|
1237
|
+
status: "blocked",
|
|
1238
|
+
errorMessage: `Dispatch requires ${error.provider} authorization.`
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (error instanceof PluginCredentialFailureError) {
|
|
1243
|
+
if (lifecycleStarted && !lifecycleTerminalized) {
|
|
1244
|
+
await turnLifecycle.fail({
|
|
1245
|
+
conversationId,
|
|
1246
|
+
turnId,
|
|
1247
|
+
createdAtMs: Date.now(),
|
|
1248
|
+
failureCode: "agent_run_failed"
|
|
1249
|
+
});
|
|
1250
|
+
lifecycleTerminalized = true;
|
|
1251
|
+
}
|
|
1252
|
+
await markDispatch({
|
|
1253
|
+
dispatch,
|
|
1254
|
+
status: "blocked",
|
|
1255
|
+
errorMessage: error.message
|
|
1256
|
+
});
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const eventId = logException(
|
|
1260
|
+
error,
|
|
1261
|
+
"agent_dispatch_run_failed",
|
|
1262
|
+
{
|
|
1263
|
+
...logContext,
|
|
1264
|
+
modelId: standardModelId(botConfig)
|
|
1265
|
+
},
|
|
1266
|
+
{},
|
|
1267
|
+
"Agent dispatch failed"
|
|
1268
|
+
);
|
|
1269
|
+
if (lifecycleStarted && !lifecycleTerminalized) {
|
|
1270
|
+
await turnLifecycle.fail({
|
|
1271
|
+
conversationId,
|
|
1272
|
+
turnId,
|
|
1273
|
+
createdAtMs: Date.now(),
|
|
1274
|
+
failureCode,
|
|
1275
|
+
...eventId ? { eventId } : {}
|
|
1276
|
+
});
|
|
1277
|
+
lifecycleTerminalized = true;
|
|
1278
|
+
}
|
|
1279
|
+
await markDispatch({
|
|
1280
|
+
dispatch,
|
|
1281
|
+
status: "failed",
|
|
1282
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
1283
|
+
});
|
|
1284
|
+
} finally {
|
|
1285
|
+
await stateAdapter.releaseLock(destinationLock);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/handlers/agent-dispatch.ts
|
|
1290
|
+
async function POST(request, waitUntil, options) {
|
|
1291
|
+
const payload = await verifyDispatchCallbackRequest(request);
|
|
1292
|
+
if (!payload) {
|
|
1293
|
+
return new Response("Unauthorized", { status: 401 });
|
|
1294
|
+
}
|
|
1295
|
+
waitUntil(
|
|
1296
|
+
() => runAgentDispatchSlice(payload, {
|
|
1297
|
+
agentRunner: options.agentRunner
|
|
1298
|
+
}).catch((error) => {
|
|
1299
|
+
logException(
|
|
1300
|
+
error,
|
|
1301
|
+
"agent_dispatch_handler_failed",
|
|
1302
|
+
{},
|
|
1303
|
+
{ "app.dispatch.id": payload.id },
|
|
1304
|
+
"Agent dispatch handler failed"
|
|
1305
|
+
);
|
|
1306
|
+
})
|
|
1307
|
+
);
|
|
1308
|
+
return new Response("Accepted", { status: 202 });
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/handlers/heartbeat.ts
|
|
1312
|
+
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1313
|
+
|
|
1314
|
+
// src/chat/task-execution/state.ts
|
|
1315
|
+
import { randomUUID } from "crypto";
|
|
1316
|
+
var CONVERSATION_PREFIX = "junior:conversation";
|
|
1317
|
+
var CONVERSATION_SCHEMA_VERSION = 1;
|
|
1318
|
+
var CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH = 1e4;
|
|
1319
|
+
var CONVERSATION_INDEX_LOCK_TTL_MS = 1e4;
|
|
1320
|
+
var CONVERSATION_INDEX_LOCK_WAIT_MS = 2e3;
|
|
1321
|
+
var CONVERSATION_INDEX_LOCK_RETRY_MS = 25;
|
|
1322
|
+
var CONVERSATION_MUTATION_LOCK_TTL_MS = 1e4;
|
|
1323
|
+
var CONVERSATION_MUTATION_WAIT_MS = 1e4;
|
|
1324
|
+
var CONVERSATION_MUTATION_RETRY_MS = 25;
|
|
1325
|
+
var InvalidConversationRecordError = class extends Error {
|
|
1326
|
+
constructor(conversationId) {
|
|
1327
|
+
super(`Conversation record is invalid for ${conversationId}`);
|
|
1328
|
+
this.name = "InvalidConversationRecordError";
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
function isInvalidConversationRecordError(error) {
|
|
1332
|
+
return error instanceof InvalidConversationRecordError;
|
|
1333
|
+
}
|
|
1334
|
+
var ConversationMutationFencedError = class extends Error {
|
|
1335
|
+
constructor(conversationId) {
|
|
1336
|
+
super(
|
|
1337
|
+
`Conversation mutation lock was lost before write for ${conversationId}`
|
|
1338
|
+
);
|
|
1339
|
+
this.name = "ConversationMutationFencedError";
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
var CONVERSATION_BY_ACTIVITY_INDEX_KEY = `${CONVERSATION_PREFIX}:by-activity`;
|
|
1343
|
+
var CONVERSATION_ACTIVE_INDEX_KEY = `${CONVERSATION_PREFIX}:active`;
|
|
1344
|
+
var CONVERSATION_WORK_LEASE_TTL_MS = 9e4;
|
|
1345
|
+
var CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15e3;
|
|
1346
|
+
var CONVERSATION_WORK_STALE_ENQUEUE_MS = 6e4;
|
|
1347
|
+
var CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS = 5;
|
|
1348
|
+
function conversationKey(conversationId) {
|
|
1349
|
+
return `${CONVERSATION_PREFIX}:${conversationId}`;
|
|
1350
|
+
}
|
|
1351
|
+
function indexLockKey(indexKey) {
|
|
1352
|
+
return `${indexKey}:lock`;
|
|
1353
|
+
}
|
|
1354
|
+
function mutationLockKey(conversationId) {
|
|
1355
|
+
return `${CONVERSATION_PREFIX}:mutation:${conversationId}`;
|
|
1356
|
+
}
|
|
1357
|
+
function now() {
|
|
1358
|
+
return Date.now();
|
|
1359
|
+
}
|
|
1360
|
+
function compareMessages(left, right) {
|
|
1361
|
+
return left.createdAtMs - right.createdAtMs || left.receivedAtMs - right.receivedAtMs || left.inboundMessageId.localeCompare(right.inboundMessageId);
|
|
1362
|
+
}
|
|
1363
|
+
function inputHasAttachments(input) {
|
|
1364
|
+
return Array.isArray(input.attachments) && input.attachments.length > 0;
|
|
1365
|
+
}
|
|
1366
|
+
function upgradedPendingMessage(stored, duplicate) {
|
|
1367
|
+
if (!inputHasAttachments(duplicate.input) || inputHasAttachments(stored.input)) {
|
|
1368
|
+
return stored;
|
|
1369
|
+
}
|
|
1370
|
+
return { ...stored, input: duplicate.input };
|
|
1371
|
+
}
|
|
1372
|
+
function compareIndexDescending(left, right) {
|
|
1373
|
+
return right.score - left.score || right.conversationId.localeCompare(left.conversationId);
|
|
1374
|
+
}
|
|
1375
|
+
function compareIndexAscending(left, right) {
|
|
1376
|
+
return left.score - right.score || left.conversationId.localeCompare(right.conversationId);
|
|
1377
|
+
}
|
|
1378
|
+
function uniqueStrings(values) {
|
|
1379
|
+
return [...new Set(values)];
|
|
1380
|
+
}
|
|
1381
|
+
function normalizeSource(value) {
|
|
1382
|
+
if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "resource_event" || value === "scheduler" || value === "slack") {
|
|
1383
|
+
return value;
|
|
1384
|
+
}
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
function normalizeExecutionStatus(value) {
|
|
1388
|
+
if (value === "awaiting_resume" || value === "failed" || value === "idle" || value === "pending" || value === "running") {
|
|
1389
|
+
return value;
|
|
1390
|
+
}
|
|
1391
|
+
return void 0;
|
|
1392
|
+
}
|
|
1393
|
+
function normalizeMetadata2(value) {
|
|
1394
|
+
if (!isRecord(value)) {
|
|
1395
|
+
return void 0;
|
|
1396
|
+
}
|
|
1397
|
+
return value;
|
|
1398
|
+
}
|
|
1399
|
+
function normalizeInput(value) {
|
|
1400
|
+
if (!isRecord(value)) {
|
|
1401
|
+
return void 0;
|
|
1402
|
+
}
|
|
1403
|
+
const text = typeof value.text === "string" ? value.text : void 0;
|
|
1404
|
+
const attachments = Array.isArray(value.attachments) ? [...value.attachments] : void 0;
|
|
1405
|
+
if (text === void 0 || !text.trim() && !attachments?.length) {
|
|
1406
|
+
return void 0;
|
|
1407
|
+
}
|
|
1408
|
+
return {
|
|
1409
|
+
text,
|
|
1410
|
+
authorId: toOptionalString(value.authorId),
|
|
1411
|
+
attachments,
|
|
1412
|
+
metadata: normalizeMetadata2(value.metadata)
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
function normalizeMessage(value) {
|
|
1416
|
+
if (!isRecord(value)) {
|
|
1417
|
+
return void 0;
|
|
1418
|
+
}
|
|
1419
|
+
const conversationId = toOptionalString(value.conversationId);
|
|
1420
|
+
const inboundMessageId = toOptionalString(value.inboundMessageId);
|
|
1421
|
+
const source = normalizeSource(value.source);
|
|
1422
|
+
const destination = parseDestination(value.destination);
|
|
1423
|
+
const createdAtMs = toOptionalNumber(value.createdAtMs);
|
|
1424
|
+
const receivedAtMs = toOptionalNumber(value.receivedAtMs);
|
|
1425
|
+
const input = normalizeInput(value.input);
|
|
1426
|
+
if (!conversationId || !destination || !inboundMessageId || !source || typeof createdAtMs !== "number" || typeof receivedAtMs !== "number" || !input) {
|
|
1427
|
+
return void 0;
|
|
1428
|
+
}
|
|
1429
|
+
return {
|
|
1430
|
+
conversationId,
|
|
1431
|
+
destination,
|
|
1432
|
+
inboundMessageId,
|
|
1433
|
+
source,
|
|
1434
|
+
createdAtMs,
|
|
1435
|
+
receivedAtMs,
|
|
1436
|
+
input,
|
|
1437
|
+
injectedAtMs: toOptionalNumber(value.injectedAtMs),
|
|
1438
|
+
attemptCount: toOptionalNumber(value.attemptCount)
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function isFinalAttempt(message) {
|
|
1442
|
+
return (message.attemptCount ?? 0) >= CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS - 1;
|
|
1443
|
+
}
|
|
1444
|
+
function normalizeActor(value) {
|
|
1445
|
+
return parseStoredSlackActor(value);
|
|
1446
|
+
}
|
|
1447
|
+
function normalizeLease(value) {
|
|
1448
|
+
if (!isRecord(value)) {
|
|
1449
|
+
return void 0;
|
|
1450
|
+
}
|
|
1451
|
+
const token = toOptionalString(value.token);
|
|
1452
|
+
const acquiredAtMs = toOptionalNumber(value.acquiredAtMs);
|
|
1453
|
+
const lastCheckInAtMs = toOptionalNumber(value.lastCheckInAtMs);
|
|
1454
|
+
const expiresAtMs = toOptionalNumber(value.expiresAtMs);
|
|
1455
|
+
if (!token || typeof acquiredAtMs !== "number" || typeof lastCheckInAtMs !== "number" || typeof expiresAtMs !== "number") {
|
|
1456
|
+
return void 0;
|
|
1457
|
+
}
|
|
1458
|
+
return {
|
|
1459
|
+
token,
|
|
1460
|
+
acquiredAtMs,
|
|
1461
|
+
lastCheckInAtMs,
|
|
1462
|
+
expiresAtMs
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function normalizeExecution(conversationId, value) {
|
|
1466
|
+
if (!isRecord(value)) {
|
|
1467
|
+
return void 0;
|
|
1468
|
+
}
|
|
1469
|
+
const status = normalizeExecutionStatus(value.status);
|
|
1470
|
+
if (!status) {
|
|
1471
|
+
return void 0;
|
|
1472
|
+
}
|
|
1473
|
+
const pendingMessages2 = Array.isArray(value.pendingMessages) ? value.pendingMessages.map(normalizeMessage).filter((message) => Boolean(message)).filter((message) => message.conversationId === conversationId).filter((message) => message.injectedAtMs === void 0).sort(compareMessages) : [];
|
|
1474
|
+
const inboundMessageIds = Array.isArray(value.inboundMessageIds) ? uniqueStrings(
|
|
1475
|
+
value.inboundMessageIds.map((id) => typeof id === "string" ? id : void 0).filter((id) => Boolean(id))
|
|
1476
|
+
) : [];
|
|
1477
|
+
const lease = normalizeLease(value.lease);
|
|
1478
|
+
const normalizedStatus = status === "idle" && lease ? "running" : status === "idle" && pendingMessages2.length > 0 ? "pending" : status;
|
|
1479
|
+
return {
|
|
1480
|
+
status: normalizedStatus,
|
|
1481
|
+
inboundMessageIds: uniqueStrings([
|
|
1482
|
+
...inboundMessageIds,
|
|
1483
|
+
...pendingMessages2.map((message) => message.inboundMessageId)
|
|
1484
|
+
]),
|
|
1485
|
+
pendingCount: pendingMessages2.length,
|
|
1486
|
+
pendingMessages: pendingMessages2,
|
|
1487
|
+
lease,
|
|
1488
|
+
lastCheckpointAtMs: toOptionalNumber(value.lastCheckpointAtMs),
|
|
1489
|
+
lastEnqueuedAtMs: toOptionalNumber(value.lastEnqueuedAtMs),
|
|
1490
|
+
runId: toOptionalString(value.runId),
|
|
1491
|
+
updatedAtMs: toOptionalNumber(value.updatedAtMs)
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function normalizeConversation(conversationId, value) {
|
|
1495
|
+
if (!isRecord(value) || value.schemaVersion !== CONVERSATION_SCHEMA_VERSION) {
|
|
1496
|
+
return void 0;
|
|
1497
|
+
}
|
|
1498
|
+
const storedConversationId = toOptionalString(value.conversationId);
|
|
1499
|
+
const createdAtMs = toOptionalNumber(value.createdAtMs);
|
|
1500
|
+
const lastActivityAtMs = toOptionalNumber(value.lastActivityAtMs);
|
|
1501
|
+
const updatedAtMs = toOptionalNumber(value.updatedAtMs);
|
|
1502
|
+
const execution = normalizeExecution(conversationId, value.execution);
|
|
1503
|
+
const destination = value.destination === void 0 ? void 0 : parseDestination(value.destination);
|
|
1504
|
+
if (storedConversationId !== conversationId || typeof createdAtMs !== "number" || typeof lastActivityAtMs !== "number" || typeof updatedAtMs !== "number" || !execution || value.destination !== void 0 && !destination) {
|
|
1505
|
+
return void 0;
|
|
1506
|
+
}
|
|
1507
|
+
if (execution.pendingMessages.length > 0 && (!destination || execution.pendingMessages.some(
|
|
1508
|
+
(message) => !sameDestination(message.destination, destination)
|
|
1509
|
+
))) {
|
|
1510
|
+
return void 0;
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
schemaVersion: CONVERSATION_SCHEMA_VERSION,
|
|
1514
|
+
conversationId,
|
|
1515
|
+
createdAtMs,
|
|
1516
|
+
lastActivityAtMs,
|
|
1517
|
+
updatedAtMs,
|
|
1518
|
+
execution,
|
|
1519
|
+
...destination ? { destination } : {},
|
|
1520
|
+
...toOptionalString(value.title) ? { title: toOptionalString(value.title) } : {},
|
|
1521
|
+
...toOptionalString(value.channelName) ? { channelName: toOptionalString(value.channelName) } : {},
|
|
1522
|
+
...normalizeActor(value.actor) ? { actor: normalizeActor(value.actor) } : {},
|
|
1523
|
+
...normalizeSource(value.source) ? { source: normalizeSource(value.source) } : {}
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
function emptyConversation(args) {
|
|
1527
|
+
return {
|
|
1528
|
+
schemaVersion: CONVERSATION_SCHEMA_VERSION,
|
|
1529
|
+
conversationId: args.conversationId,
|
|
1530
|
+
createdAtMs: args.nowMs,
|
|
1531
|
+
lastActivityAtMs: args.nowMs,
|
|
1532
|
+
updatedAtMs: args.nowMs,
|
|
1533
|
+
...args.destination ? { destination: args.destination } : {},
|
|
1534
|
+
...args.source ? { source: args.source } : {},
|
|
1535
|
+
execution: {
|
|
1536
|
+
status: "idle",
|
|
1537
|
+
inboundMessageIds: [],
|
|
1538
|
+
pendingCount: 0,
|
|
1539
|
+
pendingMessages: [],
|
|
1540
|
+
updatedAtMs: args.nowMs
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function isLeaseActive(lease, nowMs) {
|
|
1545
|
+
return Boolean(lease && lease.expiresAtMs > nowMs);
|
|
1546
|
+
}
|
|
1547
|
+
function pendingMessages(conversation) {
|
|
1548
|
+
return [...conversation.execution.pendingMessages].sort(compareMessages);
|
|
1549
|
+
}
|
|
1550
|
+
function isRunnableStatus(status) {
|
|
1551
|
+
return status !== "failed" && status !== "idle";
|
|
1552
|
+
}
|
|
1553
|
+
function hasRunnableWork(conversation) {
|
|
1554
|
+
return isRunnableStatus(conversation.execution.status) || pendingMessages(conversation).length > 0;
|
|
1555
|
+
}
|
|
1556
|
+
function executionWithPendingMessages(execution, pending) {
|
|
1557
|
+
const pendingMessages2 = [...pending].sort(compareMessages);
|
|
1558
|
+
const status = execution.status === "idle" && execution.lease ? "running" : execution.status === "idle" && pendingMessages2.length > 0 ? "pending" : execution.status;
|
|
1559
|
+
return {
|
|
1560
|
+
...execution,
|
|
1561
|
+
status,
|
|
1562
|
+
inboundMessageIds: uniqueStrings([
|
|
1563
|
+
...execution.inboundMessageIds,
|
|
1564
|
+
...pendingMessages2.map((message) => message.inboundMessageId)
|
|
1565
|
+
]),
|
|
1566
|
+
pendingMessages: pendingMessages2,
|
|
1567
|
+
pendingCount: pendingMessages2.length
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function withExecutionUpdate(conversation, execution, nowMs) {
|
|
1571
|
+
return {
|
|
1572
|
+
...conversation,
|
|
1573
|
+
updatedAtMs: nowMs,
|
|
1574
|
+
execution: {
|
|
1575
|
+
...executionWithPendingMessages(execution, execution.pendingMessages),
|
|
1576
|
+
updatedAtMs: nowMs
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
async function getConnectedState(stateAdapter) {
|
|
1581
|
+
const state = stateAdapter ?? getStateAdapter();
|
|
1582
|
+
await state.connect();
|
|
1583
|
+
return state;
|
|
1584
|
+
}
|
|
1585
|
+
async function sleep2(ms) {
|
|
1586
|
+
await new Promise((resolve) => {
|
|
1587
|
+
const timer = setTimeout(resolve, ms);
|
|
1588
|
+
timer.unref?.();
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
async function withIndexLock(state, indexKey, callback) {
|
|
1592
|
+
const startedAtMs = now();
|
|
1593
|
+
let lock;
|
|
1594
|
+
while (true) {
|
|
1595
|
+
lock = await state.acquireLock(
|
|
1596
|
+
indexLockKey(indexKey),
|
|
1597
|
+
CONVERSATION_INDEX_LOCK_TTL_MS
|
|
1598
|
+
);
|
|
1599
|
+
if (lock) {
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
if (now() - startedAtMs >= CONVERSATION_INDEX_LOCK_WAIT_MS) {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
`Could not acquire conversation index lock for ${indexKey}`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
await sleep2(CONVERSATION_INDEX_LOCK_RETRY_MS);
|
|
1608
|
+
}
|
|
1609
|
+
try {
|
|
1610
|
+
return await callback();
|
|
1611
|
+
} finally {
|
|
1612
|
+
await state.releaseLock(lock);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeIndexEntry(value) {
|
|
1616
|
+
if (!isRecord(value)) {
|
|
1617
|
+
return void 0;
|
|
1618
|
+
}
|
|
1619
|
+
const conversationId = toOptionalString(value.conversationId);
|
|
1620
|
+
const score = toOptionalNumber(value.score);
|
|
1621
|
+
if (!conversationId || typeof score !== "number") {
|
|
1622
|
+
return void 0;
|
|
1623
|
+
}
|
|
1624
|
+
return { conversationId, score };
|
|
1625
|
+
}
|
|
1626
|
+
function uniqueIndexEntries(value) {
|
|
1627
|
+
if (!Array.isArray(value)) {
|
|
1628
|
+
return [];
|
|
1629
|
+
}
|
|
1630
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1631
|
+
for (const item of value) {
|
|
1632
|
+
const entry = normalizeIndexEntry(item);
|
|
1633
|
+
if (!entry) {
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
const existing = entries.get(entry.conversationId);
|
|
1637
|
+
if (!existing || entry.score > existing.score) {
|
|
1638
|
+
entries.set(entry.conversationId, entry);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return [...entries.values()];
|
|
1642
|
+
}
|
|
1643
|
+
function retainedIndexEntries(indexKey, entries) {
|
|
1644
|
+
if (indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY) {
|
|
1645
|
+
return entries.sort(compareIndexDescending).slice(0, CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH);
|
|
1646
|
+
}
|
|
1647
|
+
if (indexKey === CONVERSATION_ACTIVE_INDEX_KEY) {
|
|
1648
|
+
return entries.sort(compareIndexAscending);
|
|
1649
|
+
}
|
|
1650
|
+
throw new Error(`Unknown conversation index ${indexKey}`);
|
|
1651
|
+
}
|
|
1652
|
+
function redisIndexKey(indexKey) {
|
|
1653
|
+
const prefix = getChatConfig().state.keyPrefix;
|
|
1654
|
+
return [...prefix ? [prefix] : [], indexKey].join(":");
|
|
1655
|
+
}
|
|
1656
|
+
function parseRedisIndexEntries(values) {
|
|
1657
|
+
if (!Array.isArray(values)) {
|
|
1658
|
+
return [];
|
|
1659
|
+
}
|
|
1660
|
+
const entries = [];
|
|
1661
|
+
for (let index = 0; index < values.length; index += 2) {
|
|
1662
|
+
const conversationId = toOptionalString(values[index]);
|
|
1663
|
+
const score = typeof values[index + 1] === "number" ? values[index + 1] : Number(values[index + 1]);
|
|
1664
|
+
if (!conversationId || !Number.isFinite(score)) {
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
entries.push({ conversationId, score });
|
|
1668
|
+
}
|
|
1669
|
+
return entries;
|
|
1670
|
+
}
|
|
1671
|
+
function redisConversationIndexStore(client) {
|
|
1672
|
+
const upsertBoundedActivityScript = `
|
|
1673
|
+
redis.call("ZADD", KEYS[1], ARGV[1], ARGV[2])
|
|
1674
|
+
redis.call("PEXPIRE", KEYS[1], ARGV[3])
|
|
1675
|
+
local extra = redis.call("ZCARD", KEYS[1]) - tonumber(ARGV[4])
|
|
1676
|
+
if extra > 0 then
|
|
1677
|
+
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, extra - 1)
|
|
1678
|
+
end
|
|
1679
|
+
return 1
|
|
1680
|
+
`;
|
|
1681
|
+
return {
|
|
1682
|
+
async list(args) {
|
|
1683
|
+
const key = redisIndexKey(args.indexKey);
|
|
1684
|
+
const limit = args.limit;
|
|
1685
|
+
const offset = Math.max(0, args.offset ?? 0);
|
|
1686
|
+
if (limit === 0) {
|
|
1687
|
+
return [];
|
|
1688
|
+
}
|
|
1689
|
+
const values = args.scoreMax !== void 0 ? await client.sendCommand([
|
|
1690
|
+
"ZRANGEBYSCORE",
|
|
1691
|
+
key,
|
|
1692
|
+
"-inf",
|
|
1693
|
+
String(args.scoreMax),
|
|
1694
|
+
"WITHSCORES",
|
|
1695
|
+
...limit !== void 0 || offset > 0 ? ["LIMIT", String(offset), String(limit ?? 1e9)] : []
|
|
1696
|
+
]) : await client.sendCommand([
|
|
1697
|
+
args.order === "asc" ? "ZRANGE" : "ZREVRANGE",
|
|
1698
|
+
key,
|
|
1699
|
+
String(offset),
|
|
1700
|
+
String(
|
|
1701
|
+
limit === void 0 ? -1 : offset + Math.max(0, limit - 1)
|
|
1702
|
+
),
|
|
1703
|
+
"WITHSCORES"
|
|
1704
|
+
]);
|
|
1705
|
+
return parseRedisIndexEntries(values);
|
|
1706
|
+
},
|
|
1707
|
+
async remove(args) {
|
|
1708
|
+
await client.sendCommand([
|
|
1709
|
+
"ZREM",
|
|
1710
|
+
redisIndexKey(args.indexKey),
|
|
1711
|
+
args.conversationId
|
|
1712
|
+
]);
|
|
1713
|
+
},
|
|
1714
|
+
async upsert(args) {
|
|
1715
|
+
const key = redisIndexKey(args.indexKey);
|
|
1716
|
+
if (args.indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY) {
|
|
1717
|
+
await client.sendCommand([
|
|
1718
|
+
"EVAL",
|
|
1719
|
+
upsertBoundedActivityScript,
|
|
1720
|
+
"1",
|
|
1721
|
+
key,
|
|
1722
|
+
String(args.score),
|
|
1723
|
+
args.conversationId,
|
|
1724
|
+
String(JUNIOR_THREAD_STATE_TTL_MS),
|
|
1725
|
+
String(CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH)
|
|
1726
|
+
]);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (args.indexKey === CONVERSATION_ACTIVE_INDEX_KEY) {
|
|
1730
|
+
await client.sendCommand([
|
|
1731
|
+
"ZADD",
|
|
1732
|
+
key,
|
|
1733
|
+
String(args.score),
|
|
1734
|
+
args.conversationId
|
|
1735
|
+
]);
|
|
1736
|
+
await client.sendCommand([
|
|
1737
|
+
"PEXPIRE",
|
|
1738
|
+
key,
|
|
1739
|
+
String(JUNIOR_THREAD_STATE_TTL_MS)
|
|
1740
|
+
]);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
throw new Error(`Unknown conversation index ${args.indexKey}`);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function emulatedConversationIndexStore(state) {
|
|
1748
|
+
const readIndex = async (indexKey) => uniqueIndexEntries(await state.get(indexKey));
|
|
1749
|
+
const writeIndex = async (indexKey, entries) => {
|
|
1750
|
+
await state.set(indexKey, entries, JUNIOR_THREAD_STATE_TTL_MS);
|
|
1751
|
+
};
|
|
1752
|
+
return {
|
|
1753
|
+
async list(args) {
|
|
1754
|
+
const entries = (await readIndex(args.indexKey)).filter(
|
|
1755
|
+
(entry) => args.scoreMax === void 0 ? true : entry.score <= args.scoreMax
|
|
1756
|
+
).sort(
|
|
1757
|
+
args.order === "asc" ? compareIndexAscending : compareIndexDescending
|
|
1758
|
+
);
|
|
1759
|
+
const offset = Math.max(0, args.offset ?? 0);
|
|
1760
|
+
return entries.slice(
|
|
1761
|
+
offset,
|
|
1762
|
+
args.limit === void 0 ? entries.length : offset + args.limit
|
|
1763
|
+
);
|
|
1764
|
+
},
|
|
1765
|
+
async remove(args) {
|
|
1766
|
+
await withIndexLock(state, args.indexKey, async () => {
|
|
1767
|
+
const entries = await readIndex(args.indexKey);
|
|
1768
|
+
const next = entries.filter(
|
|
1769
|
+
(entry) => entry.conversationId !== args.conversationId
|
|
1770
|
+
);
|
|
1771
|
+
if (next.length === entries.length) {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
await writeIndex(args.indexKey, next);
|
|
1775
|
+
});
|
|
1776
|
+
},
|
|
1777
|
+
async upsert(args) {
|
|
1778
|
+
await withIndexLock(state, args.indexKey, async () => {
|
|
1779
|
+
const entries = await readIndex(args.indexKey);
|
|
1780
|
+
const withoutCurrent = entries.filter(
|
|
1781
|
+
(entry) => entry.conversationId !== args.conversationId
|
|
1782
|
+
);
|
|
1783
|
+
const next = retainedIndexEntries(args.indexKey, [
|
|
1784
|
+
...withoutCurrent,
|
|
1785
|
+
{ conversationId: args.conversationId, score: args.score }
|
|
1786
|
+
]);
|
|
1787
|
+
await writeIndex(args.indexKey, next);
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
async function getConversationIndexStore(state) {
|
|
1793
|
+
const redisStateAdapter = await getDefaultRedisStateAdapterFor(state);
|
|
1794
|
+
if (redisStateAdapter) {
|
|
1795
|
+
return redisConversationIndexStore(redisStateAdapter.getClient());
|
|
1796
|
+
}
|
|
1797
|
+
return emulatedConversationIndexStore(state);
|
|
1798
|
+
}
|
|
1799
|
+
async function upsertIndexEntry(args) {
|
|
1800
|
+
const index = await getConversationIndexStore(args.state);
|
|
1801
|
+
await index.upsert({
|
|
1802
|
+
conversationId: args.conversationId,
|
|
1803
|
+
indexKey: args.indexKey,
|
|
1804
|
+
score: args.score
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
async function removeIndexEntry(args) {
|
|
1808
|
+
const index = await getConversationIndexStore(args.state);
|
|
1809
|
+
await index.remove({
|
|
1810
|
+
conversationId: args.conversationId,
|
|
1811
|
+
indexKey: args.indexKey
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
async function acquireMutationLock(state, conversationId) {
|
|
1815
|
+
const startedAtMs = now();
|
|
1816
|
+
while (true) {
|
|
1817
|
+
const lock = await state.acquireLock(
|
|
1818
|
+
mutationLockKey(conversationId),
|
|
1819
|
+
CONVERSATION_MUTATION_LOCK_TTL_MS
|
|
1820
|
+
);
|
|
1821
|
+
if (lock) {
|
|
1822
|
+
return lock;
|
|
1823
|
+
}
|
|
1824
|
+
if (now() - startedAtMs >= CONVERSATION_MUTATION_WAIT_MS) {
|
|
1825
|
+
throw new Error(
|
|
1826
|
+
`Could not acquire conversation mutation lock for ${conversationId}`
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
await sleep2(CONVERSATION_MUTATION_RETRY_MS);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
async function withConversationMutation(args, callback) {
|
|
1833
|
+
const state = await getConnectedState(args.state);
|
|
1834
|
+
const lock = await acquireMutationLock(state, args.conversationId);
|
|
1835
|
+
try {
|
|
1836
|
+
return await callback(state, lock);
|
|
1837
|
+
} finally {
|
|
1838
|
+
await state.releaseLock(lock);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
async function readConversation(state, conversationId) {
|
|
1842
|
+
const raw = await state.get(conversationKey(conversationId));
|
|
1843
|
+
if (raw == null) {
|
|
1844
|
+
return void 0;
|
|
1845
|
+
}
|
|
1846
|
+
const conversation = normalizeConversation(conversationId, raw);
|
|
1847
|
+
if (!conversation) {
|
|
1848
|
+
throw new InvalidConversationRecordError(conversationId);
|
|
1849
|
+
}
|
|
1850
|
+
return conversation;
|
|
1851
|
+
}
|
|
1852
|
+
async function writeConversation(state, lock, conversation) {
|
|
1853
|
+
const execution = executionWithPendingMessages(
|
|
1854
|
+
conversation.execution,
|
|
1855
|
+
conversation.execution.pendingMessages
|
|
1856
|
+
);
|
|
1857
|
+
const next = {
|
|
1858
|
+
...conversation,
|
|
1859
|
+
execution
|
|
1860
|
+
};
|
|
1861
|
+
const fenced = await state.extendLock(
|
|
1862
|
+
lock,
|
|
1863
|
+
CONVERSATION_MUTATION_LOCK_TTL_MS
|
|
1864
|
+
);
|
|
1865
|
+
if (!fenced) {
|
|
1866
|
+
throw new ConversationMutationFencedError(next.conversationId);
|
|
1867
|
+
}
|
|
1868
|
+
await state.set(
|
|
1869
|
+
conversationKey(next.conversationId),
|
|
1870
|
+
next,
|
|
1871
|
+
JUNIOR_THREAD_STATE_TTL_MS
|
|
1872
|
+
);
|
|
1873
|
+
await upsertIndexEntry({
|
|
1874
|
+
state,
|
|
1875
|
+
indexKey: CONVERSATION_BY_ACTIVITY_INDEX_KEY,
|
|
1876
|
+
conversationId: next.conversationId,
|
|
1877
|
+
score: next.lastActivityAtMs
|
|
1878
|
+
});
|
|
1879
|
+
if (!hasRunnableWork(next)) {
|
|
1880
|
+
await removeIndexEntry({
|
|
1881
|
+
state,
|
|
1882
|
+
indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
|
|
1883
|
+
conversationId: next.conversationId
|
|
1884
|
+
});
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
await upsertIndexEntry({
|
|
1888
|
+
state,
|
|
1889
|
+
indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
|
|
1890
|
+
conversationId: next.conversationId,
|
|
1891
|
+
score: next.execution.updatedAtMs ?? next.updatedAtMs
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
function assertSameConversationDestination(args) {
|
|
1895
|
+
if (!args.current || sameDestination(args.current, args.next)) {
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
throw new Error(
|
|
1899
|
+
`Conversation destination changed for ${args.conversationId}`
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
function conversationWorkState(conversation) {
|
|
1903
|
+
const lease = conversation.execution.lease;
|
|
1904
|
+
return {
|
|
1905
|
+
...conversation,
|
|
1906
|
+
lastEnqueuedAtMs: conversation.execution.lastEnqueuedAtMs,
|
|
1907
|
+
...lease ? {
|
|
1908
|
+
lease: {
|
|
1909
|
+
acquiredAtMs: lease.acquiredAtMs,
|
|
1910
|
+
lastCheckInAtMs: lease.lastCheckInAtMs,
|
|
1911
|
+
leaseExpiresAtMs: lease.expiresAtMs,
|
|
1912
|
+
leaseToken: lease.token
|
|
1913
|
+
}
|
|
1914
|
+
} : {},
|
|
1915
|
+
messages: pendingMessages(conversation),
|
|
1916
|
+
needsRun: hasRunnableWork(conversation)
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
async function getConversation(args) {
|
|
1920
|
+
const state = await getConnectedState(args.state);
|
|
1921
|
+
return await readConversation(state, args.conversationId);
|
|
1922
|
+
}
|
|
1923
|
+
async function getConversationWorkState(args) {
|
|
1924
|
+
const conversation = await getConversation(args);
|
|
1925
|
+
return conversation ? conversationWorkState(conversation) : void 0;
|
|
1926
|
+
}
|
|
1927
|
+
async function appendInboundMessage(args) {
|
|
1928
|
+
const nowMs = args.nowMs ?? now();
|
|
1929
|
+
return await withConversationMutation(
|
|
1930
|
+
{ conversationId: args.message.conversationId, state: args.state },
|
|
1931
|
+
async (state, lock) => {
|
|
1932
|
+
const current = await readConversation(state, args.message.conversationId) ?? emptyConversation({
|
|
1933
|
+
conversationId: args.message.conversationId,
|
|
1934
|
+
destination: args.message.destination,
|
|
1935
|
+
nowMs,
|
|
1936
|
+
source: args.message.source
|
|
1937
|
+
});
|
|
1938
|
+
assertSameConversationDestination({
|
|
1939
|
+
conversationId: args.message.conversationId,
|
|
1940
|
+
current: current.destination,
|
|
1941
|
+
next: args.message.destination
|
|
1942
|
+
});
|
|
1943
|
+
const existingPending = current.execution.pendingMessages.some(
|
|
1944
|
+
(message) => message.inboundMessageId === args.message.inboundMessageId
|
|
1945
|
+
);
|
|
1946
|
+
const existing = current.execution.inboundMessageIds.includes(
|
|
1947
|
+
args.message.inboundMessageId
|
|
1948
|
+
);
|
|
1949
|
+
if (existing) {
|
|
1950
|
+
if (!existingPending) {
|
|
1951
|
+
return { status: "duplicate" };
|
|
1952
|
+
}
|
|
1953
|
+
const nextStatus = current.execution.status === "idle" ? "pending" : current.execution.status;
|
|
1954
|
+
await writeConversation(
|
|
1955
|
+
state,
|
|
1956
|
+
lock,
|
|
1957
|
+
withExecutionUpdate(
|
|
1958
|
+
current,
|
|
1959
|
+
{
|
|
1960
|
+
...current.execution,
|
|
1961
|
+
status: nextStatus,
|
|
1962
|
+
pendingMessages: current.execution.pendingMessages.map(
|
|
1963
|
+
(message) => message.inboundMessageId === args.message.inboundMessageId ? upgradedPendingMessage(message, args.message) : message
|
|
1964
|
+
)
|
|
1965
|
+
},
|
|
1966
|
+
nowMs
|
|
1967
|
+
)
|
|
1968
|
+
);
|
|
1969
|
+
return { status: "duplicate" };
|
|
1970
|
+
}
|
|
1971
|
+
const status = current.execution.lease && current.execution.status === "running" ? "running" : current.execution.lease ? "awaiting_resume" : "pending";
|
|
1972
|
+
const next = {
|
|
1973
|
+
...current,
|
|
1974
|
+
destination: current.destination ?? args.message.destination,
|
|
1975
|
+
source: current.source ?? args.message.source,
|
|
1976
|
+
lastActivityAtMs: nowMs
|
|
1977
|
+
};
|
|
1978
|
+
await writeConversation(
|
|
1979
|
+
state,
|
|
1980
|
+
lock,
|
|
1981
|
+
withExecutionUpdate(
|
|
1982
|
+
next,
|
|
1983
|
+
{
|
|
1984
|
+
...current.execution,
|
|
1985
|
+
status,
|
|
1986
|
+
inboundMessageIds: [
|
|
1987
|
+
...current.execution.inboundMessageIds,
|
|
1988
|
+
args.message.inboundMessageId
|
|
1989
|
+
],
|
|
1990
|
+
pendingMessages: [
|
|
1991
|
+
...current.execution.pendingMessages,
|
|
1992
|
+
args.message
|
|
1993
|
+
].sort(compareMessages)
|
|
1994
|
+
},
|
|
1995
|
+
nowMs
|
|
1996
|
+
)
|
|
1997
|
+
);
|
|
1998
|
+
return { status: "appended" };
|
|
1999
|
+
}
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
async function requestConversationWork(args) {
|
|
2003
|
+
const nowMs = args.nowMs ?? now();
|
|
2004
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2005
|
+
const existing = await readConversation(state, args.conversationId);
|
|
2006
|
+
if (existing) {
|
|
2007
|
+
assertSameConversationDestination({
|
|
2008
|
+
conversationId: args.conversationId,
|
|
2009
|
+
current: existing.destination,
|
|
2010
|
+
next: args.destination
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
const current = existing ?? emptyConversation({
|
|
2014
|
+
conversationId: args.conversationId,
|
|
2015
|
+
destination: args.destination,
|
|
2016
|
+
nowMs
|
|
2017
|
+
});
|
|
2018
|
+
const status = current.execution.lease ? "awaiting_resume" : "pending";
|
|
2019
|
+
await writeConversation(
|
|
2020
|
+
state,
|
|
2021
|
+
lock,
|
|
2022
|
+
withExecutionUpdate(
|
|
2023
|
+
{
|
|
2024
|
+
...current,
|
|
2025
|
+
destination: current.destination ?? args.destination
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
...current.execution,
|
|
2029
|
+
status
|
|
2030
|
+
},
|
|
2031
|
+
nowMs
|
|
2032
|
+
)
|
|
2033
|
+
);
|
|
2034
|
+
return { status: existing === void 0 ? "created" : "updated" };
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
async function markConversationWorkEnqueued(args) {
|
|
2038
|
+
const nowMs = args.nowMs ?? now();
|
|
2039
|
+
await withConversationMutation(args, async (state, lock) => {
|
|
2040
|
+
const current = await readConversation(state, args.conversationId);
|
|
2041
|
+
if (!current) {
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
await writeConversation(
|
|
2045
|
+
state,
|
|
2046
|
+
lock,
|
|
2047
|
+
withExecutionUpdate(
|
|
2048
|
+
current,
|
|
2049
|
+
{
|
|
2050
|
+
...current.execution,
|
|
2051
|
+
lastEnqueuedAtMs: nowMs
|
|
2052
|
+
},
|
|
2053
|
+
nowMs
|
|
2054
|
+
)
|
|
2055
|
+
);
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
async function clearConsumedConversationWake(args) {
|
|
2059
|
+
const nowMs = args.nowMs ?? now();
|
|
2060
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2061
|
+
const current = await readConversation(state, args.conversationId);
|
|
2062
|
+
if (!current || hasRunnableWork(current) || current.execution.lastEnqueuedAtMs === void 0) {
|
|
2063
|
+
return false;
|
|
2064
|
+
}
|
|
2065
|
+
await writeConversation(
|
|
2066
|
+
state,
|
|
2067
|
+
lock,
|
|
2068
|
+
withExecutionUpdate(
|
|
2069
|
+
current,
|
|
2070
|
+
{
|
|
2071
|
+
...current.execution,
|
|
2072
|
+
lastEnqueuedAtMs: void 0
|
|
2073
|
+
},
|
|
2074
|
+
nowMs
|
|
2075
|
+
)
|
|
2076
|
+
);
|
|
2077
|
+
return true;
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
async function startConversationWork(args) {
|
|
2081
|
+
const nowMs = args.nowMs ?? now();
|
|
2082
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2083
|
+
const current = await readConversation(state, args.conversationId);
|
|
2084
|
+
if (!current) {
|
|
2085
|
+
return { status: "no_work" };
|
|
2086
|
+
}
|
|
2087
|
+
if (isLeaseActive(current.execution.lease, nowMs)) {
|
|
2088
|
+
return {
|
|
2089
|
+
status: "active",
|
|
2090
|
+
leaseExpiresAtMs: current.execution.lease.expiresAtMs
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
if (!hasRunnableWork(current)) {
|
|
2094
|
+
return { status: "no_work" };
|
|
2095
|
+
}
|
|
2096
|
+
const lease = {
|
|
2097
|
+
token: randomUUID(),
|
|
2098
|
+
acquiredAtMs: nowMs,
|
|
2099
|
+
lastCheckInAtMs: nowMs,
|
|
2100
|
+
expiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
|
|
2101
|
+
};
|
|
2102
|
+
await writeConversation(
|
|
2103
|
+
state,
|
|
2104
|
+
lock,
|
|
2105
|
+
withExecutionUpdate(
|
|
2106
|
+
current,
|
|
2107
|
+
{
|
|
2108
|
+
...current.execution,
|
|
2109
|
+
lease,
|
|
2110
|
+
status: "running",
|
|
2111
|
+
runId: current.execution.runId ?? randomUUID(),
|
|
2112
|
+
lastEnqueuedAtMs: void 0
|
|
2113
|
+
},
|
|
2114
|
+
nowMs
|
|
2115
|
+
)
|
|
2116
|
+
);
|
|
2117
|
+
return {
|
|
2118
|
+
status: "acquired",
|
|
2119
|
+
leaseToken: lease.token,
|
|
2120
|
+
leaseExpiresAtMs: lease.expiresAtMs
|
|
2121
|
+
};
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
async function checkInConversationWork(args) {
|
|
2125
|
+
const nowMs = args.nowMs ?? now();
|
|
2126
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2127
|
+
const current = await readConversation(state, args.conversationId);
|
|
2128
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
await writeConversation(
|
|
2132
|
+
state,
|
|
2133
|
+
lock,
|
|
2134
|
+
withExecutionUpdate(
|
|
2135
|
+
current,
|
|
2136
|
+
{
|
|
2137
|
+
...current.execution,
|
|
2138
|
+
lease: {
|
|
2139
|
+
...current.execution.lease,
|
|
2140
|
+
lastCheckInAtMs: nowMs,
|
|
2141
|
+
expiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
|
|
2142
|
+
}
|
|
2143
|
+
},
|
|
2144
|
+
nowMs
|
|
2145
|
+
)
|
|
2146
|
+
);
|
|
2147
|
+
return true;
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
async function drainConversationMailbox(args) {
|
|
2151
|
+
const nowMs = args.nowMs ?? now();
|
|
2152
|
+
const pending = await withConversationMutation(args, async (state) => {
|
|
2153
|
+
const current = await readConversation(state, args.conversationId);
|
|
2154
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2155
|
+
throw new Error(
|
|
2156
|
+
`Conversation lease is not held for ${args.conversationId}`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
return pendingMessages(current);
|
|
2160
|
+
});
|
|
2161
|
+
if (pending.length === 0) {
|
|
2162
|
+
return [];
|
|
2163
|
+
}
|
|
2164
|
+
const acknowledgedIds = await args.handle(pending);
|
|
2165
|
+
const pendingIds = new Set(
|
|
2166
|
+
pending.map((message) => message.inboundMessageId)
|
|
2167
|
+
);
|
|
2168
|
+
for (const inboundMessageId of acknowledgedIds ?? []) {
|
|
2169
|
+
if (!pendingIds.has(inboundMessageId)) {
|
|
2170
|
+
throw new Error(
|
|
2171
|
+
`Conversation mailbox acknowledgement is not pending for ${args.conversationId}`
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
const drainedIds = new Set(
|
|
2176
|
+
acknowledgedIds ?? pending.map((message) => message.inboundMessageId)
|
|
2177
|
+
);
|
|
2178
|
+
await withConversationMutation(args, async (state, lock) => {
|
|
2179
|
+
const current = await readConversation(state, args.conversationId);
|
|
2180
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2181
|
+
throw new Error(
|
|
2182
|
+
`Conversation lease is not held for ${args.conversationId}`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
const pendingMessages2 = current.execution.pendingMessages.filter(
|
|
2186
|
+
(message) => !drainedIds.has(message.inboundMessageId)
|
|
2187
|
+
);
|
|
2188
|
+
await writeConversation(
|
|
2189
|
+
state,
|
|
2190
|
+
lock,
|
|
2191
|
+
withExecutionUpdate(
|
|
2192
|
+
current,
|
|
2193
|
+
{
|
|
2194
|
+
...current.execution,
|
|
2195
|
+
status: current.execution.status === "pending" && pendingMessages2.length === 0 ? "running" : current.execution.status,
|
|
2196
|
+
pendingMessages: pendingMessages2
|
|
2197
|
+
},
|
|
2198
|
+
nowMs
|
|
2199
|
+
)
|
|
2200
|
+
);
|
|
2201
|
+
});
|
|
2202
|
+
return pending.filter((message) => drainedIds.has(message.inboundMessageId));
|
|
2203
|
+
}
|
|
2204
|
+
async function ackMessages(args) {
|
|
2205
|
+
const nowMs = args.nowMs ?? now();
|
|
2206
|
+
const inboundMessageIds = new Set(args.inboundMessageIds);
|
|
2207
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2208
|
+
const current = await readConversation(state, args.conversationId);
|
|
2209
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
if (inboundMessageIds.size === 0) {
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
const pendingMessages2 = current.execution.pendingMessages.filter(
|
|
2216
|
+
(message) => !inboundMessageIds.has(message.inboundMessageId)
|
|
2217
|
+
);
|
|
2218
|
+
if (pendingMessages2.length === current.execution.pendingMessages.length) {
|
|
2219
|
+
return true;
|
|
2220
|
+
}
|
|
2221
|
+
await writeConversation(
|
|
2222
|
+
state,
|
|
2223
|
+
lock,
|
|
2224
|
+
withExecutionUpdate(
|
|
2225
|
+
current,
|
|
2226
|
+
{
|
|
2227
|
+
...current.execution,
|
|
2228
|
+
pendingMessages: pendingMessages2
|
|
2229
|
+
},
|
|
2230
|
+
nowMs
|
|
2231
|
+
)
|
|
2232
|
+
);
|
|
2233
|
+
return true;
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
async function requestConversationContinuation(args) {
|
|
2237
|
+
const nowMs = args.nowMs ?? now();
|
|
2238
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2239
|
+
const current = await readConversation(state, args.conversationId);
|
|
2240
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
assertSameConversationDestination({
|
|
2244
|
+
conversationId: args.conversationId,
|
|
2245
|
+
current: current.destination,
|
|
2246
|
+
next: args.destination
|
|
1258
2247
|
});
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2248
|
+
await writeConversation(
|
|
2249
|
+
state,
|
|
2250
|
+
lock,
|
|
2251
|
+
withExecutionUpdate(
|
|
2252
|
+
current,
|
|
2253
|
+
{
|
|
2254
|
+
...current.execution,
|
|
2255
|
+
status: "awaiting_resume"
|
|
2256
|
+
},
|
|
2257
|
+
nowMs
|
|
2258
|
+
)
|
|
2259
|
+
);
|
|
2260
|
+
return true;
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
async function releaseConversationWork(args) {
|
|
2264
|
+
const nowMs = args.nowMs ?? now();
|
|
2265
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2266
|
+
const current = await readConversation(state, args.conversationId);
|
|
2267
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2268
|
+
return false;
|
|
1274
2269
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
2270
|
+
await writeConversation(
|
|
2271
|
+
state,
|
|
2272
|
+
lock,
|
|
2273
|
+
withExecutionUpdate(
|
|
2274
|
+
current,
|
|
2275
|
+
{
|
|
2276
|
+
...current.execution,
|
|
2277
|
+
lease: void 0,
|
|
2278
|
+
status: current.execution.status === "running" ? "pending" : current.execution.status
|
|
2279
|
+
},
|
|
2280
|
+
nowMs
|
|
2281
|
+
)
|
|
2282
|
+
);
|
|
2283
|
+
return true;
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
async function completeConversationWork(args) {
|
|
2287
|
+
const nowMs = args.nowMs ?? now();
|
|
2288
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2289
|
+
const current = await readConversation(state, args.conversationId);
|
|
2290
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2291
|
+
return "lost_lease";
|
|
2292
|
+
}
|
|
2293
|
+
const hasPending = pendingMessages(current).length > 0;
|
|
2294
|
+
const needsRun = current.execution.status === "awaiting_resume";
|
|
2295
|
+
const runnable = needsRun || hasPending;
|
|
2296
|
+
await writeConversation(
|
|
2297
|
+
state,
|
|
2298
|
+
lock,
|
|
2299
|
+
withExecutionUpdate(
|
|
2300
|
+
current,
|
|
2301
|
+
{
|
|
2302
|
+
...current.execution,
|
|
2303
|
+
lease: void 0,
|
|
2304
|
+
status: runnable ? "pending" : "idle",
|
|
2305
|
+
runId: runnable ? current.execution.runId : void 0
|
|
2306
|
+
},
|
|
2307
|
+
nowMs
|
|
2308
|
+
)
|
|
2309
|
+
);
|
|
2310
|
+
return runnable ? "pending" : "completed";
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
async function recordAttemptFailure(args) {
|
|
2314
|
+
const nowMs = args.nowMs ?? now();
|
|
2315
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2316
|
+
const current = await readConversation(state, args.conversationId);
|
|
2317
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2318
|
+
return {
|
|
2319
|
+
status: "lost_lease",
|
|
2320
|
+
pendingCount: 0,
|
|
2321
|
+
deadLetteredMessages: []
|
|
2322
|
+
};
|
|
1292
2323
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
2324
|
+
const pendingIds = new Set(
|
|
2325
|
+
current.execution.pendingMessages.map(
|
|
2326
|
+
(message) => message.inboundMessageId
|
|
2327
|
+
)
|
|
2328
|
+
);
|
|
2329
|
+
if (args.inboundMessageIds.length === 0 || args.inboundMessageIds.some((id) => !pendingIds.has(id))) {
|
|
2330
|
+
return {
|
|
2331
|
+
status: "skipped",
|
|
2332
|
+
pendingCount: current.execution.pendingMessages.length,
|
|
2333
|
+
deadLetteredMessages: []
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
const attemptedIds = new Set(args.inboundMessageIds);
|
|
2337
|
+
const deadLetteredMessages = [];
|
|
2338
|
+
const pendingMessages2 = [];
|
|
2339
|
+
for (const message of current.execution.pendingMessages) {
|
|
2340
|
+
if (!attemptedIds.has(message.inboundMessageId)) {
|
|
2341
|
+
pendingMessages2.push(message);
|
|
2342
|
+
continue;
|
|
1302
2343
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
2344
|
+
const attempted = {
|
|
2345
|
+
...message,
|
|
2346
|
+
attemptCount: (message.attemptCount ?? 0) + 1
|
|
2347
|
+
};
|
|
2348
|
+
if (attempted.attemptCount >= CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS) {
|
|
2349
|
+
deadLetteredMessages.push(attempted);
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
pendingMessages2.push(attempted);
|
|
1309
2353
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
2354
|
+
await writeConversation(
|
|
2355
|
+
state,
|
|
2356
|
+
lock,
|
|
2357
|
+
withExecutionUpdate(
|
|
2358
|
+
current,
|
|
2359
|
+
{
|
|
2360
|
+
...current.execution,
|
|
2361
|
+
pendingMessages: pendingMessages2
|
|
2362
|
+
},
|
|
2363
|
+
nowMs
|
|
2364
|
+
)
|
|
1319
2365
|
);
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
2366
|
+
return {
|
|
2367
|
+
status: "recorded",
|
|
2368
|
+
pendingCount: pendingMessages2.length,
|
|
2369
|
+
deadLetteredMessages
|
|
2370
|
+
};
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
async function deadLetterAttempt(args) {
|
|
2374
|
+
const nowMs = args.nowMs ?? now();
|
|
2375
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2376
|
+
const current = await readConversation(state, args.conversationId);
|
|
2377
|
+
if (!current || current.execution.lease?.token !== args.leaseToken) {
|
|
2378
|
+
return "lost_lease";
|
|
1329
2379
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
2380
|
+
const runnable = pendingMessages(current).length > 0;
|
|
2381
|
+
await writeConversation(
|
|
2382
|
+
state,
|
|
2383
|
+
lock,
|
|
2384
|
+
withExecutionUpdate(
|
|
2385
|
+
current,
|
|
2386
|
+
{
|
|
2387
|
+
...current.execution,
|
|
2388
|
+
lease: void 0,
|
|
2389
|
+
status: runnable ? "pending" : "failed",
|
|
2390
|
+
runId: runnable ? current.execution.runId : void 0
|
|
2391
|
+
},
|
|
2392
|
+
nowMs
|
|
2393
|
+
)
|
|
2394
|
+
);
|
|
2395
|
+
return runnable ? "pending" : "failed";
|
|
2396
|
+
});
|
|
1338
2397
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
2398
|
+
async function clearExpiredConversationLease(args) {
|
|
2399
|
+
const nowMs = args.nowMs ?? now();
|
|
2400
|
+
return await withConversationMutation(args, async (state, lock) => {
|
|
2401
|
+
const current = await readConversation(state, args.conversationId);
|
|
2402
|
+
if (!current?.execution.lease || current.execution.lease.expiresAtMs > nowMs) {
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
await writeConversation(
|
|
2406
|
+
state,
|
|
2407
|
+
lock,
|
|
2408
|
+
withExecutionUpdate(
|
|
2409
|
+
current,
|
|
2410
|
+
{
|
|
2411
|
+
...current.execution,
|
|
2412
|
+
lease: void 0,
|
|
2413
|
+
status: "pending"
|
|
2414
|
+
},
|
|
2415
|
+
nowMs
|
|
2416
|
+
)
|
|
2417
|
+
);
|
|
2418
|
+
return true;
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
async function removeActiveConversation(args) {
|
|
2422
|
+
const state = await getConnectedState(args.state);
|
|
2423
|
+
await removeIndexEntry({
|
|
2424
|
+
state,
|
|
2425
|
+
indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
|
|
2426
|
+
conversationId: args.conversationId
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
async function listActiveConversationIds(args = {}) {
|
|
2430
|
+
const state = await getConnectedState(args.state);
|
|
2431
|
+
const index = await getConversationIndexStore(state);
|
|
2432
|
+
const entries = await index.list({
|
|
2433
|
+
indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
|
|
2434
|
+
limit: args.limit,
|
|
2435
|
+
order: "asc",
|
|
2436
|
+
scoreMax: args.staleBeforeMs
|
|
2437
|
+
});
|
|
2438
|
+
return entries.map((entry) => entry.conversationId);
|
|
1360
2439
|
}
|
|
1361
|
-
|
|
1362
|
-
// src/handlers/heartbeat.ts
|
|
1363
|
-
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1364
2440
|
|
|
1365
2441
|
// src/chat/task-execution/store.ts
|
|
1366
2442
|
function metadataStore(options) {
|
|
@@ -1373,7 +2449,7 @@ function hasRecentEnqueueMarker(conversation, nowMs) {
|
|
|
1373
2449
|
const lastEnqueuedAtMs = conversation.execution.lastEnqueuedAtMs;
|
|
1374
2450
|
return typeof lastEnqueuedAtMs === "number" && lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > nowMs;
|
|
1375
2451
|
}
|
|
1376
|
-
function
|
|
2452
|
+
function now2() {
|
|
1377
2453
|
return Date.now();
|
|
1378
2454
|
}
|
|
1379
2455
|
async function recordExecutionMetadata(args) {
|
|
@@ -1414,7 +2490,7 @@ function hasRunnableConversationWork(conversation) {
|
|
|
1414
2490
|
return conversation.execution.status !== "idle" || countPendingConversationMessages(conversation) > 0;
|
|
1415
2491
|
}
|
|
1416
2492
|
async function ensureConversationWake(args) {
|
|
1417
|
-
const nowMs = args.nowMs ??
|
|
2493
|
+
const nowMs = args.nowMs ?? now2();
|
|
1418
2494
|
const conversation = await getConversation({
|
|
1419
2495
|
conversationId: args.conversationId,
|
|
1420
2496
|
state: args.state
|
|
@@ -1447,7 +2523,7 @@ async function ensureConversationWake(args) {
|
|
|
1447
2523
|
return { status: "enqueued", queueMessageId: queueResult?.messageId };
|
|
1448
2524
|
}
|
|
1449
2525
|
async function appendAndEnqueueInboundMessage(args) {
|
|
1450
|
-
const nowMs = args.nowMs ??
|
|
2526
|
+
const nowMs = args.nowMs ?? now2();
|
|
1451
2527
|
const appendResult = await appendInboundMessage({
|
|
1452
2528
|
message: args.message,
|
|
1453
2529
|
nowMs,
|
|
@@ -2445,7 +3521,7 @@ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
|
2445
3521
|
var STATUS_MIN_VISIBLE_MS = 1200;
|
|
2446
3522
|
var STATUS_ROTATION_INTERVAL_MS = 3e4;
|
|
2447
3523
|
function createAssistantStatusScheduler(args) {
|
|
2448
|
-
const
|
|
3524
|
+
const now4 = args.now ?? (() => Date.now());
|
|
2449
3525
|
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
2450
3526
|
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
2451
3527
|
const random = args.random ?? Math.random;
|
|
@@ -2509,7 +3585,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
2509
3585
|
}
|
|
2510
3586
|
currentVisibleStatus = text;
|
|
2511
3587
|
currentLoadingMessages = nextLoadingMessages;
|
|
2512
|
-
lastStatusAt =
|
|
3588
|
+
lastStatusAt = now4();
|
|
2513
3589
|
scheduleRotation();
|
|
2514
3590
|
await enqueueStatusUpdate(async () => {
|
|
2515
3591
|
await args.sendStatus(text, nextLoadingMessages);
|
|
@@ -2596,7 +3672,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
2596
3672
|
}
|
|
2597
3673
|
return;
|
|
2598
3674
|
}
|
|
2599
|
-
const elapsed =
|
|
3675
|
+
const elapsed = now4() - lastStatusAt;
|
|
2600
3676
|
const waitMs = Math.max(
|
|
2601
3677
|
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
2602
3678
|
STATUS_MIN_VISIBLE_MS - elapsed,
|
|
@@ -2994,19 +4070,14 @@ function resolveReplyTimeoutMs(explicitTimeoutMs) {
|
|
|
2994
4070
|
const parsed = Number.parseInt(raw, 10);
|
|
2995
4071
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
2996
4072
|
}
|
|
2997
|
-
async function postSlackMessageBestEffort(channelId, threadTs, text,
|
|
4073
|
+
async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId) {
|
|
2998
4074
|
try {
|
|
2999
|
-
if (
|
|
3000
|
-
await
|
|
4075
|
+
if (conversationId) {
|
|
4076
|
+
await sendSlackReply({
|
|
3001
4077
|
channelId,
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
text,
|
|
3006
|
-
stage: "thread_reply"
|
|
3007
|
-
}
|
|
3008
|
-
],
|
|
3009
|
-
footer
|
|
4078
|
+
conversationId,
|
|
4079
|
+
text,
|
|
4080
|
+
threadTs
|
|
3010
4081
|
});
|
|
3011
4082
|
return;
|
|
3012
4083
|
}
|
|
@@ -3054,10 +4125,10 @@ function getResumeLogContext(args, lockKey) {
|
|
|
3054
4125
|
const actor = routing?.actor;
|
|
3055
4126
|
return {
|
|
3056
4127
|
conversationId: args.conversationId,
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
4128
|
+
messageConversationId: lockKey,
|
|
4129
|
+
userId: isUserActor(actor) ? actor.userId : void 0,
|
|
4130
|
+
userName: isUserActor(actor) ? actor.userName : void 0,
|
|
4131
|
+
destinationName: args.channelId,
|
|
3061
4132
|
assistantUserName: botConfig.userName,
|
|
3062
4133
|
modelId: standardModelId(botConfig)
|
|
3063
4134
|
};
|
|
@@ -3321,18 +4392,18 @@ async function resumeSlackTurn(args) {
|
|
|
3321
4392
|
};
|
|
3322
4393
|
};
|
|
3323
4394
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
3324
|
-
|
|
3325
|
-
if (posts.length === 0) {
|
|
4395
|
+
if (!assistantMessage.text.trim()) {
|
|
3326
4396
|
return;
|
|
3327
4397
|
}
|
|
3328
4398
|
failureCode = "delivery_failed";
|
|
3329
4399
|
const deliveryState2 = await getDeliveryConversation();
|
|
3330
4400
|
let messageTs;
|
|
3331
4401
|
try {
|
|
3332
|
-
messageTs = await
|
|
4402
|
+
messageTs = await sendSlackReply({
|
|
3333
4403
|
channelId: runArgs.channelId,
|
|
3334
|
-
|
|
3335
|
-
|
|
4404
|
+
conversationId: runArgs.conversationId,
|
|
4405
|
+
text: assistantMessage.text,
|
|
4406
|
+
threadTs: runArgs.threadTs
|
|
3336
4407
|
});
|
|
3337
4408
|
} catch (error) {
|
|
3338
4409
|
if (isRetryableSlackPostError(error)) {
|
|
@@ -3459,16 +4530,10 @@ async function resumeSlackTurn(args) {
|
|
|
3459
4530
|
destination: replyContext.routing.destination,
|
|
3460
4531
|
source: replyContext.routing.source,
|
|
3461
4532
|
actor: resumeActor,
|
|
3462
|
-
surface: "slack"
|
|
3463
|
-
logContext: {
|
|
3464
|
-
threadId: runArgs.conversationId,
|
|
3465
|
-
actorId: isUserActor(replyContext.routing.actor) ? replyContext.routing.actor.userId : void 0,
|
|
3466
|
-
channelId: runArgs.channelId,
|
|
3467
|
-
assistantUserName: botConfig.userName
|
|
3468
|
-
}
|
|
4533
|
+
surface: "slack"
|
|
3469
4534
|
});
|
|
3470
4535
|
}
|
|
3471
|
-
await runArgs.
|
|
4536
|
+
await runArgs.commitResult?.(reply);
|
|
3472
4537
|
if (reply.diagnostics.outcome === "success") {
|
|
3473
4538
|
await turnLifecycle.complete({
|
|
3474
4539
|
conversationId: runArgs.conversationId,
|
|
@@ -3564,9 +4629,6 @@ async function resumeSlackTurn(args) {
|
|
|
3564
4629
|
try {
|
|
3565
4630
|
await deferredPauseHandler();
|
|
3566
4631
|
if (deferredAuthInfo) {
|
|
3567
|
-
const footer = buildSlackReplyFooter({
|
|
3568
|
-
conversationId: runArgs.conversationId
|
|
3569
|
-
});
|
|
3570
4632
|
await postSlackMessageBestEffort(
|
|
3571
4633
|
runArgs.channelId,
|
|
3572
4634
|
runArgs.threadTs,
|
|
@@ -3574,7 +4636,7 @@ async function resumeSlackTurn(args) {
|
|
|
3574
4636
|
deferredAuthInfo.actorId,
|
|
3575
4637
|
deferredAuthInfo.providerDisplayName
|
|
3576
4638
|
),
|
|
3577
|
-
|
|
4639
|
+
runArgs.conversationId
|
|
3578
4640
|
);
|
|
3579
4641
|
}
|
|
3580
4642
|
return true;
|
|
@@ -3977,7 +5039,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
3977
5039
|
}
|
|
3978
5040
|
}
|
|
3979
5041
|
},
|
|
3980
|
-
|
|
5042
|
+
commitResult: async (reply) => {
|
|
3981
5043
|
await persistCompletedReplyState(
|
|
3982
5044
|
authSession.channelId,
|
|
3983
5045
|
authSession.threadTs,
|
|
@@ -4575,7 +5637,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
|
|
|
4575
5637
|
}
|
|
4576
5638
|
}
|
|
4577
5639
|
},
|
|
4578
|
-
|
|
5640
|
+
commitResult: async (reply) => {
|
|
4579
5641
|
logInfo(
|
|
4580
5642
|
"oauth_callback_resume_complete",
|
|
4581
5643
|
{},
|
|
@@ -4862,9 +5924,9 @@ function buildJwksUrl(value) {
|
|
|
4862
5924
|
return url;
|
|
4863
5925
|
}
|
|
4864
5926
|
async function getJwks(issuer) {
|
|
4865
|
-
const
|
|
5927
|
+
const now4 = Date.now();
|
|
4866
5928
|
const cached = jwksByIssuer.get(issuer);
|
|
4867
|
-
if (cached && cached.expiresAtMs >
|
|
5929
|
+
if (cached && cached.expiresAtMs > now4) {
|
|
4868
5930
|
return cached.jwks;
|
|
4869
5931
|
}
|
|
4870
5932
|
if (cached) {
|
|
@@ -4889,7 +5951,7 @@ async function getJwks(issuer) {
|
|
|
4889
5951
|
}
|
|
4890
5952
|
jwksByIssuer.set(issuer, {
|
|
4891
5953
|
jwks,
|
|
4892
|
-
expiresAtMs:
|
|
5954
|
+
expiresAtMs: now4 + OIDC_DISCOVERY_CACHE_TTL_MS
|
|
4893
5955
|
});
|
|
4894
5956
|
return jwks;
|
|
4895
5957
|
}
|
|
@@ -5239,7 +6301,7 @@ function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downlo
|
|
|
5239
6301
|
|
|
5240
6302
|
// src/chat/slack/adapter-context.ts
|
|
5241
6303
|
var initializedAdapters = /* @__PURE__ */ new WeakSet();
|
|
5242
|
-
async function
|
|
6304
|
+
async function getConnectedState2(stateAdapter) {
|
|
5243
6305
|
const state = stateAdapter ?? getStateAdapter();
|
|
5244
6306
|
await state.connect();
|
|
5245
6307
|
return state;
|
|
@@ -5248,7 +6310,7 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
5248
6310
|
if (initializedAdapters.has(args.adapter)) {
|
|
5249
6311
|
return;
|
|
5250
6312
|
}
|
|
5251
|
-
const state = await
|
|
6313
|
+
const state = await getConnectedState2(args.state);
|
|
5252
6314
|
await args.adapter.initialize({
|
|
5253
6315
|
getState: () => state
|
|
5254
6316
|
});
|
|
@@ -5716,6 +6778,7 @@ function slackSerializedResourceEventMessage(input) {
|
|
|
5716
6778
|
raw: {
|
|
5717
6779
|
channel: input.channelId,
|
|
5718
6780
|
event_type: "resource_event",
|
|
6781
|
+
resource_event_type: input.eventType,
|
|
5719
6782
|
thread_ts: input.threadTs,
|
|
5720
6783
|
type: "message",
|
|
5721
6784
|
user: "UJRNEVENT"
|
|
@@ -5741,6 +6804,7 @@ function createSlackResourceEventInboundMessage(input) {
|
|
|
5741
6804
|
const timestampIso = new Date(input.event.occurredAtMs).toISOString();
|
|
5742
6805
|
const message = slackSerializedResourceEventMessage({
|
|
5743
6806
|
channelId: slack.channelId,
|
|
6807
|
+
eventType: input.event.eventType,
|
|
5744
6808
|
id: messageId,
|
|
5745
6809
|
text: input.text,
|
|
5746
6810
|
threadTs: slack.threadTs,
|
|
@@ -5781,7 +6845,7 @@ function createSlackResourceEventInboundMessage(input) {
|
|
|
5781
6845
|
}
|
|
5782
6846
|
};
|
|
5783
6847
|
}
|
|
5784
|
-
function
|
|
6848
|
+
function getConnectedState3(stateAdapter) {
|
|
5785
6849
|
return stateAdapter ?? getStateAdapter();
|
|
5786
6850
|
}
|
|
5787
6851
|
function parseSlackMetadata(value) {
|
|
@@ -5883,7 +6947,7 @@ function createSlackConversationWorker(options) {
|
|
|
5883
6947
|
return async (context) => {
|
|
5884
6948
|
const adapter = options.getSlackAdapter();
|
|
5885
6949
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
5886
|
-
const state =
|
|
6950
|
+
const state = getConnectedState3(options.state);
|
|
5887
6951
|
await state.connect();
|
|
5888
6952
|
const records = getPendingRecords({
|
|
5889
6953
|
execution: { pendingMessages: [...context.attempt.messages] }
|
|
@@ -6235,7 +7299,7 @@ async function handleUnlink(event, actorId, provider) {
|
|
|
6235
7299
|
await tokenStore.delete(actorId, provider);
|
|
6236
7300
|
logInfo(
|
|
6237
7301
|
"slash_command_unlink",
|
|
6238
|
-
{
|
|
7302
|
+
{ userId: actorId },
|
|
6239
7303
|
{ "app.credential.provider": provider },
|
|
6240
7304
|
`Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
|
|
6241
7305
|
);
|
|
@@ -6457,7 +7521,7 @@ async function handleSlackEvent(args) {
|
|
|
6457
7521
|
);
|
|
6458
7522
|
} catch (error) {
|
|
6459
7523
|
logException(error, "slack_app_home_publish_failed", {
|
|
6460
|
-
|
|
7524
|
+
userId
|
|
6461
7525
|
});
|
|
6462
7526
|
}
|
|
6463
7527
|
}
|
|
@@ -6568,7 +7632,7 @@ async function handleSlashCommandForm(args) {
|
|
|
6568
7632
|
await withSpan(
|
|
6569
7633
|
"chat.slash_command",
|
|
6570
7634
|
"chat.slash_command",
|
|
6571
|
-
{
|
|
7635
|
+
{ userId },
|
|
6572
7636
|
async () => {
|
|
6573
7637
|
await handleSlashCommand({
|
|
6574
7638
|
adapter: args.adapter,
|
|
@@ -6607,7 +7671,7 @@ async function handleInteractivePayload(args) {
|
|
|
6607
7671
|
await withSpan(
|
|
6608
7672
|
"chat.app_home_disconnect",
|
|
6609
7673
|
"chat.app_home_disconnect",
|
|
6610
|
-
{
|
|
7674
|
+
{ userId },
|
|
6611
7675
|
async () => {
|
|
6612
7676
|
try {
|
|
6613
7677
|
await unlinkProvider(userId, provider, args.userTokenStore);
|
|
@@ -6615,7 +7679,7 @@ async function handleInteractivePayload(args) {
|
|
|
6615
7679
|
logException(
|
|
6616
7680
|
error,
|
|
6617
7681
|
"app_home_disconnect_unlink_failed",
|
|
6618
|
-
{
|
|
7682
|
+
{ userId },
|
|
6619
7683
|
{ "app.credential.provider": provider }
|
|
6620
7684
|
);
|
|
6621
7685
|
}
|
|
@@ -6625,7 +7689,7 @@ async function handleInteractivePayload(args) {
|
|
|
6625
7689
|
logException(
|
|
6626
7690
|
error,
|
|
6627
7691
|
"app_home_disconnect_publish_failed",
|
|
6628
|
-
{
|
|
7692
|
+
{ userId },
|
|
6629
7693
|
{ "app.credential.provider": provider }
|
|
6630
7694
|
);
|
|
6631
7695
|
}
|
|
@@ -6668,7 +7732,7 @@ async function handleSlackForm(args) {
|
|
|
6668
7732
|
})
|
|
6669
7733
|
).catch((error) => {
|
|
6670
7734
|
logException(error, "slash_command_failed", {
|
|
6671
|
-
|
|
7735
|
+
userId: params.get("user_id") ?? void 0
|
|
6672
7736
|
});
|
|
6673
7737
|
})
|
|
6674
7738
|
);
|
|
@@ -6698,7 +7762,7 @@ async function handleSlackForm(args) {
|
|
|
6698
7762
|
})
|
|
6699
7763
|
).catch((error) => {
|
|
6700
7764
|
logException(error, "slack_interactive_payload_failed", {
|
|
6701
|
-
|
|
7765
|
+
userId: payload.user?.id?.trim() || void 0
|
|
6702
7766
|
});
|
|
6703
7767
|
})
|
|
6704
7768
|
);
|
|
@@ -6848,7 +7912,7 @@ function isConversationQueueMessageRejectedError(error) {
|
|
|
6848
7912
|
// src/chat/task-execution/worker.ts
|
|
6849
7913
|
var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
|
|
6850
7914
|
var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
|
|
6851
|
-
function
|
|
7915
|
+
function now3(options) {
|
|
6852
7916
|
return options.nowMs?.() ?? Date.now();
|
|
6853
7917
|
}
|
|
6854
7918
|
function nudgeIdempotencyKey(reason, conversationId, nowMs) {
|
|
@@ -6919,7 +7983,7 @@ function isTerminalFailure(failure) {
|
|
|
6919
7983
|
}
|
|
6920
7984
|
function startLeaseCheckIn(args) {
|
|
6921
7985
|
const timer = setInterval(() => {
|
|
6922
|
-
const nowMs =
|
|
7986
|
+
const nowMs = now3(args.options);
|
|
6923
7987
|
void checkInConversationWork2({
|
|
6924
7988
|
conversationId: args.conversationId,
|
|
6925
7989
|
leaseToken: args.leaseToken,
|
|
@@ -6975,7 +8039,7 @@ async function processConversationWork(message, options) {
|
|
|
6975
8039
|
await clearConsumedConversationWake2({
|
|
6976
8040
|
conversationId,
|
|
6977
8041
|
conversationStore: options.conversationStore,
|
|
6978
|
-
nowMs:
|
|
8042
|
+
nowMs: now3(options),
|
|
6979
8043
|
state: options.state
|
|
6980
8044
|
});
|
|
6981
8045
|
}
|
|
@@ -6992,20 +8056,20 @@ async function processConversationWork(message, options) {
|
|
|
6992
8056
|
const lease = await startConversationWork2({
|
|
6993
8057
|
conversationId,
|
|
6994
8058
|
conversationStore: options.conversationStore,
|
|
6995
|
-
nowMs:
|
|
8059
|
+
nowMs: now3(options),
|
|
6996
8060
|
state: options.state
|
|
6997
8061
|
});
|
|
6998
8062
|
if (lease.status === "no_work") {
|
|
6999
8063
|
await clearConsumedConversationWake2({
|
|
7000
8064
|
conversationId,
|
|
7001
8065
|
conversationStore: options.conversationStore,
|
|
7002
|
-
nowMs:
|
|
8066
|
+
nowMs: now3(options),
|
|
7003
8067
|
state: options.state
|
|
7004
8068
|
});
|
|
7005
8069
|
return { status: "no_work" };
|
|
7006
8070
|
}
|
|
7007
8071
|
if (lease.status === "active") {
|
|
7008
|
-
const nudgeNowMs =
|
|
8072
|
+
const nudgeNowMs = now3(options);
|
|
7009
8073
|
await ensureConversationWake({
|
|
7010
8074
|
conversationId,
|
|
7011
8075
|
conversationStore: options.conversationStore,
|
|
@@ -7026,7 +8090,7 @@ async function processConversationWork(message, options) {
|
|
|
7026
8090
|
);
|
|
7027
8091
|
return { status: "active" };
|
|
7028
8092
|
}
|
|
7029
|
-
const startedAtMs =
|
|
8093
|
+
const startedAtMs = now3(options);
|
|
7030
8094
|
const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
|
|
7031
8095
|
const leasedWork = await getConversationWorkState2({
|
|
7032
8096
|
conversationId,
|
|
@@ -7060,7 +8124,7 @@ async function processConversationWork(message, options) {
|
|
|
7060
8124
|
leaseToken: lease.leaseToken,
|
|
7061
8125
|
conversationStore: options.conversationStore,
|
|
7062
8126
|
handle,
|
|
7063
|
-
nowMs:
|
|
8127
|
+
nowMs: now3(options),
|
|
7064
8128
|
state: options.state
|
|
7065
8129
|
});
|
|
7066
8130
|
const ack = async () => {
|
|
@@ -7069,7 +8133,7 @@ async function processConversationWork(message, options) {
|
|
|
7069
8133
|
inboundMessageIds: attemptMessageIds,
|
|
7070
8134
|
leaseToken: lease.leaseToken,
|
|
7071
8135
|
conversationStore: options.conversationStore,
|
|
7072
|
-
nowMs:
|
|
8136
|
+
nowMs: now3(options),
|
|
7073
8137
|
state: options.state
|
|
7074
8138
|
});
|
|
7075
8139
|
if (!acknowledged) {
|
|
@@ -7092,13 +8156,13 @@ async function processConversationWork(message, options) {
|
|
|
7092
8156
|
},
|
|
7093
8157
|
conversationId,
|
|
7094
8158
|
destination,
|
|
7095
|
-
shouldYield: () => leaseLost ||
|
|
8159
|
+
shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
|
|
7096
8160
|
checkIn: async () => {
|
|
7097
8161
|
const checkedIn = await checkInConversationWork2({
|
|
7098
8162
|
conversationId,
|
|
7099
8163
|
leaseToken: lease.leaseToken,
|
|
7100
8164
|
conversationStore: options.conversationStore,
|
|
7101
|
-
nowMs:
|
|
8165
|
+
nowMs: now3(options),
|
|
7102
8166
|
state: options.state
|
|
7103
8167
|
});
|
|
7104
8168
|
if (!checkedIn) {
|
|
@@ -7114,7 +8178,7 @@ async function processConversationWork(message, options) {
|
|
|
7114
8178
|
conversationId,
|
|
7115
8179
|
destination,
|
|
7116
8180
|
leaseToken: lease.leaseToken,
|
|
7117
|
-
nowMs:
|
|
8181
|
+
nowMs: now3(options),
|
|
7118
8182
|
options
|
|
7119
8183
|
});
|
|
7120
8184
|
return { status: "lost_lease" };
|
|
@@ -7124,13 +8188,13 @@ async function processConversationWork(message, options) {
|
|
|
7124
8188
|
conversationId,
|
|
7125
8189
|
destination,
|
|
7126
8190
|
leaseToken: lease.leaseToken,
|
|
7127
|
-
nowMs:
|
|
8191
|
+
nowMs: now3(options),
|
|
7128
8192
|
options
|
|
7129
8193
|
});
|
|
7130
8194
|
return { status: "lost_lease" };
|
|
7131
8195
|
}
|
|
7132
8196
|
if (result.status === "yielded") {
|
|
7133
|
-
const yieldNowMs =
|
|
8197
|
+
const yieldNowMs = now3(options);
|
|
7134
8198
|
const continuationMarked = await requestConversationContinuation2({
|
|
7135
8199
|
conversationId,
|
|
7136
8200
|
destination,
|
|
@@ -7165,7 +8229,7 @@ async function processConversationWork(message, options) {
|
|
|
7165
8229
|
"conversation_work_cooperative_yield",
|
|
7166
8230
|
{ conversationId },
|
|
7167
8231
|
{
|
|
7168
|
-
"app.worker.elapsed_ms":
|
|
8232
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs,
|
|
7169
8233
|
"app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
|
|
7170
8234
|
},
|
|
7171
8235
|
"Conversation work yielded cooperatively"
|
|
@@ -7173,7 +8237,7 @@ async function processConversationWork(message, options) {
|
|
|
7173
8237
|
return { status: "yielded" };
|
|
7174
8238
|
}
|
|
7175
8239
|
if (result.status === "deferred") {
|
|
7176
|
-
const deferredNowMs =
|
|
8240
|
+
const deferredNowMs = now3(options);
|
|
7177
8241
|
const released = await releaseConversationWork2({
|
|
7178
8242
|
conversationId,
|
|
7179
8243
|
leaseToken: lease.leaseToken,
|
|
@@ -7202,7 +8266,7 @@ async function processConversationWork(message, options) {
|
|
|
7202
8266
|
const failure = await recordFailedDeliveryAttempt({
|
|
7203
8267
|
conversationId,
|
|
7204
8268
|
leaseToken: lease.leaseToken,
|
|
7205
|
-
nowMs:
|
|
8269
|
+
nowMs: now3(options),
|
|
7206
8270
|
messageIds: attemptMessageIds,
|
|
7207
8271
|
options
|
|
7208
8272
|
});
|
|
@@ -7211,7 +8275,7 @@ async function processConversationWork(message, options) {
|
|
|
7211
8275
|
conversationId,
|
|
7212
8276
|
leaseToken: lease.leaseToken,
|
|
7213
8277
|
conversationStore: options.conversationStore,
|
|
7214
|
-
nowMs:
|
|
8278
|
+
nowMs: now3(options),
|
|
7215
8279
|
state: options.state
|
|
7216
8280
|
});
|
|
7217
8281
|
return { status: "failed" };
|
|
@@ -7221,14 +8285,14 @@ async function processConversationWork(message, options) {
|
|
|
7221
8285
|
conversationId,
|
|
7222
8286
|
leaseToken: lease.leaseToken,
|
|
7223
8287
|
conversationStore: options.conversationStore,
|
|
7224
|
-
nowMs:
|
|
8288
|
+
nowMs: now3(options),
|
|
7225
8289
|
state: options.state
|
|
7226
8290
|
});
|
|
7227
8291
|
if (completion === "lost_lease") {
|
|
7228
8292
|
return { status: "lost_lease" };
|
|
7229
8293
|
}
|
|
7230
8294
|
if (completion === "pending") {
|
|
7231
|
-
const nudgeNowMs =
|
|
8295
|
+
const nudgeNowMs = now3(options);
|
|
7232
8296
|
const wake = await ensureConversationWake({
|
|
7233
8297
|
conversationId,
|
|
7234
8298
|
conversationStore: options.conversationStore,
|
|
@@ -7247,13 +8311,13 @@ async function processConversationWork(message, options) {
|
|
|
7247
8311
|
"conversation_work_completed",
|
|
7248
8312
|
{ conversationId },
|
|
7249
8313
|
{
|
|
7250
|
-
"app.worker.elapsed_ms":
|
|
8314
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs
|
|
7251
8315
|
},
|
|
7252
8316
|
"Conversation work completed"
|
|
7253
8317
|
);
|
|
7254
8318
|
return { status: "completed" };
|
|
7255
8319
|
} catch (error) {
|
|
7256
|
-
const errorNowMs =
|
|
8320
|
+
const errorNowMs = now3(options);
|
|
7257
8321
|
let recoveryRecorded = false;
|
|
7258
8322
|
try {
|
|
7259
8323
|
const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
|
|
@@ -7318,7 +8382,7 @@ async function processConversationWork(message, options) {
|
|
|
7318
8382
|
"conversation_work_failed",
|
|
7319
8383
|
{ conversationId },
|
|
7320
8384
|
{
|
|
7321
|
-
"app.worker.elapsed_ms":
|
|
8385
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs
|
|
7322
8386
|
},
|
|
7323
8387
|
"Conversation work failed"
|
|
7324
8388
|
);
|
|
@@ -7898,10 +8962,10 @@ function createAcceptedSteeringDrain(hooks, options) {
|
|
|
7898
8962
|
function buildLogContext(deps, args) {
|
|
7899
8963
|
return {
|
|
7900
8964
|
conversationId: args.threadId ?? args.runId,
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
8965
|
+
messageConversationId: args.threadId,
|
|
8966
|
+
userId: args.actorId,
|
|
8967
|
+
userName: args.actorUserName,
|
|
8968
|
+
destinationName: args.channelId,
|
|
7905
8969
|
runId: args.runId,
|
|
7906
8970
|
assistantUserName: deps.assistantUserName,
|
|
7907
8971
|
modelId: deps.modelId
|
|
@@ -8620,9 +9684,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8620
9684
|
error,
|
|
8621
9685
|
"assistant_thread_started_handler_failed",
|
|
8622
9686
|
{
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
9687
|
+
messageConversationId: event.threadId,
|
|
9688
|
+
userId: event.userId,
|
|
9689
|
+
destinationName: event.channelId,
|
|
8626
9690
|
assistantUserName: deps.assistantUserName,
|
|
8627
9691
|
modelId: deps.modelId
|
|
8628
9692
|
},
|
|
@@ -8644,9 +9708,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8644
9708
|
error,
|
|
8645
9709
|
"assistant_context_changed_handler_failed",
|
|
8646
9710
|
{
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
9711
|
+
messageConversationId: event.threadId,
|
|
9712
|
+
userId: event.userId,
|
|
9713
|
+
destinationName: event.channelId,
|
|
8650
9714
|
assistantUserName: deps.assistantUserName,
|
|
8651
9715
|
modelId: deps.modelId
|
|
8652
9716
|
},
|
|
@@ -8670,9 +9734,9 @@ function createSubscribedReplyPolicy(deps) {
|
|
|
8670
9734
|
logWarn(
|
|
8671
9735
|
"subscribed_message_classifier_failed",
|
|
8672
9736
|
{
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
9737
|
+
messageConversationId: input.context.threadId,
|
|
9738
|
+
userId: input.context.actorId,
|
|
9739
|
+
destinationName: input.context.channelId,
|
|
8676
9740
|
runId: input.context.runId,
|
|
8677
9741
|
assistantUserName: botConfig.userName,
|
|
8678
9742
|
modelId: botConfig.fastModelId
|
|
@@ -8881,9 +9945,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8881
9945
|
logWarn(
|
|
8882
9946
|
"attachment_skipped_size_limit",
|
|
8883
9947
|
{
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
9948
|
+
messageConversationId: context.threadId,
|
|
9949
|
+
userId: context.actorId,
|
|
9950
|
+
destinationName: context.channelId,
|
|
8887
9951
|
runId: context.runId,
|
|
8888
9952
|
assistantUserName: botConfig.userName,
|
|
8889
9953
|
modelId: standardModelId(botConfig)
|
|
@@ -8906,9 +9970,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8906
9970
|
logWarn(
|
|
8907
9971
|
"image_attachment_processing_failed",
|
|
8908
9972
|
{
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
9973
|
+
messageConversationId: context.threadId,
|
|
9974
|
+
userId: context.actorId,
|
|
9975
|
+
destinationName: context.channelId,
|
|
8912
9976
|
runId: context.runId,
|
|
8913
9977
|
assistantUserName: botConfig.userName,
|
|
8914
9978
|
modelId: botConfig.visionModelId ?? standardModelId(botConfig)
|
|
@@ -8925,9 +9989,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8925
9989
|
logWarn(
|
|
8926
9990
|
"attachment_resolution_failed",
|
|
8927
9991
|
{
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
9992
|
+
messageConversationId: context.threadId,
|
|
9993
|
+
userId: context.actorId,
|
|
9994
|
+
destinationName: context.channelId,
|
|
8931
9995
|
runId: context.runId,
|
|
8932
9996
|
assistantUserName: botConfig.userName,
|
|
8933
9997
|
modelId: standardModelId(botConfig)
|
|
@@ -8975,9 +10039,9 @@ async function summarizeConversationImage(args, deps) {
|
|
|
8975
10039
|
logWarn(
|
|
8976
10040
|
"conversation_image_vision_failed",
|
|
8977
10041
|
{
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
10042
|
+
messageConversationId: args.context.threadId,
|
|
10043
|
+
userId: args.context.actorId,
|
|
10044
|
+
destinationName: args.context.channelId,
|
|
8981
10045
|
runId: args.context.runId,
|
|
8982
10046
|
assistantUserName: botConfig.userName,
|
|
8983
10047
|
modelId: visionModelId
|
|
@@ -9031,9 +10095,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
9031
10095
|
logWarn(
|
|
9032
10096
|
"conversation_image_replies_fetch_failed",
|
|
9033
10097
|
{
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
10098
|
+
messageConversationId: context.threadId,
|
|
10099
|
+
userId: context.actorId,
|
|
10100
|
+
destinationName: context.channelId,
|
|
9037
10101
|
runId: context.runId,
|
|
9038
10102
|
assistantUserName: botConfig.userName,
|
|
9039
10103
|
modelId: standardModelId(botConfig)
|
|
@@ -9098,9 +10162,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
9098
10162
|
logWarn(
|
|
9099
10163
|
"conversation_image_skipped_size_limit",
|
|
9100
10164
|
{
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
10165
|
+
messageConversationId: context.threadId,
|
|
10166
|
+
userId: context.actorId,
|
|
10167
|
+
destinationName: context.channelId,
|
|
9104
10168
|
runId: context.runId,
|
|
9105
10169
|
assistantUserName: botConfig.userName,
|
|
9106
10170
|
modelId: standardModelId(botConfig)
|
|
@@ -9125,9 +10189,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
9125
10189
|
logWarn(
|
|
9126
10190
|
"conversation_image_download_failed",
|
|
9127
10191
|
{
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
10192
|
+
messageConversationId: context.threadId,
|
|
10193
|
+
userId: context.actorId,
|
|
10194
|
+
destinationName: context.channelId,
|
|
9131
10195
|
runId: context.runId,
|
|
9132
10196
|
assistantUserName: botConfig.userName,
|
|
9133
10197
|
modelId: standardModelId(botConfig)
|
|
@@ -9145,9 +10209,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
9145
10209
|
logWarn(
|
|
9146
10210
|
"conversation_image_skipped_size_limit",
|
|
9147
10211
|
{
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
10212
|
+
messageConversationId: context.threadId,
|
|
10213
|
+
userId: context.actorId,
|
|
10214
|
+
destinationName: context.channelId,
|
|
9151
10215
|
runId: context.runId,
|
|
9152
10216
|
assistantUserName: botConfig.userName,
|
|
9153
10217
|
modelId: standardModelId(botConfig)
|
|
@@ -9188,9 +10252,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
9188
10252
|
logInfo(
|
|
9189
10253
|
"conversation_image_context_hydrated",
|
|
9190
10254
|
{
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
10255
|
+
messageConversationId: context.threadId,
|
|
10256
|
+
userId: context.actorId,
|
|
10257
|
+
destinationName: context.channelId,
|
|
9194
10258
|
runId: context.runId,
|
|
9195
10259
|
assistantUserName: botConfig.userName,
|
|
9196
10260
|
modelId: standardModelId(botConfig)
|
|
@@ -9313,9 +10377,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9313
10377
|
logWarn(
|
|
9314
10378
|
"thread_title_generation_failed",
|
|
9315
10379
|
{
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
10380
|
+
messageConversationId: args.threadId,
|
|
10381
|
+
userId: args.actorId,
|
|
10382
|
+
destinationName: args.channelId,
|
|
9319
10383
|
runId: args.runId,
|
|
9320
10384
|
assistantUserName: args.assistantUserName,
|
|
9321
10385
|
modelId: args.modelId
|
|
@@ -9347,9 +10411,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9347
10411
|
logError(
|
|
9348
10412
|
"thread_title_generation_permission_denied",
|
|
9349
10413
|
{
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
10414
|
+
messageConversationId: args.threadId,
|
|
10415
|
+
userId: args.actorId,
|
|
10416
|
+
destinationName: args.channelId,
|
|
9353
10417
|
runId: args.runId,
|
|
9354
10418
|
assistantUserName: args.assistantUserName,
|
|
9355
10419
|
modelId: args.modelId
|
|
@@ -9361,9 +10425,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9361
10425
|
logWarn(
|
|
9362
10426
|
"thread_title_slack_update_failed",
|
|
9363
10427
|
{
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
10428
|
+
messageConversationId: args.threadId,
|
|
10429
|
+
userId: args.actorId,
|
|
10430
|
+
destinationName: args.channelId,
|
|
9367
10431
|
runId: args.runId,
|
|
9368
10432
|
assistantUserName: args.assistantUserName,
|
|
9369
10433
|
modelId: args.modelId
|
|
@@ -9595,9 +10659,9 @@ function createReplyToThread(deps) {
|
|
|
9595
10659
|
"chat.reply",
|
|
9596
10660
|
{
|
|
9597
10661
|
conversationId,
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
10662
|
+
messageConversationId: threadId,
|
|
10663
|
+
userId: message.author.userId,
|
|
10664
|
+
destinationName: channelId,
|
|
9601
10665
|
runId,
|
|
9602
10666
|
assistantUserName: botConfig.userName,
|
|
9603
10667
|
modelId: standardModelId(botConfig)
|
|
@@ -9653,9 +10717,9 @@ function createReplyToThread(deps) {
|
|
|
9653
10717
|
const turnId = buildDeterministicTurnId(message.id);
|
|
9654
10718
|
const turnTraceContext = {
|
|
9655
10719
|
conversationId,
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
10720
|
+
messageConversationId: threadId,
|
|
10721
|
+
userId: message.author.userId,
|
|
10722
|
+
destinationName: channelId,
|
|
9659
10723
|
runId,
|
|
9660
10724
|
assistantUserName: botConfig.userName,
|
|
9661
10725
|
modelId: standardModelId(botConfig)
|
|
@@ -9676,23 +10740,16 @@ function createReplyToThread(deps) {
|
|
|
9676
10740
|
actor.userId,
|
|
9677
10741
|
providerDisplayName
|
|
9678
10742
|
);
|
|
9679
|
-
const footer = buildSlackReplyFooter({ conversationId });
|
|
9680
10743
|
try {
|
|
10744
|
+
await beforeFirstResponsePost();
|
|
9681
10745
|
if (channelId && threadTs) {
|
|
9682
|
-
await
|
|
9683
|
-
beforePost: beforeFirstResponsePost,
|
|
10746
|
+
await sendSlackReply({
|
|
9684
10747
|
channelId,
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
text,
|
|
9689
|
-
stage: "thread_reply"
|
|
9690
|
-
}
|
|
9691
|
-
],
|
|
9692
|
-
footer
|
|
10748
|
+
conversationId,
|
|
10749
|
+
text,
|
|
10750
|
+
threadTs
|
|
9693
10751
|
});
|
|
9694
10752
|
} else {
|
|
9695
|
-
await beforeFirstResponsePost();
|
|
9696
10753
|
await thread.post(buildSlackOutputMessage(text));
|
|
9697
10754
|
}
|
|
9698
10755
|
} catch (error) {
|
|
@@ -9988,7 +11045,7 @@ function createReplyToThread(deps) {
|
|
|
9988
11045
|
});
|
|
9989
11046
|
}
|
|
9990
11047
|
if (actor?.userName) {
|
|
9991
|
-
setTags({
|
|
11048
|
+
setTags({ userName: actor.userName });
|
|
9992
11049
|
}
|
|
9993
11050
|
const turnAttachments = collectTurnAttachments(
|
|
9994
11051
|
message,
|
|
@@ -10014,32 +11071,6 @@ function createReplyToThread(deps) {
|
|
|
10014
11071
|
const compactingStatus = {
|
|
10015
11072
|
text: "Compacting context"
|
|
10016
11073
|
};
|
|
10017
|
-
const postThreadReply = async (payload, stage) => {
|
|
10018
|
-
await beforeFirstResponsePost();
|
|
10019
|
-
try {
|
|
10020
|
-
return await thread.post(payload);
|
|
10021
|
-
} catch (error) {
|
|
10022
|
-
if (isRetryableSlackPostError(error)) {
|
|
10023
|
-
throw new RetryableDeliveryError(error);
|
|
10024
|
-
}
|
|
10025
|
-
const eventId = logException(
|
|
10026
|
-
error,
|
|
10027
|
-
"slack_thread_post_failed",
|
|
10028
|
-
turnTraceContext,
|
|
10029
|
-
{
|
|
10030
|
-
"app.slack.reply_stage": stage,
|
|
10031
|
-
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10032
|
-
...getSlackErrorObservabilityAttributes(error)
|
|
10033
|
-
},
|
|
10034
|
-
"Failed to post Slack thread reply"
|
|
10035
|
-
);
|
|
10036
|
-
throw new ConversationTurnBoundaryError({
|
|
10037
|
-
cause: error,
|
|
10038
|
-
...eventId ? { eventId } : {},
|
|
10039
|
-
failureCode: "delivery_failed"
|
|
10040
|
-
});
|
|
10041
|
-
}
|
|
10042
|
-
};
|
|
10043
11074
|
let persistedAtLeastOnce = false;
|
|
10044
11075
|
let shouldPersistFailureState = true;
|
|
10045
11076
|
let runResultHandled = false;
|
|
@@ -10059,18 +11090,45 @@ function createReplyToThread(deps) {
|
|
|
10059
11090
|
turnCompletionNotified = true;
|
|
10060
11091
|
};
|
|
10061
11092
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
10062
|
-
|
|
10063
|
-
if (posts.length === 0) {
|
|
11093
|
+
if (!assistantMessage.text.trim()) {
|
|
10064
11094
|
return;
|
|
10065
11095
|
}
|
|
10066
11096
|
boundaryFailureCode = "delivery_failed";
|
|
10067
11097
|
let slackTs;
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
11098
|
+
await beforeFirstResponsePost();
|
|
11099
|
+
try {
|
|
11100
|
+
if (channelId && threadTs && thread.adapter.name === "slack") {
|
|
11101
|
+
slackTs = await sendSlackReply({
|
|
11102
|
+
channelId,
|
|
11103
|
+
conversationId,
|
|
11104
|
+
text: assistantMessage.text,
|
|
11105
|
+
threadTs
|
|
11106
|
+
});
|
|
11107
|
+
} else {
|
|
11108
|
+
for (const text of splitSlackReplyText(assistantMessage.text)) {
|
|
11109
|
+
slackTs = (await thread.post(buildSlackOutputMessage(text))).id;
|
|
11110
|
+
}
|
|
11111
|
+
}
|
|
11112
|
+
} catch (error) {
|
|
11113
|
+
if (isRetryableSlackPostError(error)) {
|
|
11114
|
+
throw new RetryableDeliveryError(error);
|
|
11115
|
+
}
|
|
11116
|
+
const eventId = logException(
|
|
11117
|
+
error,
|
|
11118
|
+
"slack_thread_post_failed",
|
|
11119
|
+
turnTraceContext,
|
|
11120
|
+
{
|
|
11121
|
+
"app.slack.reply_stage": "thread_reply",
|
|
11122
|
+
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
11123
|
+
...getSlackErrorObservabilityAttributes(error)
|
|
11124
|
+
},
|
|
11125
|
+
"Failed to post Slack thread reply"
|
|
10072
11126
|
);
|
|
10073
|
-
|
|
11127
|
+
throw new ConversationTurnBoundaryError({
|
|
11128
|
+
cause: error,
|
|
11129
|
+
...eventId ? { eventId } : {},
|
|
11130
|
+
failureCode: "delivery_failed"
|
|
11131
|
+
});
|
|
10074
11132
|
}
|
|
10075
11133
|
assistantMessageDelivered = true;
|
|
10076
11134
|
const recordedMessageId = recordDeliveredAssistantMessage({
|
|
@@ -10402,9 +11460,9 @@ function createReplyToThread(deps) {
|
|
|
10402
11460
|
}
|
|
10403
11461
|
let reply = outcome.result;
|
|
10404
11462
|
const diagnosticsContext = {
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
11463
|
+
messageConversationId: threadId,
|
|
11464
|
+
userId: message.author.userId,
|
|
11465
|
+
destinationName: channelId,
|
|
10408
11466
|
runId,
|
|
10409
11467
|
assistantUserName: botConfig.userName,
|
|
10410
11468
|
modelId: reply.diagnostics.modelId
|
|
@@ -10454,13 +11512,6 @@ function createReplyToThread(deps) {
|
|
|
10454
11512
|
sliceId: 1,
|
|
10455
11513
|
messages: reply.piMessages,
|
|
10456
11514
|
modelId: reply.diagnostics.modelId,
|
|
10457
|
-
logContext: {
|
|
10458
|
-
threadId,
|
|
10459
|
-
actorId: slackActorId,
|
|
10460
|
-
channelId,
|
|
10461
|
-
runId,
|
|
10462
|
-
assistantUserName: botConfig.userName
|
|
10463
|
-
},
|
|
10464
11515
|
actor: executionActor,
|
|
10465
11516
|
surface: "slack"
|
|
10466
11517
|
});
|
|
@@ -10747,6 +11798,11 @@ async function refreshAssistantThreadContext(event) {
|
|
|
10747
11798
|
|
|
10748
11799
|
// src/chat/runtime/conversation-message.ts
|
|
10749
11800
|
var NON_TEXT_MESSAGE_TEXT = "[non-text message]";
|
|
11801
|
+
function resourceEventType(entry) {
|
|
11802
|
+
if (!entry.raw || typeof entry.raw !== "object") return void 0;
|
|
11803
|
+
const raw = entry.raw;
|
|
11804
|
+
return raw.event_type === "resource_event" && typeof raw.resource_event_type === "string" ? raw.resource_event_type : void 0;
|
|
11805
|
+
}
|
|
10750
11806
|
function resolveMessageText(args) {
|
|
10751
11807
|
const text = normalizeConversationText(args.text);
|
|
10752
11808
|
return text || NON_TEXT_MESSAGE_TEXT;
|
|
@@ -10771,6 +11827,7 @@ function toConversationMessage(args) {
|
|
|
10771
11827
|
},
|
|
10772
11828
|
meta: {
|
|
10773
11829
|
attachmentCount: args.entry.attachments.length,
|
|
11830
|
+
eventType: resourceEventType(args.entry),
|
|
10774
11831
|
explicitMention: args.explicitMention,
|
|
10775
11832
|
imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
|
|
10776
11833
|
imagesHydrated: !messageHasPotentialImageAttachment,
|
|
@@ -11377,7 +12434,7 @@ async function continueSlackAgentRun(payload, options) {
|
|
|
11377
12434
|
}
|
|
11378
12435
|
}
|
|
11379
12436
|
},
|
|
11380
|
-
|
|
12437
|
+
commitResult: async (reply) => {
|
|
11381
12438
|
await persistCompletedReplyState2({
|
|
11382
12439
|
sessionRecord: activeSessionRecord,
|
|
11383
12440
|
reply
|
|
@@ -11704,6 +12761,9 @@ function renderResourceEventNotificationText(subscription, event) {
|
|
|
11704
12761
|
"Handling:",
|
|
11705
12762
|
"- This is a subscribed conversation update, not a user-authored command.",
|
|
11706
12763
|
"- Use the subscription intent to decide whether this event warrants action or a visible reply. Otherwise, stay silent.",
|
|
12764
|
+
"- Treat the trusted summary as sufficient evidence for the facts it reports. Do not verify or expand those facts with tools.",
|
|
12765
|
+
"- Use tools only when the subscription intent explicitly requires missing details or an action.",
|
|
12766
|
+
"- When replying, state what changed and the useful next step, if any.",
|
|
11707
12767
|
"",
|
|
11708
12768
|
"Subscription:",
|
|
11709
12769
|
`- resource: ${subscription.label}`,
|
|
@@ -12210,6 +13270,7 @@ async function createApp(options) {
|
|
|
12210
13270
|
return app;
|
|
12211
13271
|
}
|
|
12212
13272
|
export {
|
|
13273
|
+
JUNIOR_VERSION,
|
|
12213
13274
|
createApp,
|
|
12214
13275
|
defineJuniorPlugins
|
|
12215
13276
|
};
|