@sentry/junior 0.107.1 → 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-ICPIJAFY.js → agent-hooks-FHHVLNPA.js} +12 -12
- package/dist/api/conversations/events.d.ts +1 -1
- package/dist/api/conversations/list.d.ts +1 -0
- package/dist/api/conversations/schema.d.ts +57 -6
- package/dist/api/schema.js +1 -1
- package/dist/api.js +116 -27
- package/dist/app.d.ts +1 -0
- package/dist/app.js +1741 -458
- package/dist/{catalog-runtime-FOFNPR3P.js → catalog-runtime-DMGJSLV2.js} +6 -4
- package/dist/chat/conversations/history.d.ts +16 -5
- package/dist/chat/conversations/message-projection.d.ts +3 -5
- 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/resource-events/store.d.ts +6 -0
- package/dist/chat/runtime/slack-resume.d.ts +2 -2
- package/dist/chat/runtime/slack-runtime.d.ts +3 -0
- 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 +237 -10
- package/dist/chat/tool-support/turn-deadline-result.d.ts +3 -0
- package/dist/chunk-3SXUV423.js +10 -0
- package/dist/{chunk-YFQ7CQDE.js → chunk-72OMGPDS.js} +1017 -39
- package/dist/{chunk-2XXEKR44.js → chunk-7Q6WCISF.js} +1 -1
- package/dist/{chunk-NVOTGWYX.js → chunk-FENLIFJD.js} +97 -601
- package/dist/{chunk-IGHMVDWI.js → chunk-FWJRXN4F.js} +3 -3
- package/dist/{chunk-PUAMO4PI.js → chunk-HHVY4SMP.js} +41 -7
- package/dist/{chunk-SPUAJVVH.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-EDLNHZH3.js → chunk-IXKSQGIO.js} +226 -191
- package/dist/{chunk-SS67LUOK.js → chunk-JNAVF45E.js} +6 -7
- package/dist/{chunk-DY7TRYB5.js → chunk-MGUMR35T.js} +1 -1
- package/dist/{chunk-CQ7KSO2B.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-B2Z2H66D.js → chunk-OUHNWH46.js} +60 -5
- package/dist/{chunk-VIXFG2VC.js → chunk-PLB7MRCQ.js} +1 -2
- package/dist/{chunk-RMVOAJRL.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-DIGO4TGW.js → db-V3NU72O2.js} +8 -6
- package/dist/instrumentation.js +3 -2
- package/dist/nitro.js +5 -4
- package/dist/{runner-ACR2HAIC.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 +11 -7
- package/dist/chunk-4YF7Z6IA.js +0 -1061
- package/dist/chunk-AUUOHQAT.js +0 -115
- package/dist/chunk-G3E7SCME.js +0 -28
- package/dist/chunk-YNP2ATQX.js +0 -1282
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-YNP2ATQX.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,
|
|
@@ -69,6 +45,7 @@ import {
|
|
|
69
45
|
appendSlackLegacyAttachmentText,
|
|
70
46
|
buildSlackOutputMessage,
|
|
71
47
|
buildSteeringPiMessage,
|
|
48
|
+
cancelSubscriptions,
|
|
72
49
|
createAgentRunner,
|
|
73
50
|
createContextCompactor,
|
|
74
51
|
createUserTokenStore,
|
|
@@ -105,21 +82,28 @@ import {
|
|
|
105
82
|
splitSlackReplyText,
|
|
106
83
|
startOAuthFlow,
|
|
107
84
|
truncateStatusText
|
|
108
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-IXKSQGIO.js";
|
|
109
86
|
import {
|
|
110
87
|
ConversationTurnBoundaryError,
|
|
111
88
|
CooperativeTurnYieldError,
|
|
89
|
+
JUNIOR_THREAD_STATE_TTL_MS,
|
|
112
90
|
TurnInputCommitLostError,
|
|
113
91
|
TurnInputDeferredError,
|
|
114
92
|
TurnSliceLimitExceededError,
|
|
93
|
+
abandonAgentTurnSessionRecord,
|
|
115
94
|
buildConversationContext,
|
|
116
95
|
buildDeterministicAssistantMessageId,
|
|
117
96
|
buildDeterministicTurnId,
|
|
118
97
|
buildTurnLimitResponse,
|
|
119
98
|
clearPendingAuth,
|
|
120
99
|
coerceThreadArtifactsState,
|
|
100
|
+
coerceThreadConversationState,
|
|
101
|
+
commitMessages,
|
|
121
102
|
completeDeliveredTurn,
|
|
122
103
|
createConversationMemoryService,
|
|
104
|
+
failAgentTurnSessionRecord,
|
|
105
|
+
getAgentTurnSessionRecord,
|
|
106
|
+
getAgentTurnSessionRecordForResume,
|
|
123
107
|
getChannelConfigurationService,
|
|
124
108
|
getChannelConfigurationServiceById,
|
|
125
109
|
getConversationMessageSlackTs,
|
|
@@ -134,6 +118,9 @@ import {
|
|
|
134
118
|
isPendingAuthLatestRequest,
|
|
135
119
|
isTurnInputCommitLostError,
|
|
136
120
|
isTurnInputDeferredError,
|
|
121
|
+
listAgentTurnSessionSummariesForConversation,
|
|
122
|
+
loadConversationProjection,
|
|
123
|
+
loadProjection,
|
|
137
124
|
markConversationMessage,
|
|
138
125
|
markTurnClosed,
|
|
139
126
|
markTurnFailed,
|
|
@@ -147,33 +134,23 @@ import {
|
|
|
147
134
|
persistThreadStateById,
|
|
148
135
|
persistWithRetry,
|
|
149
136
|
persistYieldSessionRecord,
|
|
137
|
+
recordAgentTurnSessionSummary,
|
|
138
|
+
recordAuthorizationCompleted,
|
|
150
139
|
recordDeliveredAssistantMessage,
|
|
151
140
|
startActiveTurn,
|
|
152
141
|
turnHasReply,
|
|
153
142
|
updateConversationStats,
|
|
154
143
|
upsertConversationMessage
|
|
155
|
-
} from "./chunk-
|
|
156
|
-
import
|
|
157
|
-
JUNIOR_THREAD_STATE_TTL_MS,
|
|
158
|
-
abandonAgentTurnSessionRecord,
|
|
159
|
-
coerceThreadConversationState,
|
|
160
|
-
commitMessages,
|
|
161
|
-
failAgentTurnSessionRecord,
|
|
162
|
-
getAgentTurnSessionRecord,
|
|
163
|
-
getAgentTurnSessionRecordForResume,
|
|
164
|
-
listAgentTurnSessionSummariesForConversation,
|
|
165
|
-
loadConversationProjection,
|
|
166
|
-
loadProjection,
|
|
167
|
-
recordAgentTurnSessionSummary,
|
|
168
|
-
recordAuthorizationCompleted
|
|
169
|
-
} from "./chunk-4YF7Z6IA.js";
|
|
170
|
-
import "./chunk-SPUAJVVH.js";
|
|
144
|
+
} from "./chunk-72OMGPDS.js";
|
|
145
|
+
import "./chunk-HO5NT6OE.js";
|
|
171
146
|
import "./chunk-MU6HHZEN.js";
|
|
172
147
|
import "./chunk-PDO5BLNM.js";
|
|
173
148
|
import "./chunk-UIE3R5XU.js";
|
|
174
149
|
import {
|
|
175
150
|
bindScheduledTaskCredentialSubject,
|
|
176
151
|
bindSlackDirectCredentialSubject,
|
|
152
|
+
createPluginLogger,
|
|
153
|
+
createPluginState,
|
|
177
154
|
getDashboardConversationLink,
|
|
178
155
|
getPluginApiRoutes,
|
|
179
156
|
getPluginRoutes,
|
|
@@ -184,16 +161,12 @@ import {
|
|
|
184
161
|
validatePlugins,
|
|
185
162
|
verifyScheduledTaskCredentialSubject,
|
|
186
163
|
verifySlackDirectCredentialSubject
|
|
187
|
-
} from "./chunk-
|
|
188
|
-
import {
|
|
189
|
-
createPluginLogger,
|
|
190
|
-
createPluginState
|
|
191
|
-
} from "./chunk-AUUOHQAT.js";
|
|
192
|
-
import "./chunk-G3E7SCME.js";
|
|
164
|
+
} from "./chunk-QY636HZU.js";
|
|
193
165
|
import {
|
|
194
166
|
acquireActiveLock,
|
|
167
|
+
getDefaultRedisStateAdapterFor,
|
|
195
168
|
getStateAdapter
|
|
196
|
-
} from "./chunk-
|
|
169
|
+
} from "./chunk-OUHNWH46.js";
|
|
197
170
|
import {
|
|
198
171
|
SlackActionError,
|
|
199
172
|
downloadPrivateSlackFile,
|
|
@@ -204,7 +177,7 @@ import {
|
|
|
204
177
|
normalizeSlackConversationId,
|
|
205
178
|
runWithSlackInstallationToken,
|
|
206
179
|
runWithWorkspaceTeamId
|
|
207
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-RGX4V75M.js";
|
|
208
181
|
import {
|
|
209
182
|
contextProvenance,
|
|
210
183
|
getConversationEventStore,
|
|
@@ -212,10 +185,12 @@ import {
|
|
|
212
185
|
getDb,
|
|
213
186
|
getSqlExecutor,
|
|
214
187
|
instructionProvenanceFor,
|
|
215
|
-
juniorConversationEvents,
|
|
216
|
-
juniorConversations,
|
|
217
188
|
withConversationEventLock
|
|
218
|
-
} from "./chunk-
|
|
189
|
+
} from "./chunk-FENLIFJD.js";
|
|
190
|
+
import {
|
|
191
|
+
juniorConversationEvents,
|
|
192
|
+
juniorConversations
|
|
193
|
+
} from "./chunk-TMBRFULF.js";
|
|
219
194
|
import {
|
|
220
195
|
sleep
|
|
221
196
|
} from "./chunk-4ZNGQH7C.js";
|
|
@@ -239,7 +214,7 @@ import {
|
|
|
239
214
|
standardModelId,
|
|
240
215
|
stripRuntimeTurnContext,
|
|
241
216
|
trimTrailingAssistantMessages
|
|
242
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-JNAVF45E.js";
|
|
243
218
|
import {
|
|
244
219
|
juniorDestinations
|
|
245
220
|
} from "./chunk-VFUK3X5B.js";
|
|
@@ -248,32 +223,35 @@ import {
|
|
|
248
223
|
readSlackRawMessageContext,
|
|
249
224
|
resolveSlackChannelIdFromMessage,
|
|
250
225
|
resolveSlackChannelIdFromThreadId
|
|
251
|
-
} from "./chunk-
|
|
226
|
+
} from "./chunk-O3JMSZLI.js";
|
|
252
227
|
import {
|
|
253
228
|
createSlackDestination,
|
|
254
229
|
destinationKey,
|
|
255
230
|
parseDestination,
|
|
256
231
|
requireSlackDestination,
|
|
257
232
|
sameDestination
|
|
258
|
-
} from "./chunk-
|
|
233
|
+
} from "./chunk-SYAQIR5Z.js";
|
|
259
234
|
import {
|
|
260
235
|
discoverSkills
|
|
261
|
-
} from "./chunk-
|
|
236
|
+
} from "./chunk-WHMOBKT7.js";
|
|
237
|
+
import {
|
|
238
|
+
pluginCatalogRuntime
|
|
239
|
+
} from "./chunk-3SXUV423.js";
|
|
262
240
|
import {
|
|
263
241
|
buildOAuthTokenRequest,
|
|
264
242
|
credentialSubjectSchema,
|
|
265
243
|
hasRequiredOAuthScope,
|
|
266
|
-
parseOAuthTokenResponse
|
|
267
|
-
|
|
268
|
-
} from "./chunk-YIPD3L6L.js";
|
|
244
|
+
parseOAuthTokenResponse
|
|
245
|
+
} from "./chunk-TT6GMXNA.js";
|
|
269
246
|
import {
|
|
270
247
|
createActor,
|
|
271
248
|
createSlackActor,
|
|
272
249
|
createSlackResumeActor,
|
|
273
250
|
isActorUserId,
|
|
274
251
|
isUserActor,
|
|
275
|
-
parseActorUserId
|
|
276
|
-
|
|
252
|
+
parseActorUserId,
|
|
253
|
+
parseStoredSlackActor
|
|
254
|
+
} from "./chunk-PLB7MRCQ.js";
|
|
277
255
|
import {
|
|
278
256
|
buildTurnFailureResponse,
|
|
279
257
|
createChatSdkLogger,
|
|
@@ -289,22 +267,27 @@ import {
|
|
|
289
267
|
setTags,
|
|
290
268
|
withContext,
|
|
291
269
|
withSpan
|
|
292
|
-
} from "./chunk-
|
|
270
|
+
} from "./chunk-ISMWX44P.js";
|
|
293
271
|
import {
|
|
272
|
+
isRecord,
|
|
273
|
+
toOptionalNumber,
|
|
294
274
|
toOptionalString
|
|
295
275
|
} from "./chunk-OB42YVAE.js";
|
|
296
276
|
import {
|
|
297
277
|
continueTrace
|
|
298
278
|
} from "./chunk-6GWA276C.js";
|
|
299
279
|
import "./chunk-B3IT5END.js";
|
|
280
|
+
import {
|
|
281
|
+
homeDir
|
|
282
|
+
} from "./chunk-3V7OFJZC.js";
|
|
300
283
|
import {
|
|
301
284
|
JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
|
|
302
285
|
parseSlackChannelId,
|
|
303
286
|
parseSlackMessageTs
|
|
304
|
-
} from "./chunk-
|
|
287
|
+
} from "./chunk-IVVGFRQQ.js";
|
|
305
288
|
import {
|
|
306
|
-
|
|
307
|
-
} from "./chunk-
|
|
289
|
+
JUNIOR_VERSION
|
|
290
|
+
} from "./chunk-OCD3OZRK.js";
|
|
308
291
|
import "./chunk-MLKGABMK.js";
|
|
309
292
|
|
|
310
293
|
// src/app.ts
|
|
@@ -352,53 +335,26 @@ function buildSlackReplyBlocks(text, footer) {
|
|
|
352
335
|
}
|
|
353
336
|
|
|
354
337
|
// src/chat/slack/reply.ts
|
|
355
|
-
function
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const hasVisibleDelivery = post.text.trim().length > 0;
|
|
374
|
-
if (hasVisibleDelivery) {
|
|
375
|
-
await args.beforePost?.();
|
|
376
|
-
}
|
|
377
|
-
let messageTs;
|
|
378
|
-
try {
|
|
379
|
-
if (post.text.trim().length > 0) {
|
|
380
|
-
const footer = index === lastTextPostIndex ? args.footer : void 0;
|
|
381
|
-
const blocks = buildSlackReplyBlocks(post.text, footer);
|
|
382
|
-
const response = await postSlackMessage({
|
|
383
|
-
channelId: args.channelId,
|
|
384
|
-
threadTs: args.threadTs,
|
|
385
|
-
text: post.text,
|
|
386
|
-
...blocks ? { blocks } : {}
|
|
387
|
-
});
|
|
388
|
-
messageTs = response.ts;
|
|
389
|
-
lastPostedMessageTs = response.ts;
|
|
390
|
-
}
|
|
391
|
-
continue;
|
|
392
|
-
} catch (error) {
|
|
393
|
-
await args.onPostError?.({
|
|
394
|
-
error,
|
|
395
|
-
messageTs,
|
|
396
|
-
stage: post.stage
|
|
397
|
-
});
|
|
398
|
-
throw error;
|
|
399
|
-
}
|
|
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;
|
|
400
356
|
}
|
|
401
|
-
return
|
|
357
|
+
return lastMessageTs;
|
|
402
358
|
}
|
|
403
359
|
|
|
404
360
|
// src/chat/slack/errors.ts
|
|
@@ -583,6 +539,7 @@ var DEFAULT_MAX_ATTEMPTS = 5;
|
|
|
583
539
|
var nonEmptyExactStringSchema = z.string().min(1).refine(
|
|
584
540
|
(value) => value === value.trim() && value.toLowerCase() !== "unknown"
|
|
585
541
|
);
|
|
542
|
+
var incompleteDispatchIndexSchema = z.array(nonEmptyExactStringSchema);
|
|
586
543
|
var dispatchStatusSchema = z.enum([
|
|
587
544
|
"pending",
|
|
588
545
|
"running",
|
|
@@ -664,6 +621,9 @@ function incompleteDispatchIndexKey() {
|
|
|
664
621
|
function incompleteDispatchIndexLockKey() {
|
|
665
622
|
return `${DISPATCH_PREFIX}:incomplete:lock`;
|
|
666
623
|
}
|
|
624
|
+
function parseIncompleteDispatchIndex(value) {
|
|
625
|
+
return value === void 0 || value === null ? [] : incompleteDispatchIndexSchema.parse(value);
|
|
626
|
+
}
|
|
667
627
|
function dispatchLockKey(id) {
|
|
668
628
|
return `${DISPATCH_PREFIX}:lock:${id}`;
|
|
669
629
|
}
|
|
@@ -736,9 +696,12 @@ async function withIncompleteDispatchIndexLock(state, callback) {
|
|
|
736
696
|
}
|
|
737
697
|
async function syncIncompleteDispatchIndex(state, record) {
|
|
738
698
|
await withIncompleteDispatchIndexLock(state, async () => {
|
|
739
|
-
const existing = await state.get(incompleteDispatchIndexKey()) ?? [];
|
|
740
699
|
const ids = [
|
|
741
|
-
...new Set(
|
|
700
|
+
...new Set(
|
|
701
|
+
parseIncompleteDispatchIndex(
|
|
702
|
+
await state.get(incompleteDispatchIndexKey())
|
|
703
|
+
)
|
|
704
|
+
)
|
|
742
705
|
];
|
|
743
706
|
const next = isTerminalDispatchStatus(record.status) ? ids.filter((id) => id !== record.id) : ids.includes(record.id) ? ids : [...ids, record.id];
|
|
744
707
|
if (next.length === ids.length && next.every((id, index) => id === ids[index])) {
|
|
@@ -815,8 +778,13 @@ async function updateDispatchRecord(state, record) {
|
|
|
815
778
|
async function listIncompleteDispatchIds() {
|
|
816
779
|
const state = getStateAdapter();
|
|
817
780
|
await state.connect();
|
|
818
|
-
|
|
819
|
-
|
|
781
|
+
return [
|
|
782
|
+
...new Set(
|
|
783
|
+
parseIncompleteDispatchIndex(
|
|
784
|
+
await state.get(incompleteDispatchIndexKey())
|
|
785
|
+
)
|
|
786
|
+
)
|
|
787
|
+
];
|
|
820
788
|
}
|
|
821
789
|
async function getPluginDispatchProjection(args) {
|
|
822
790
|
const record = await getDispatchRecord(args.id);
|
|
@@ -914,8 +882,8 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
914
882
|
const turnId = getDispatchTurnId(dispatch.id);
|
|
915
883
|
const logContext = {
|
|
916
884
|
conversationId,
|
|
917
|
-
|
|
918
|
-
|
|
885
|
+
messageConversationId: conversationId,
|
|
886
|
+
destinationName: dispatch.destination.channelId,
|
|
919
887
|
runId: dispatch.id,
|
|
920
888
|
actorType: dispatch.actor.platform,
|
|
921
889
|
actorId: dispatch.actor.name,
|
|
@@ -1005,15 +973,15 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1005
973
|
let resultMessageTs;
|
|
1006
974
|
let assistantMessageDelivered = turnHasReply(conversation, turnId);
|
|
1007
975
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
1008
|
-
|
|
1009
|
-
if (posts.length === 0) {
|
|
976
|
+
if (!assistantMessage.text.trim()) {
|
|
1010
977
|
return;
|
|
1011
978
|
}
|
|
1012
979
|
failureCode = "delivery_failed";
|
|
1013
980
|
try {
|
|
1014
|
-
resultMessageTs = await
|
|
981
|
+
resultMessageTs = await sendSlackReply({
|
|
1015
982
|
channelId: dispatch.destination.channelId,
|
|
1016
|
-
|
|
983
|
+
conversationId,
|
|
984
|
+
text: assistantMessage.text
|
|
1017
985
|
});
|
|
1018
986
|
} catch (error) {
|
|
1019
987
|
if (isRetryableSlackPostError(error)) {
|
|
@@ -1209,13 +1177,7 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1209
1177
|
destinationVisibility: dispatch.destinationVisibility,
|
|
1210
1178
|
source: dispatch.source,
|
|
1211
1179
|
actor: dispatch.actor,
|
|
1212
|
-
surface: "api"
|
|
1213
|
-
logContext: {
|
|
1214
|
-
threadId: conversationId,
|
|
1215
|
-
channelId: dispatch.destination.channelId,
|
|
1216
|
-
runId: dispatch.id,
|
|
1217
|
-
assistantUserName: botConfig.userName
|
|
1218
|
-
}
|
|
1180
|
+
surface: "api"
|
|
1219
1181
|
});
|
|
1220
1182
|
}
|
|
1221
1183
|
if (statePersisted) {
|
|
@@ -1237,117 +1199,1244 @@ async function runAgentDispatchSlice(callback, deps) {
|
|
|
1237
1199
|
}
|
|
1238
1200
|
lifecycleTerminalized = true;
|
|
1239
1201
|
}
|
|
1240
|
-
dispatch = await markDispatch({
|
|
1241
|
-
dispatch,
|
|
1242
|
-
status: failure ? "failed" : "completed",
|
|
1243
|
-
...failure ? { errorMessage: failure } : {},
|
|
1244
|
-
resultMessageTs
|
|
1245
|
-
});
|
|
1246
|
-
if (!failure) {
|
|
1247
|
-
try {
|
|
1248
|
-
await scheduleCompletedTasks({
|
|
1249
|
-
conversationId,
|
|
1250
|
-
sessionId: turnId
|
|
1251
|
-
});
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
logException(
|
|
1254
|
-
error,
|
|
1255
|
-
"plugin_session_completed_task_schedule_failed",
|
|
1256
|
-
logContext,
|
|
1257
|
-
{},
|
|
1258
|
-
"Plugin session.completed task scheduling failed"
|
|
1259
|
-
);
|
|
1260
|
-
}
|
|
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
|
|
2247
|
+
});
|
|
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;
|
|
2269
|
+
}
|
|
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
|
+
};
|
|
2323
|
+
}
|
|
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
|
+
};
|
|
1261
2335
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
failureCode: "agent_run_failed"
|
|
1270
|
-
});
|
|
1271
|
-
lifecycleTerminalized = true;
|
|
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;
|
|
1272
2343
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (error instanceof PluginCredentialFailureError) {
|
|
1281
|
-
if (lifecycleStarted && !lifecycleTerminalized) {
|
|
1282
|
-
await turnLifecycle.fail({
|
|
1283
|
-
conversationId,
|
|
1284
|
-
turnId,
|
|
1285
|
-
createdAtMs: Date.now(),
|
|
1286
|
-
failureCode: "agent_run_failed"
|
|
1287
|
-
});
|
|
1288
|
-
lifecycleTerminalized = true;
|
|
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;
|
|
1289
2351
|
}
|
|
1290
|
-
|
|
1291
|
-
dispatch,
|
|
1292
|
-
status: "blocked",
|
|
1293
|
-
errorMessage: error.message
|
|
1294
|
-
});
|
|
1295
|
-
return;
|
|
2352
|
+
pendingMessages2.push(attempted);
|
|
1296
2353
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
2354
|
+
await writeConversation(
|
|
2355
|
+
state,
|
|
2356
|
+
lock,
|
|
2357
|
+
withExecutionUpdate(
|
|
2358
|
+
current,
|
|
2359
|
+
{
|
|
2360
|
+
...current.execution,
|
|
2361
|
+
pendingMessages: pendingMessages2
|
|
2362
|
+
},
|
|
2363
|
+
nowMs
|
|
2364
|
+
)
|
|
1306
2365
|
);
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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";
|
|
1316
2379
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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
|
+
});
|
|
1325
2397
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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);
|
|
1347
2439
|
}
|
|
1348
|
-
|
|
1349
|
-
// src/handlers/heartbeat.ts
|
|
1350
|
-
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1351
2440
|
|
|
1352
2441
|
// src/chat/task-execution/store.ts
|
|
1353
2442
|
function metadataStore(options) {
|
|
@@ -1360,7 +2449,7 @@ function hasRecentEnqueueMarker(conversation, nowMs) {
|
|
|
1360
2449
|
const lastEnqueuedAtMs = conversation.execution.lastEnqueuedAtMs;
|
|
1361
2450
|
return typeof lastEnqueuedAtMs === "number" && lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > nowMs;
|
|
1362
2451
|
}
|
|
1363
|
-
function
|
|
2452
|
+
function now2() {
|
|
1364
2453
|
return Date.now();
|
|
1365
2454
|
}
|
|
1366
2455
|
async function recordExecutionMetadata(args) {
|
|
@@ -1401,7 +2490,7 @@ function hasRunnableConversationWork(conversation) {
|
|
|
1401
2490
|
return conversation.execution.status !== "idle" || countPendingConversationMessages(conversation) > 0;
|
|
1402
2491
|
}
|
|
1403
2492
|
async function ensureConversationWake(args) {
|
|
1404
|
-
const nowMs = args.nowMs ??
|
|
2493
|
+
const nowMs = args.nowMs ?? now2();
|
|
1405
2494
|
const conversation = await getConversation({
|
|
1406
2495
|
conversationId: args.conversationId,
|
|
1407
2496
|
state: args.state
|
|
@@ -1434,7 +2523,7 @@ async function ensureConversationWake(args) {
|
|
|
1434
2523
|
return { status: "enqueued", queueMessageId: queueResult?.messageId };
|
|
1435
2524
|
}
|
|
1436
2525
|
async function appendAndEnqueueInboundMessage(args) {
|
|
1437
|
-
const nowMs = args.nowMs ??
|
|
2526
|
+
const nowMs = args.nowMs ?? now2();
|
|
1438
2527
|
const appendResult = await appendInboundMessage({
|
|
1439
2528
|
message: args.message,
|
|
1440
2529
|
nowMs,
|
|
@@ -2432,7 +3521,7 @@ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
|
2432
3521
|
var STATUS_MIN_VISIBLE_MS = 1200;
|
|
2433
3522
|
var STATUS_ROTATION_INTERVAL_MS = 3e4;
|
|
2434
3523
|
function createAssistantStatusScheduler(args) {
|
|
2435
|
-
const
|
|
3524
|
+
const now4 = args.now ?? (() => Date.now());
|
|
2436
3525
|
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
2437
3526
|
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
2438
3527
|
const random = args.random ?? Math.random;
|
|
@@ -2496,7 +3585,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
2496
3585
|
}
|
|
2497
3586
|
currentVisibleStatus = text;
|
|
2498
3587
|
currentLoadingMessages = nextLoadingMessages;
|
|
2499
|
-
lastStatusAt =
|
|
3588
|
+
lastStatusAt = now4();
|
|
2500
3589
|
scheduleRotation();
|
|
2501
3590
|
await enqueueStatusUpdate(async () => {
|
|
2502
3591
|
await args.sendStatus(text, nextLoadingMessages);
|
|
@@ -2583,7 +3672,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
2583
3672
|
}
|
|
2584
3673
|
return;
|
|
2585
3674
|
}
|
|
2586
|
-
const elapsed =
|
|
3675
|
+
const elapsed = now4() - lastStatusAt;
|
|
2587
3676
|
const waitMs = Math.max(
|
|
2588
3677
|
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
2589
3678
|
STATUS_MIN_VISIBLE_MS - elapsed,
|
|
@@ -2981,19 +4070,14 @@ function resolveReplyTimeoutMs(explicitTimeoutMs) {
|
|
|
2981
4070
|
const parsed = Number.parseInt(raw, 10);
|
|
2982
4071
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
2983
4072
|
}
|
|
2984
|
-
async function postSlackMessageBestEffort(channelId, threadTs, text,
|
|
4073
|
+
async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId) {
|
|
2985
4074
|
try {
|
|
2986
|
-
if (
|
|
2987
|
-
await
|
|
4075
|
+
if (conversationId) {
|
|
4076
|
+
await sendSlackReply({
|
|
2988
4077
|
channelId,
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
text,
|
|
2993
|
-
stage: "thread_reply"
|
|
2994
|
-
}
|
|
2995
|
-
],
|
|
2996
|
-
footer
|
|
4078
|
+
conversationId,
|
|
4079
|
+
text,
|
|
4080
|
+
threadTs
|
|
2997
4081
|
});
|
|
2998
4082
|
return;
|
|
2999
4083
|
}
|
|
@@ -3041,10 +4125,10 @@ function getResumeLogContext(args, lockKey) {
|
|
|
3041
4125
|
const actor = routing?.actor;
|
|
3042
4126
|
return {
|
|
3043
4127
|
conversationId: args.conversationId,
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
4128
|
+
messageConversationId: lockKey,
|
|
4129
|
+
userId: isUserActor(actor) ? actor.userId : void 0,
|
|
4130
|
+
userName: isUserActor(actor) ? actor.userName : void 0,
|
|
4131
|
+
destinationName: args.channelId,
|
|
3048
4132
|
assistantUserName: botConfig.userName,
|
|
3049
4133
|
modelId: standardModelId(botConfig)
|
|
3050
4134
|
};
|
|
@@ -3308,18 +4392,18 @@ async function resumeSlackTurn(args) {
|
|
|
3308
4392
|
};
|
|
3309
4393
|
};
|
|
3310
4394
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
3311
|
-
|
|
3312
|
-
if (posts.length === 0) {
|
|
4395
|
+
if (!assistantMessage.text.trim()) {
|
|
3313
4396
|
return;
|
|
3314
4397
|
}
|
|
3315
4398
|
failureCode = "delivery_failed";
|
|
3316
4399
|
const deliveryState2 = await getDeliveryConversation();
|
|
3317
4400
|
let messageTs;
|
|
3318
4401
|
try {
|
|
3319
|
-
messageTs = await
|
|
4402
|
+
messageTs = await sendSlackReply({
|
|
3320
4403
|
channelId: runArgs.channelId,
|
|
3321
|
-
|
|
3322
|
-
|
|
4404
|
+
conversationId: runArgs.conversationId,
|
|
4405
|
+
text: assistantMessage.text,
|
|
4406
|
+
threadTs: runArgs.threadTs
|
|
3323
4407
|
});
|
|
3324
4408
|
} catch (error) {
|
|
3325
4409
|
if (isRetryableSlackPostError(error)) {
|
|
@@ -3446,16 +4530,10 @@ async function resumeSlackTurn(args) {
|
|
|
3446
4530
|
destination: replyContext.routing.destination,
|
|
3447
4531
|
source: replyContext.routing.source,
|
|
3448
4532
|
actor: resumeActor,
|
|
3449
|
-
surface: "slack"
|
|
3450
|
-
logContext: {
|
|
3451
|
-
threadId: runArgs.conversationId,
|
|
3452
|
-
actorId: isUserActor(replyContext.routing.actor) ? replyContext.routing.actor.userId : void 0,
|
|
3453
|
-
channelId: runArgs.channelId,
|
|
3454
|
-
assistantUserName: botConfig.userName
|
|
3455
|
-
}
|
|
4533
|
+
surface: "slack"
|
|
3456
4534
|
});
|
|
3457
4535
|
}
|
|
3458
|
-
await runArgs.
|
|
4536
|
+
await runArgs.commitResult?.(reply);
|
|
3459
4537
|
if (reply.diagnostics.outcome === "success") {
|
|
3460
4538
|
await turnLifecycle.complete({
|
|
3461
4539
|
conversationId: runArgs.conversationId,
|
|
@@ -3551,9 +4629,6 @@ async function resumeSlackTurn(args) {
|
|
|
3551
4629
|
try {
|
|
3552
4630
|
await deferredPauseHandler();
|
|
3553
4631
|
if (deferredAuthInfo) {
|
|
3554
|
-
const footer = buildSlackReplyFooter({
|
|
3555
|
-
conversationId: runArgs.conversationId
|
|
3556
|
-
});
|
|
3557
4632
|
await postSlackMessageBestEffort(
|
|
3558
4633
|
runArgs.channelId,
|
|
3559
4634
|
runArgs.threadTs,
|
|
@@ -3561,7 +4636,7 @@ async function resumeSlackTurn(args) {
|
|
|
3561
4636
|
deferredAuthInfo.actorId,
|
|
3562
4637
|
deferredAuthInfo.providerDisplayName
|
|
3563
4638
|
),
|
|
3564
|
-
|
|
4639
|
+
runArgs.conversationId
|
|
3565
4640
|
);
|
|
3566
4641
|
}
|
|
3567
4642
|
return true;
|
|
@@ -3964,7 +5039,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
3964
5039
|
}
|
|
3965
5040
|
}
|
|
3966
5041
|
},
|
|
3967
|
-
|
|
5042
|
+
commitResult: async (reply) => {
|
|
3968
5043
|
await persistCompletedReplyState(
|
|
3969
5044
|
authSession.channelId,
|
|
3970
5045
|
authSession.threadTs,
|
|
@@ -4562,7 +5637,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
|
|
|
4562
5637
|
}
|
|
4563
5638
|
}
|
|
4564
5639
|
},
|
|
4565
|
-
|
|
5640
|
+
commitResult: async (reply) => {
|
|
4566
5641
|
logInfo(
|
|
4567
5642
|
"oauth_callback_resume_complete",
|
|
4568
5643
|
{},
|
|
@@ -4849,9 +5924,9 @@ function buildJwksUrl(value) {
|
|
|
4849
5924
|
return url;
|
|
4850
5925
|
}
|
|
4851
5926
|
async function getJwks(issuer) {
|
|
4852
|
-
const
|
|
5927
|
+
const now4 = Date.now();
|
|
4853
5928
|
const cached = jwksByIssuer.get(issuer);
|
|
4854
|
-
if (cached && cached.expiresAtMs >
|
|
5929
|
+
if (cached && cached.expiresAtMs > now4) {
|
|
4855
5930
|
return cached.jwks;
|
|
4856
5931
|
}
|
|
4857
5932
|
if (cached) {
|
|
@@ -4876,7 +5951,7 @@ async function getJwks(issuer) {
|
|
|
4876
5951
|
}
|
|
4877
5952
|
jwksByIssuer.set(issuer, {
|
|
4878
5953
|
jwks,
|
|
4879
|
-
expiresAtMs:
|
|
5954
|
+
expiresAtMs: now4 + OIDC_DISCOVERY_CACHE_TTL_MS
|
|
4880
5955
|
});
|
|
4881
5956
|
return jwks;
|
|
4882
5957
|
}
|
|
@@ -5188,6 +6263,7 @@ import {
|
|
|
5188
6263
|
Message,
|
|
5189
6264
|
ThreadImpl
|
|
5190
6265
|
} from "chat";
|
|
6266
|
+
import { z as z3 } from "zod";
|
|
5191
6267
|
|
|
5192
6268
|
// src/chat/ingress/message-router.ts
|
|
5193
6269
|
function normalizeIncomingSlackThreadId(threadId, message) {
|
|
@@ -5225,7 +6301,7 @@ function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downlo
|
|
|
5225
6301
|
|
|
5226
6302
|
// src/chat/slack/adapter-context.ts
|
|
5227
6303
|
var initializedAdapters = /* @__PURE__ */ new WeakSet();
|
|
5228
|
-
async function
|
|
6304
|
+
async function getConnectedState2(stateAdapter) {
|
|
5229
6305
|
const state = stateAdapter ?? getStateAdapter();
|
|
5230
6306
|
await state.connect();
|
|
5231
6307
|
return state;
|
|
@@ -5234,7 +6310,7 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
5234
6310
|
if (initializedAdapters.has(args.adapter)) {
|
|
5235
6311
|
return;
|
|
5236
6312
|
}
|
|
5237
|
-
const state = await
|
|
6313
|
+
const state = await getConnectedState2(args.state);
|
|
5238
6314
|
await args.adapter.initialize({
|
|
5239
6315
|
getState: () => state
|
|
5240
6316
|
});
|
|
@@ -5453,6 +6529,211 @@ async function lookupSlackUser(teamId, userId) {
|
|
|
5453
6529
|
}
|
|
5454
6530
|
|
|
5455
6531
|
// src/chat/task-execution/slack-work.ts
|
|
6532
|
+
var slackConversationRouteSchema = z3.enum(["mention", "subscribed"]);
|
|
6533
|
+
var serializedDateSchema = z3.iso.datetime();
|
|
6534
|
+
var mdastPointSchema = z3.object({
|
|
6535
|
+
column: z3.number().int().positive(),
|
|
6536
|
+
line: z3.number().int().positive(),
|
|
6537
|
+
offset: z3.number().int().nonnegative().optional()
|
|
6538
|
+
}).strict();
|
|
6539
|
+
var mdastPositionSchema = z3.object({
|
|
6540
|
+
end: mdastPointSchema,
|
|
6541
|
+
start: mdastPointSchema
|
|
6542
|
+
}).strict();
|
|
6543
|
+
function isOptionalNullableString(value) {
|
|
6544
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
6545
|
+
}
|
|
6546
|
+
function isOptionalNullableBoolean(value) {
|
|
6547
|
+
return value === void 0 || value === null || typeof value === "boolean";
|
|
6548
|
+
}
|
|
6549
|
+
function hasValidMdastBase(node, fields) {
|
|
6550
|
+
const allowed = /* @__PURE__ */ new Set(["data", "position", "type", ...fields]);
|
|
6551
|
+
return Object.keys(node).every((key) => allowed.has(key)) && (node.data === void 0 || typeof node.data === "object" && node.data !== null && !Array.isArray(node.data)) && (node.position === void 0 || mdastPositionSchema.safeParse(node.position).success);
|
|
6552
|
+
}
|
|
6553
|
+
var mdastPhrasingTypes = /* @__PURE__ */ new Set([
|
|
6554
|
+
"break",
|
|
6555
|
+
"delete",
|
|
6556
|
+
"emphasis",
|
|
6557
|
+
"footnoteReference",
|
|
6558
|
+
"html",
|
|
6559
|
+
"image",
|
|
6560
|
+
"imageReference",
|
|
6561
|
+
"inlineCode",
|
|
6562
|
+
"link",
|
|
6563
|
+
"linkReference",
|
|
6564
|
+
"strong",
|
|
6565
|
+
"text"
|
|
6566
|
+
]);
|
|
6567
|
+
var mdastBlockOrDefinitionTypes = /* @__PURE__ */ new Set([
|
|
6568
|
+
"blockquote",
|
|
6569
|
+
"code",
|
|
6570
|
+
"definition",
|
|
6571
|
+
"footnoteDefinition",
|
|
6572
|
+
"heading",
|
|
6573
|
+
"html",
|
|
6574
|
+
"list",
|
|
6575
|
+
"paragraph",
|
|
6576
|
+
"table",
|
|
6577
|
+
"thematicBreak"
|
|
6578
|
+
]);
|
|
6579
|
+
var mdastListItemTypes = /* @__PURE__ */ new Set(["listItem"]);
|
|
6580
|
+
var mdastTableCellTypes = /* @__PURE__ */ new Set(["tableCell"]);
|
|
6581
|
+
var mdastTableRowTypes = /* @__PURE__ */ new Set(["tableRow"]);
|
|
6582
|
+
function hasMdastChildren(node, allowedTypes) {
|
|
6583
|
+
return Array.isArray(node.children) && node.children.every(
|
|
6584
|
+
(child) => isFormattedContentNode(child) && (!allowedTypes || typeof child === "object" && child !== null && !Array.isArray(child) && allowedTypes.has(String(child.type)))
|
|
6585
|
+
);
|
|
6586
|
+
}
|
|
6587
|
+
function isFormattedContentNode(value) {
|
|
6588
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
6589
|
+
return false;
|
|
6590
|
+
}
|
|
6591
|
+
const node = value;
|
|
6592
|
+
switch (node.type) {
|
|
6593
|
+
case "break":
|
|
6594
|
+
case "thematicBreak":
|
|
6595
|
+
return hasValidMdastBase(node, []);
|
|
6596
|
+
case "html":
|
|
6597
|
+
case "inlineCode":
|
|
6598
|
+
case "text":
|
|
6599
|
+
case "yaml":
|
|
6600
|
+
return hasValidMdastBase(node, ["value"]) && typeof node.value === "string";
|
|
6601
|
+
case "code":
|
|
6602
|
+
return hasValidMdastBase(node, ["lang", "meta", "value"]) && typeof node.value === "string" && isOptionalNullableString(node.lang) && isOptionalNullableString(node.meta);
|
|
6603
|
+
case "delete":
|
|
6604
|
+
case "emphasis":
|
|
6605
|
+
case "paragraph":
|
|
6606
|
+
case "strong":
|
|
6607
|
+
case "tableCell":
|
|
6608
|
+
return hasValidMdastBase(node, ["children"]) && hasMdastChildren(node, mdastPhrasingTypes);
|
|
6609
|
+
case "blockquote":
|
|
6610
|
+
return hasValidMdastBase(node, ["children"]) && hasMdastChildren(node, mdastBlockOrDefinitionTypes);
|
|
6611
|
+
case "tableRow":
|
|
6612
|
+
return hasValidMdastBase(node, ["children"]) && hasMdastChildren(node, mdastTableCellTypes);
|
|
6613
|
+
case "heading":
|
|
6614
|
+
return hasValidMdastBase(node, ["children", "depth"]) && hasMdastChildren(node, mdastPhrasingTypes) && Number.isInteger(node.depth) && Number(node.depth) >= 1 && Number(node.depth) <= 6;
|
|
6615
|
+
case "list":
|
|
6616
|
+
return hasValidMdastBase(node, ["children", "ordered", "spread", "start"]) && hasMdastChildren(node, mdastListItemTypes) && isOptionalNullableBoolean(node.ordered) && isOptionalNullableBoolean(node.spread) && (node.start === void 0 || node.start === null || typeof node.start === "number" && Number.isFinite(node.start));
|
|
6617
|
+
case "listItem":
|
|
6618
|
+
return hasValidMdastBase(node, ["checked", "children", "spread"]) && hasMdastChildren(node, mdastBlockOrDefinitionTypes) && isOptionalNullableBoolean(node.checked) && isOptionalNullableBoolean(node.spread);
|
|
6619
|
+
case "table":
|
|
6620
|
+
return hasValidMdastBase(node, ["align", "children"]) && hasMdastChildren(node, mdastTableRowTypes) && (node.align === void 0 || node.align === null || Array.isArray(node.align) && node.align.every(
|
|
6621
|
+
(align) => align === null || align === "center" || align === "left" || align === "right"
|
|
6622
|
+
));
|
|
6623
|
+
case "definition":
|
|
6624
|
+
return hasValidMdastBase(node, ["identifier", "label", "title", "url"]) && typeof node.identifier === "string" && typeof node.url === "string" && isOptionalNullableString(node.label) && isOptionalNullableString(node.title);
|
|
6625
|
+
case "footnoteDefinition":
|
|
6626
|
+
return hasValidMdastBase(node, ["children", "identifier", "label"]) && hasMdastChildren(node, mdastBlockOrDefinitionTypes) && typeof node.identifier === "string" && isOptionalNullableString(node.label);
|
|
6627
|
+
case "footnoteReference":
|
|
6628
|
+
return hasValidMdastBase(node, ["identifier", "label"]) && typeof node.identifier === "string" && isOptionalNullableString(node.label);
|
|
6629
|
+
case "image":
|
|
6630
|
+
return hasValidMdastBase(node, ["alt", "title", "url"]) && typeof node.url === "string" && isOptionalNullableString(node.alt) && isOptionalNullableString(node.title);
|
|
6631
|
+
case "imageReference":
|
|
6632
|
+
return hasValidMdastBase(node, [
|
|
6633
|
+
"alt",
|
|
6634
|
+
"identifier",
|
|
6635
|
+
"label",
|
|
6636
|
+
"referenceType"
|
|
6637
|
+
]) && typeof node.identifier === "string" && isOptionalNullableString(node.alt) && isOptionalNullableString(node.label) && (node.referenceType === "collapsed" || node.referenceType === "full" || node.referenceType === "shortcut");
|
|
6638
|
+
case "link":
|
|
6639
|
+
return hasValidMdastBase(node, ["children", "title", "url"]) && hasMdastChildren(node, mdastPhrasingTypes) && typeof node.url === "string" && isOptionalNullableString(node.title);
|
|
6640
|
+
case "linkReference":
|
|
6641
|
+
return hasValidMdastBase(node, [
|
|
6642
|
+
"children",
|
|
6643
|
+
"identifier",
|
|
6644
|
+
"label",
|
|
6645
|
+
"referenceType"
|
|
6646
|
+
]) && hasMdastChildren(node, mdastPhrasingTypes) && typeof node.identifier === "string" && isOptionalNullableString(node.label) && (node.referenceType === "collapsed" || node.referenceType === "full" || node.referenceType === "shortcut");
|
|
6647
|
+
default:
|
|
6648
|
+
return false;
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
var formattedContentSchema = z3.custom(
|
|
6652
|
+
(value) => {
|
|
6653
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
6654
|
+
return false;
|
|
6655
|
+
}
|
|
6656
|
+
const root = value;
|
|
6657
|
+
return root.type === "root" && hasValidMdastBase(root, ["children"]) && hasMdastChildren(root);
|
|
6658
|
+
},
|
|
6659
|
+
"must be a valid formatted-content root"
|
|
6660
|
+
);
|
|
6661
|
+
var serializedMessageSchema = z3.object({
|
|
6662
|
+
_type: z3.literal("chat:Message"),
|
|
6663
|
+
attachments: z3.array(
|
|
6664
|
+
z3.object({
|
|
6665
|
+
type: z3.enum(["image", "file", "video", "audio"]),
|
|
6666
|
+
url: z3.string().optional(),
|
|
6667
|
+
name: z3.string().optional(),
|
|
6668
|
+
mimeType: z3.string().optional(),
|
|
6669
|
+
size: z3.number().finite().optional(),
|
|
6670
|
+
width: z3.number().finite().optional(),
|
|
6671
|
+
height: z3.number().finite().optional(),
|
|
6672
|
+
fetchMetadata: z3.record(z3.string(), z3.string()).optional()
|
|
6673
|
+
}).strict()
|
|
6674
|
+
),
|
|
6675
|
+
author: z3.object({
|
|
6676
|
+
userId: z3.string(),
|
|
6677
|
+
userName: z3.string(),
|
|
6678
|
+
fullName: z3.string(),
|
|
6679
|
+
isBot: z3.union([z3.boolean(), z3.literal("unknown")]),
|
|
6680
|
+
isMe: z3.boolean()
|
|
6681
|
+
}).strict(),
|
|
6682
|
+
formatted: formattedContentSchema,
|
|
6683
|
+
id: z3.string().min(1),
|
|
6684
|
+
isMention: z3.boolean().optional(),
|
|
6685
|
+
links: z3.array(
|
|
6686
|
+
z3.object({
|
|
6687
|
+
url: z3.string(),
|
|
6688
|
+
title: z3.string().optional(),
|
|
6689
|
+
description: z3.string().optional(),
|
|
6690
|
+
imageUrl: z3.string().optional(),
|
|
6691
|
+
siteName: z3.string().optional()
|
|
6692
|
+
}).strict()
|
|
6693
|
+
).optional(),
|
|
6694
|
+
metadata: z3.object({
|
|
6695
|
+
dateSent: serializedDateSchema,
|
|
6696
|
+
edited: z3.boolean(),
|
|
6697
|
+
editedAt: serializedDateSchema.optional()
|
|
6698
|
+
}).strict(),
|
|
6699
|
+
raw: z3.unknown(),
|
|
6700
|
+
text: z3.string(),
|
|
6701
|
+
threadId: z3.string().min(1)
|
|
6702
|
+
}).strict();
|
|
6703
|
+
var serializedThreadSchema = z3.object({
|
|
6704
|
+
_type: z3.literal("chat:Thread"),
|
|
6705
|
+
adapterName: z3.literal("slack"),
|
|
6706
|
+
channelId: z3.string().min(1),
|
|
6707
|
+
channelVisibility: z3.enum(["private", "workspace", "external", "unknown"]).optional(),
|
|
6708
|
+
currentMessage: serializedMessageSchema.optional(),
|
|
6709
|
+
id: z3.string().min(1),
|
|
6710
|
+
isDM: z3.boolean()
|
|
6711
|
+
}).strict();
|
|
6712
|
+
var slackInstallationSchema = z3.object({
|
|
6713
|
+
enterpriseId: z3.string().optional(),
|
|
6714
|
+
isEnterpriseInstall: z3.boolean().optional(),
|
|
6715
|
+
teamId: z3.string().optional()
|
|
6716
|
+
}).strict();
|
|
6717
|
+
var slackConversationMessageMetadataBaseSchema = z3.object({
|
|
6718
|
+
installation: slackInstallationSchema.optional(),
|
|
6719
|
+
message: serializedMessageSchema,
|
|
6720
|
+
platform: z3.literal("slack"),
|
|
6721
|
+
route: slackConversationRouteSchema,
|
|
6722
|
+
thread: serializedThreadSchema
|
|
6723
|
+
});
|
|
6724
|
+
var slackConversationMessageMetadataSchema = z3.union([
|
|
6725
|
+
slackConversationMessageMetadataBaseSchema.strict(),
|
|
6726
|
+
slackConversationMessageMetadataBaseSchema.extend({
|
|
6727
|
+
kind: z3.literal("resource_event"),
|
|
6728
|
+
resourceEvent: z3.object({
|
|
6729
|
+
eventKey: z3.string(),
|
|
6730
|
+
eventType: z3.string(),
|
|
6731
|
+
provider: z3.string(),
|
|
6732
|
+
resourceRef: z3.string(),
|
|
6733
|
+
subscriptionId: z3.string()
|
|
6734
|
+
}).strict()
|
|
6735
|
+
}).strict()
|
|
6736
|
+
]);
|
|
5456
6737
|
function requireSlackAuthorId(message) {
|
|
5457
6738
|
const authorId = parseActorUserId(message.author.userId);
|
|
5458
6739
|
if (!authorId) {
|
|
@@ -5497,6 +6778,7 @@ function slackSerializedResourceEventMessage(input) {
|
|
|
5497
6778
|
raw: {
|
|
5498
6779
|
channel: input.channelId,
|
|
5499
6780
|
event_type: "resource_event",
|
|
6781
|
+
resource_event_type: input.eventType,
|
|
5500
6782
|
thread_ts: input.threadTs,
|
|
5501
6783
|
type: "message",
|
|
5502
6784
|
user: "UJRNEVENT"
|
|
@@ -5522,6 +6804,7 @@ function createSlackResourceEventInboundMessage(input) {
|
|
|
5522
6804
|
const timestampIso = new Date(input.event.occurredAtMs).toISOString();
|
|
5523
6805
|
const message = slackSerializedResourceEventMessage({
|
|
5524
6806
|
channelId: slack.channelId,
|
|
6807
|
+
eventType: input.event.eventType,
|
|
5525
6808
|
id: messageId,
|
|
5526
6809
|
text: input.text,
|
|
5527
6810
|
threadTs: slack.threadTs,
|
|
@@ -5562,19 +6845,20 @@ function createSlackResourceEventInboundMessage(input) {
|
|
|
5562
6845
|
}
|
|
5563
6846
|
};
|
|
5564
6847
|
}
|
|
5565
|
-
function
|
|
6848
|
+
function getConnectedState3(stateAdapter) {
|
|
5566
6849
|
return stateAdapter ?? getStateAdapter();
|
|
5567
6850
|
}
|
|
5568
|
-
function
|
|
5569
|
-
|
|
6851
|
+
function parseSlackMetadata(value) {
|
|
6852
|
+
const parsed = slackConversationMessageMetadataSchema.safeParse(value);
|
|
6853
|
+
return parsed.success ? parsed.data : void 0;
|
|
5570
6854
|
}
|
|
5571
6855
|
function compareInboundMessages(left, right) {
|
|
5572
6856
|
return left.createdAtMs - right.createdAtMs || left.receivedAtMs - right.receivedAtMs || left.inboundMessageId.localeCompare(right.inboundMessageId);
|
|
5573
6857
|
}
|
|
5574
6858
|
function routeForRecords(records) {
|
|
5575
6859
|
return records.some((record) => {
|
|
5576
|
-
const metadata = record.input.metadata;
|
|
5577
|
-
if (!
|
|
6860
|
+
const metadata = parseSlackMetadata(record.input.metadata);
|
|
6861
|
+
if (!metadata) {
|
|
5578
6862
|
throw new Error("Conversation mailbox record is not Slack metadata");
|
|
5579
6863
|
}
|
|
5580
6864
|
return metadata.route === "mention";
|
|
@@ -5589,8 +6873,8 @@ function isResourceEventNotificationMessage(message) {
|
|
|
5589
6873
|
return raw?.event_type === "resource_event";
|
|
5590
6874
|
}
|
|
5591
6875
|
function restoreMessage(args) {
|
|
5592
|
-
const metadata = args.record.input.metadata;
|
|
5593
|
-
if (!
|
|
6876
|
+
const metadata = parseSlackMetadata(args.record.input.metadata);
|
|
6877
|
+
if (!metadata) {
|
|
5594
6878
|
throw new Error("Conversation mailbox record is not a Slack message");
|
|
5595
6879
|
}
|
|
5596
6880
|
const message = Message.fromJSON(metadata.message);
|
|
@@ -5646,8 +6930,8 @@ function restoreThread(args) {
|
|
|
5646
6930
|
}
|
|
5647
6931
|
function getInstallation(records) {
|
|
5648
6932
|
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
5649
|
-
const metadata = records[index]?.input.metadata;
|
|
5650
|
-
if (
|
|
6933
|
+
const metadata = parseSlackMetadata(records[index]?.input.metadata);
|
|
6934
|
+
if (metadata?.installation) {
|
|
5651
6935
|
return metadata.installation;
|
|
5652
6936
|
}
|
|
5653
6937
|
}
|
|
@@ -5663,7 +6947,7 @@ function createSlackConversationWorker(options) {
|
|
|
5663
6947
|
return async (context) => {
|
|
5664
6948
|
const adapter = options.getSlackAdapter();
|
|
5665
6949
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
5666
|
-
const state =
|
|
6950
|
+
const state = getConnectedState3(options.state);
|
|
5667
6951
|
await state.connect();
|
|
5668
6952
|
const records = getPendingRecords({
|
|
5669
6953
|
execution: { pendingMessages: [...context.attempt.messages] }
|
|
@@ -5687,8 +6971,8 @@ function createSlackConversationWorker(options) {
|
|
|
5687
6971
|
if (!latestRecord) {
|
|
5688
6972
|
return { status: "completed" };
|
|
5689
6973
|
}
|
|
5690
|
-
const latestMetadata = latestRecord.input.metadata;
|
|
5691
|
-
if (!
|
|
6974
|
+
const latestMetadata = parseSlackMetadata(latestRecord.input.metadata);
|
|
6975
|
+
if (!latestMetadata) {
|
|
5692
6976
|
throw new Error(
|
|
5693
6977
|
"Latest conversation mailbox record is not Slack metadata"
|
|
5694
6978
|
);
|
|
@@ -5747,8 +7031,8 @@ function createSlackConversationWorker(options) {
|
|
|
5747
7031
|
const drainSteeringMessages = async (accept) => {
|
|
5748
7032
|
await context.attempt.drain(async (pendingRecords) => {
|
|
5749
7033
|
const messages2 = pendingRecords.map((record) => {
|
|
5750
|
-
const metadata = record.input.metadata;
|
|
5751
|
-
if (!
|
|
7034
|
+
const metadata = parseSlackMetadata(record.input.metadata);
|
|
7035
|
+
if (!metadata) {
|
|
5752
7036
|
throw new Error(
|
|
5753
7037
|
"Conversation mailbox record is not Slack metadata"
|
|
5754
7038
|
);
|
|
@@ -5832,7 +7116,7 @@ function buildSlackInboundMessage(args) {
|
|
|
5832
7116
|
platform: "slack",
|
|
5833
7117
|
route: args.route,
|
|
5834
7118
|
installation: args.installation,
|
|
5835
|
-
thread: args.thread.toJSON(),
|
|
7119
|
+
thread: serializedThreadSchema.parse(args.thread.toJSON()),
|
|
5836
7120
|
message: args.message.toJSON()
|
|
5837
7121
|
}
|
|
5838
7122
|
}
|
|
@@ -6015,7 +7299,7 @@ async function handleUnlink(event, actorId, provider) {
|
|
|
6015
7299
|
await tokenStore.delete(actorId, provider);
|
|
6016
7300
|
logInfo(
|
|
6017
7301
|
"slash_command_unlink",
|
|
6018
|
-
{
|
|
7302
|
+
{ userId: actorId },
|
|
6019
7303
|
{ "app.credential.provider": provider },
|
|
6020
7304
|
`Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
|
|
6021
7305
|
);
|
|
@@ -6237,7 +7521,7 @@ async function handleSlackEvent(args) {
|
|
|
6237
7521
|
);
|
|
6238
7522
|
} catch (error) {
|
|
6239
7523
|
logException(error, "slack_app_home_publish_failed", {
|
|
6240
|
-
|
|
7524
|
+
userId
|
|
6241
7525
|
});
|
|
6242
7526
|
}
|
|
6243
7527
|
}
|
|
@@ -6348,7 +7632,7 @@ async function handleSlashCommandForm(args) {
|
|
|
6348
7632
|
await withSpan(
|
|
6349
7633
|
"chat.slash_command",
|
|
6350
7634
|
"chat.slash_command",
|
|
6351
|
-
{
|
|
7635
|
+
{ userId },
|
|
6352
7636
|
async () => {
|
|
6353
7637
|
await handleSlashCommand({
|
|
6354
7638
|
adapter: args.adapter,
|
|
@@ -6387,7 +7671,7 @@ async function handleInteractivePayload(args) {
|
|
|
6387
7671
|
await withSpan(
|
|
6388
7672
|
"chat.app_home_disconnect",
|
|
6389
7673
|
"chat.app_home_disconnect",
|
|
6390
|
-
{
|
|
7674
|
+
{ userId },
|
|
6391
7675
|
async () => {
|
|
6392
7676
|
try {
|
|
6393
7677
|
await unlinkProvider(userId, provider, args.userTokenStore);
|
|
@@ -6395,7 +7679,7 @@ async function handleInteractivePayload(args) {
|
|
|
6395
7679
|
logException(
|
|
6396
7680
|
error,
|
|
6397
7681
|
"app_home_disconnect_unlink_failed",
|
|
6398
|
-
{
|
|
7682
|
+
{ userId },
|
|
6399
7683
|
{ "app.credential.provider": provider }
|
|
6400
7684
|
);
|
|
6401
7685
|
}
|
|
@@ -6405,7 +7689,7 @@ async function handleInteractivePayload(args) {
|
|
|
6405
7689
|
logException(
|
|
6406
7690
|
error,
|
|
6407
7691
|
"app_home_disconnect_publish_failed",
|
|
6408
|
-
{
|
|
7692
|
+
{ userId },
|
|
6409
7693
|
{ "app.credential.provider": provider }
|
|
6410
7694
|
);
|
|
6411
7695
|
}
|
|
@@ -6448,7 +7732,7 @@ async function handleSlackForm(args) {
|
|
|
6448
7732
|
})
|
|
6449
7733
|
).catch((error) => {
|
|
6450
7734
|
logException(error, "slash_command_failed", {
|
|
6451
|
-
|
|
7735
|
+
userId: params.get("user_id") ?? void 0
|
|
6452
7736
|
});
|
|
6453
7737
|
})
|
|
6454
7738
|
);
|
|
@@ -6478,7 +7762,7 @@ async function handleSlackForm(args) {
|
|
|
6478
7762
|
})
|
|
6479
7763
|
).catch((error) => {
|
|
6480
7764
|
logException(error, "slack_interactive_payload_failed", {
|
|
6481
|
-
|
|
7765
|
+
userId: payload.user?.id?.trim() || void 0
|
|
6482
7766
|
});
|
|
6483
7767
|
})
|
|
6484
7768
|
);
|
|
@@ -6628,7 +7912,7 @@ function isConversationQueueMessageRejectedError(error) {
|
|
|
6628
7912
|
// src/chat/task-execution/worker.ts
|
|
6629
7913
|
var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
|
|
6630
7914
|
var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
|
|
6631
|
-
function
|
|
7915
|
+
function now3(options) {
|
|
6632
7916
|
return options.nowMs?.() ?? Date.now();
|
|
6633
7917
|
}
|
|
6634
7918
|
function nudgeIdempotencyKey(reason, conversationId, nowMs) {
|
|
@@ -6699,7 +7983,7 @@ function isTerminalFailure(failure) {
|
|
|
6699
7983
|
}
|
|
6700
7984
|
function startLeaseCheckIn(args) {
|
|
6701
7985
|
const timer = setInterval(() => {
|
|
6702
|
-
const nowMs =
|
|
7986
|
+
const nowMs = now3(args.options);
|
|
6703
7987
|
void checkInConversationWork2({
|
|
6704
7988
|
conversationId: args.conversationId,
|
|
6705
7989
|
leaseToken: args.leaseToken,
|
|
@@ -6755,7 +8039,7 @@ async function processConversationWork(message, options) {
|
|
|
6755
8039
|
await clearConsumedConversationWake2({
|
|
6756
8040
|
conversationId,
|
|
6757
8041
|
conversationStore: options.conversationStore,
|
|
6758
|
-
nowMs:
|
|
8042
|
+
nowMs: now3(options),
|
|
6759
8043
|
state: options.state
|
|
6760
8044
|
});
|
|
6761
8045
|
}
|
|
@@ -6772,20 +8056,20 @@ async function processConversationWork(message, options) {
|
|
|
6772
8056
|
const lease = await startConversationWork2({
|
|
6773
8057
|
conversationId,
|
|
6774
8058
|
conversationStore: options.conversationStore,
|
|
6775
|
-
nowMs:
|
|
8059
|
+
nowMs: now3(options),
|
|
6776
8060
|
state: options.state
|
|
6777
8061
|
});
|
|
6778
8062
|
if (lease.status === "no_work") {
|
|
6779
8063
|
await clearConsumedConversationWake2({
|
|
6780
8064
|
conversationId,
|
|
6781
8065
|
conversationStore: options.conversationStore,
|
|
6782
|
-
nowMs:
|
|
8066
|
+
nowMs: now3(options),
|
|
6783
8067
|
state: options.state
|
|
6784
8068
|
});
|
|
6785
8069
|
return { status: "no_work" };
|
|
6786
8070
|
}
|
|
6787
8071
|
if (lease.status === "active") {
|
|
6788
|
-
const nudgeNowMs =
|
|
8072
|
+
const nudgeNowMs = now3(options);
|
|
6789
8073
|
await ensureConversationWake({
|
|
6790
8074
|
conversationId,
|
|
6791
8075
|
conversationStore: options.conversationStore,
|
|
@@ -6806,7 +8090,7 @@ async function processConversationWork(message, options) {
|
|
|
6806
8090
|
);
|
|
6807
8091
|
return { status: "active" };
|
|
6808
8092
|
}
|
|
6809
|
-
const startedAtMs =
|
|
8093
|
+
const startedAtMs = now3(options);
|
|
6810
8094
|
const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
|
|
6811
8095
|
const leasedWork = await getConversationWorkState2({
|
|
6812
8096
|
conversationId,
|
|
@@ -6840,7 +8124,7 @@ async function processConversationWork(message, options) {
|
|
|
6840
8124
|
leaseToken: lease.leaseToken,
|
|
6841
8125
|
conversationStore: options.conversationStore,
|
|
6842
8126
|
handle,
|
|
6843
|
-
nowMs:
|
|
8127
|
+
nowMs: now3(options),
|
|
6844
8128
|
state: options.state
|
|
6845
8129
|
});
|
|
6846
8130
|
const ack = async () => {
|
|
@@ -6849,7 +8133,7 @@ async function processConversationWork(message, options) {
|
|
|
6849
8133
|
inboundMessageIds: attemptMessageIds,
|
|
6850
8134
|
leaseToken: lease.leaseToken,
|
|
6851
8135
|
conversationStore: options.conversationStore,
|
|
6852
|
-
nowMs:
|
|
8136
|
+
nowMs: now3(options),
|
|
6853
8137
|
state: options.state
|
|
6854
8138
|
});
|
|
6855
8139
|
if (!acknowledged) {
|
|
@@ -6872,13 +8156,13 @@ async function processConversationWork(message, options) {
|
|
|
6872
8156
|
},
|
|
6873
8157
|
conversationId,
|
|
6874
8158
|
destination,
|
|
6875
|
-
shouldYield: () => leaseLost ||
|
|
8159
|
+
shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
|
|
6876
8160
|
checkIn: async () => {
|
|
6877
8161
|
const checkedIn = await checkInConversationWork2({
|
|
6878
8162
|
conversationId,
|
|
6879
8163
|
leaseToken: lease.leaseToken,
|
|
6880
8164
|
conversationStore: options.conversationStore,
|
|
6881
|
-
nowMs:
|
|
8165
|
+
nowMs: now3(options),
|
|
6882
8166
|
state: options.state
|
|
6883
8167
|
});
|
|
6884
8168
|
if (!checkedIn) {
|
|
@@ -6894,7 +8178,7 @@ async function processConversationWork(message, options) {
|
|
|
6894
8178
|
conversationId,
|
|
6895
8179
|
destination,
|
|
6896
8180
|
leaseToken: lease.leaseToken,
|
|
6897
|
-
nowMs:
|
|
8181
|
+
nowMs: now3(options),
|
|
6898
8182
|
options
|
|
6899
8183
|
});
|
|
6900
8184
|
return { status: "lost_lease" };
|
|
@@ -6904,13 +8188,13 @@ async function processConversationWork(message, options) {
|
|
|
6904
8188
|
conversationId,
|
|
6905
8189
|
destination,
|
|
6906
8190
|
leaseToken: lease.leaseToken,
|
|
6907
|
-
nowMs:
|
|
8191
|
+
nowMs: now3(options),
|
|
6908
8192
|
options
|
|
6909
8193
|
});
|
|
6910
8194
|
return { status: "lost_lease" };
|
|
6911
8195
|
}
|
|
6912
8196
|
if (result.status === "yielded") {
|
|
6913
|
-
const yieldNowMs =
|
|
8197
|
+
const yieldNowMs = now3(options);
|
|
6914
8198
|
const continuationMarked = await requestConversationContinuation2({
|
|
6915
8199
|
conversationId,
|
|
6916
8200
|
destination,
|
|
@@ -6945,7 +8229,7 @@ async function processConversationWork(message, options) {
|
|
|
6945
8229
|
"conversation_work_cooperative_yield",
|
|
6946
8230
|
{ conversationId },
|
|
6947
8231
|
{
|
|
6948
|
-
"app.worker.elapsed_ms":
|
|
8232
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs,
|
|
6949
8233
|
"app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
|
|
6950
8234
|
},
|
|
6951
8235
|
"Conversation work yielded cooperatively"
|
|
@@ -6953,7 +8237,7 @@ async function processConversationWork(message, options) {
|
|
|
6953
8237
|
return { status: "yielded" };
|
|
6954
8238
|
}
|
|
6955
8239
|
if (result.status === "deferred") {
|
|
6956
|
-
const deferredNowMs =
|
|
8240
|
+
const deferredNowMs = now3(options);
|
|
6957
8241
|
const released = await releaseConversationWork2({
|
|
6958
8242
|
conversationId,
|
|
6959
8243
|
leaseToken: lease.leaseToken,
|
|
@@ -6982,7 +8266,7 @@ async function processConversationWork(message, options) {
|
|
|
6982
8266
|
const failure = await recordFailedDeliveryAttempt({
|
|
6983
8267
|
conversationId,
|
|
6984
8268
|
leaseToken: lease.leaseToken,
|
|
6985
|
-
nowMs:
|
|
8269
|
+
nowMs: now3(options),
|
|
6986
8270
|
messageIds: attemptMessageIds,
|
|
6987
8271
|
options
|
|
6988
8272
|
});
|
|
@@ -6991,7 +8275,7 @@ async function processConversationWork(message, options) {
|
|
|
6991
8275
|
conversationId,
|
|
6992
8276
|
leaseToken: lease.leaseToken,
|
|
6993
8277
|
conversationStore: options.conversationStore,
|
|
6994
|
-
nowMs:
|
|
8278
|
+
nowMs: now3(options),
|
|
6995
8279
|
state: options.state
|
|
6996
8280
|
});
|
|
6997
8281
|
return { status: "failed" };
|
|
@@ -7001,14 +8285,14 @@ async function processConversationWork(message, options) {
|
|
|
7001
8285
|
conversationId,
|
|
7002
8286
|
leaseToken: lease.leaseToken,
|
|
7003
8287
|
conversationStore: options.conversationStore,
|
|
7004
|
-
nowMs:
|
|
8288
|
+
nowMs: now3(options),
|
|
7005
8289
|
state: options.state
|
|
7006
8290
|
});
|
|
7007
8291
|
if (completion === "lost_lease") {
|
|
7008
8292
|
return { status: "lost_lease" };
|
|
7009
8293
|
}
|
|
7010
8294
|
if (completion === "pending") {
|
|
7011
|
-
const nudgeNowMs =
|
|
8295
|
+
const nudgeNowMs = now3(options);
|
|
7012
8296
|
const wake = await ensureConversationWake({
|
|
7013
8297
|
conversationId,
|
|
7014
8298
|
conversationStore: options.conversationStore,
|
|
@@ -7027,13 +8311,13 @@ async function processConversationWork(message, options) {
|
|
|
7027
8311
|
"conversation_work_completed",
|
|
7028
8312
|
{ conversationId },
|
|
7029
8313
|
{
|
|
7030
|
-
"app.worker.elapsed_ms":
|
|
8314
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs
|
|
7031
8315
|
},
|
|
7032
8316
|
"Conversation work completed"
|
|
7033
8317
|
);
|
|
7034
8318
|
return { status: "completed" };
|
|
7035
8319
|
} catch (error) {
|
|
7036
|
-
const errorNowMs =
|
|
8320
|
+
const errorNowMs = now3(options);
|
|
7037
8321
|
let recoveryRecorded = false;
|
|
7038
8322
|
try {
|
|
7039
8323
|
const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
|
|
@@ -7098,7 +8382,7 @@ async function processConversationWork(message, options) {
|
|
|
7098
8382
|
"conversation_work_failed",
|
|
7099
8383
|
{ conversationId },
|
|
7100
8384
|
{
|
|
7101
|
-
"app.worker.elapsed_ms":
|
|
8385
|
+
"app.worker.elapsed_ms": now3(options) - startedAtMs
|
|
7102
8386
|
},
|
|
7103
8387
|
"Conversation work failed"
|
|
7104
8388
|
);
|
|
@@ -7279,14 +8563,14 @@ function registerVercelPluginTaskDevConsumer() {
|
|
|
7279
8563
|
}
|
|
7280
8564
|
|
|
7281
8565
|
// src/chat/services/subscribed-decision.ts
|
|
7282
|
-
import { z as
|
|
7283
|
-
var replyDecisionSchema =
|
|
7284
|
-
should_reply:
|
|
7285
|
-
should_unsubscribe:
|
|
8566
|
+
import { z as z4 } from "zod";
|
|
8567
|
+
var replyDecisionSchema = z4.object({
|
|
8568
|
+
should_reply: z4.boolean().describe("Whether Junior should respond to this thread message."),
|
|
8569
|
+
should_unsubscribe: z4.boolean().describe(
|
|
7286
8570
|
"Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
|
|
7287
8571
|
),
|
|
7288
|
-
confidence:
|
|
7289
|
-
reason:
|
|
8572
|
+
confidence: z4.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
|
|
8573
|
+
reason: z4.string().describe("Short reason for the decision; use an empty string if none.")
|
|
7290
8574
|
}).strict();
|
|
7291
8575
|
var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
|
|
7292
8576
|
var ROUTER_CLASSIFIER_MAX_TOKENS = 240;
|
|
@@ -7632,15 +8916,6 @@ var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone
|
|
|
7632
8916
|
function shouldRethrowTurnControlError(error) {
|
|
7633
8917
|
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isTurnInputDeferredError(error) || isProviderRetryError(error);
|
|
7634
8918
|
}
|
|
7635
|
-
async function maybeHandleThreadOptOutDecision(args) {
|
|
7636
|
-
if (!args.decision?.shouldUnsubscribe) {
|
|
7637
|
-
return false;
|
|
7638
|
-
}
|
|
7639
|
-
await args.thread.unsubscribe();
|
|
7640
|
-
await args.beforeFirstResponsePost?.();
|
|
7641
|
-
await args.thread.post(THREAD_OPTOUT_ACK);
|
|
7642
|
-
return true;
|
|
7643
|
-
}
|
|
7644
8919
|
function getQueuedMessages(context, options) {
|
|
7645
8920
|
return (context?.skipped ?? []).map((message) => {
|
|
7646
8921
|
const stripped = options.stripLeadingBotMention(message.text, {
|
|
@@ -7687,10 +8962,10 @@ function createAcceptedSteeringDrain(hooks, options) {
|
|
|
7687
8962
|
function buildLogContext(deps, args) {
|
|
7688
8963
|
return {
|
|
7689
8964
|
conversationId: args.threadId ?? args.runId,
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
8965
|
+
messageConversationId: args.threadId,
|
|
8966
|
+
userId: args.actorId,
|
|
8967
|
+
userName: args.actorUserName,
|
|
8968
|
+
destinationName: args.channelId,
|
|
7694
8969
|
runId: args.runId,
|
|
7695
8970
|
assistantUserName: deps.assistantUserName,
|
|
7696
8971
|
modelId: deps.modelId
|
|
@@ -7705,6 +8980,16 @@ function isResourceEventNotificationMessage2(message) {
|
|
|
7705
8980
|
}
|
|
7706
8981
|
function createSlackTurnRuntime(deps) {
|
|
7707
8982
|
const logContext = (args) => buildLogContext(deps, args);
|
|
8983
|
+
const maybeHandleThreadOptOutDecision = async (args) => {
|
|
8984
|
+
if (!args.decision?.shouldUnsubscribe) {
|
|
8985
|
+
return false;
|
|
8986
|
+
}
|
|
8987
|
+
await deps.cancelEventSubscriptions({ conversationId: args.thread.id });
|
|
8988
|
+
await args.thread.unsubscribe();
|
|
8989
|
+
await args.beforeFirstResponsePost?.();
|
|
8990
|
+
await args.thread.post(THREAD_OPTOUT_ACK);
|
|
8991
|
+
return true;
|
|
8992
|
+
};
|
|
7708
8993
|
const failConversationTurn = async (args) => {
|
|
7709
8994
|
const conversationId = deps.getThreadId(args.thread, args.message) ?? deps.getRunId(args.thread, args.message);
|
|
7710
8995
|
if (!conversationId) {
|
|
@@ -8399,9 +9684,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8399
9684
|
error,
|
|
8400
9685
|
"assistant_thread_started_handler_failed",
|
|
8401
9686
|
{
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
9687
|
+
messageConversationId: event.threadId,
|
|
9688
|
+
userId: event.userId,
|
|
9689
|
+
destinationName: event.channelId,
|
|
8405
9690
|
assistantUserName: deps.assistantUserName,
|
|
8406
9691
|
modelId: deps.modelId
|
|
8407
9692
|
},
|
|
@@ -8423,9 +9708,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8423
9708
|
error,
|
|
8424
9709
|
"assistant_context_changed_handler_failed",
|
|
8425
9710
|
{
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
9711
|
+
messageConversationId: event.threadId,
|
|
9712
|
+
userId: event.userId,
|
|
9713
|
+
destinationName: event.channelId,
|
|
8429
9714
|
assistantUserName: deps.assistantUserName,
|
|
8430
9715
|
modelId: deps.modelId
|
|
8431
9716
|
},
|
|
@@ -8449,9 +9734,9 @@ function createSubscribedReplyPolicy(deps) {
|
|
|
8449
9734
|
logWarn(
|
|
8450
9735
|
"subscribed_message_classifier_failed",
|
|
8451
9736
|
{
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
9737
|
+
messageConversationId: input.context.threadId,
|
|
9738
|
+
userId: input.context.actorId,
|
|
9739
|
+
destinationName: input.context.channelId,
|
|
8455
9740
|
runId: input.context.runId,
|
|
8456
9741
|
assistantUserName: botConfig.userName,
|
|
8457
9742
|
modelId: botConfig.fastModelId
|
|
@@ -8660,9 +9945,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8660
9945
|
logWarn(
|
|
8661
9946
|
"attachment_skipped_size_limit",
|
|
8662
9947
|
{
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
9948
|
+
messageConversationId: context.threadId,
|
|
9949
|
+
userId: context.actorId,
|
|
9950
|
+
destinationName: context.channelId,
|
|
8666
9951
|
runId: context.runId,
|
|
8667
9952
|
assistantUserName: botConfig.userName,
|
|
8668
9953
|
modelId: standardModelId(botConfig)
|
|
@@ -8685,9 +9970,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8685
9970
|
logWarn(
|
|
8686
9971
|
"image_attachment_processing_failed",
|
|
8687
9972
|
{
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
9973
|
+
messageConversationId: context.threadId,
|
|
9974
|
+
userId: context.actorId,
|
|
9975
|
+
destinationName: context.channelId,
|
|
8691
9976
|
runId: context.runId,
|
|
8692
9977
|
assistantUserName: botConfig.userName,
|
|
8693
9978
|
modelId: botConfig.visionModelId ?? standardModelId(botConfig)
|
|
@@ -8704,9 +9989,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
8704
9989
|
logWarn(
|
|
8705
9990
|
"attachment_resolution_failed",
|
|
8706
9991
|
{
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
9992
|
+
messageConversationId: context.threadId,
|
|
9993
|
+
userId: context.actorId,
|
|
9994
|
+
destinationName: context.channelId,
|
|
8710
9995
|
runId: context.runId,
|
|
8711
9996
|
assistantUserName: botConfig.userName,
|
|
8712
9997
|
modelId: standardModelId(botConfig)
|
|
@@ -8754,9 +10039,9 @@ async function summarizeConversationImage(args, deps) {
|
|
|
8754
10039
|
logWarn(
|
|
8755
10040
|
"conversation_image_vision_failed",
|
|
8756
10041
|
{
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
10042
|
+
messageConversationId: args.context.threadId,
|
|
10043
|
+
userId: args.context.actorId,
|
|
10044
|
+
destinationName: args.context.channelId,
|
|
8760
10045
|
runId: args.context.runId,
|
|
8761
10046
|
assistantUserName: botConfig.userName,
|
|
8762
10047
|
modelId: visionModelId
|
|
@@ -8810,9 +10095,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
8810
10095
|
logWarn(
|
|
8811
10096
|
"conversation_image_replies_fetch_failed",
|
|
8812
10097
|
{
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
10098
|
+
messageConversationId: context.threadId,
|
|
10099
|
+
userId: context.actorId,
|
|
10100
|
+
destinationName: context.channelId,
|
|
8816
10101
|
runId: context.runId,
|
|
8817
10102
|
assistantUserName: botConfig.userName,
|
|
8818
10103
|
modelId: standardModelId(botConfig)
|
|
@@ -8877,9 +10162,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
8877
10162
|
logWarn(
|
|
8878
10163
|
"conversation_image_skipped_size_limit",
|
|
8879
10164
|
{
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
10165
|
+
messageConversationId: context.threadId,
|
|
10166
|
+
userId: context.actorId,
|
|
10167
|
+
destinationName: context.channelId,
|
|
8883
10168
|
runId: context.runId,
|
|
8884
10169
|
assistantUserName: botConfig.userName,
|
|
8885
10170
|
modelId: standardModelId(botConfig)
|
|
@@ -8904,9 +10189,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
8904
10189
|
logWarn(
|
|
8905
10190
|
"conversation_image_download_failed",
|
|
8906
10191
|
{
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
10192
|
+
messageConversationId: context.threadId,
|
|
10193
|
+
userId: context.actorId,
|
|
10194
|
+
destinationName: context.channelId,
|
|
8910
10195
|
runId: context.runId,
|
|
8911
10196
|
assistantUserName: botConfig.userName,
|
|
8912
10197
|
modelId: standardModelId(botConfig)
|
|
@@ -8924,9 +10209,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
8924
10209
|
logWarn(
|
|
8925
10210
|
"conversation_image_skipped_size_limit",
|
|
8926
10211
|
{
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
10212
|
+
messageConversationId: context.threadId,
|
|
10213
|
+
userId: context.actorId,
|
|
10214
|
+
destinationName: context.channelId,
|
|
8930
10215
|
runId: context.runId,
|
|
8931
10216
|
assistantUserName: botConfig.userName,
|
|
8932
10217
|
modelId: standardModelId(botConfig)
|
|
@@ -8967,9 +10252,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
8967
10252
|
logInfo(
|
|
8968
10253
|
"conversation_image_context_hydrated",
|
|
8969
10254
|
{
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
10255
|
+
messageConversationId: context.threadId,
|
|
10256
|
+
userId: context.actorId,
|
|
10257
|
+
destinationName: context.channelId,
|
|
8973
10258
|
runId: context.runId,
|
|
8974
10259
|
assistantUserName: botConfig.userName,
|
|
8975
10260
|
modelId: standardModelId(botConfig)
|
|
@@ -9052,11 +10337,11 @@ function getSlackMessageTs(message) {
|
|
|
9052
10337
|
}
|
|
9053
10338
|
|
|
9054
10339
|
// src/chat/slack/action-token.ts
|
|
9055
|
-
import { z as
|
|
9056
|
-
var slackActionTokenSchema =
|
|
9057
|
-
var slackMessageEnvelopeSchema =
|
|
9058
|
-
raw:
|
|
9059
|
-
action_token:
|
|
10340
|
+
import { z as z5 } from "zod";
|
|
10341
|
+
var slackActionTokenSchema = z5.string().trim().min(1).brand();
|
|
10342
|
+
var slackMessageEnvelopeSchema = z5.object({
|
|
10343
|
+
raw: z5.object({
|
|
10344
|
+
action_token: z5.unknown().optional()
|
|
9060
10345
|
}).optional()
|
|
9061
10346
|
});
|
|
9062
10347
|
function readSlackActionToken(message) {
|
|
@@ -9092,9 +10377,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9092
10377
|
logWarn(
|
|
9093
10378
|
"thread_title_generation_failed",
|
|
9094
10379
|
{
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
10380
|
+
messageConversationId: args.threadId,
|
|
10381
|
+
userId: args.actorId,
|
|
10382
|
+
destinationName: args.channelId,
|
|
9098
10383
|
runId: args.runId,
|
|
9099
10384
|
assistantUserName: args.assistantUserName,
|
|
9100
10385
|
modelId: args.modelId
|
|
@@ -9126,9 +10411,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9126
10411
|
logError(
|
|
9127
10412
|
"thread_title_generation_permission_denied",
|
|
9128
10413
|
{
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
10414
|
+
messageConversationId: args.threadId,
|
|
10415
|
+
userId: args.actorId,
|
|
10416
|
+
destinationName: args.channelId,
|
|
9132
10417
|
runId: args.runId,
|
|
9133
10418
|
assistantUserName: args.assistantUserName,
|
|
9134
10419
|
modelId: args.modelId
|
|
@@ -9140,9 +10425,9 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
9140
10425
|
logWarn(
|
|
9141
10426
|
"thread_title_slack_update_failed",
|
|
9142
10427
|
{
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
10428
|
+
messageConversationId: args.threadId,
|
|
10429
|
+
userId: args.actorId,
|
|
10430
|
+
destinationName: args.channelId,
|
|
9146
10431
|
runId: args.runId,
|
|
9147
10432
|
assistantUserName: args.assistantUserName,
|
|
9148
10433
|
modelId: args.modelId
|
|
@@ -9374,9 +10659,9 @@ function createReplyToThread(deps) {
|
|
|
9374
10659
|
"chat.reply",
|
|
9375
10660
|
{
|
|
9376
10661
|
conversationId,
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
10662
|
+
messageConversationId: threadId,
|
|
10663
|
+
userId: message.author.userId,
|
|
10664
|
+
destinationName: channelId,
|
|
9380
10665
|
runId,
|
|
9381
10666
|
assistantUserName: botConfig.userName,
|
|
9382
10667
|
modelId: standardModelId(botConfig)
|
|
@@ -9432,9 +10717,9 @@ function createReplyToThread(deps) {
|
|
|
9432
10717
|
const turnId = buildDeterministicTurnId(message.id);
|
|
9433
10718
|
const turnTraceContext = {
|
|
9434
10719
|
conversationId,
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
10720
|
+
messageConversationId: threadId,
|
|
10721
|
+
userId: message.author.userId,
|
|
10722
|
+
destinationName: channelId,
|
|
9438
10723
|
runId,
|
|
9439
10724
|
assistantUserName: botConfig.userName,
|
|
9440
10725
|
modelId: standardModelId(botConfig)
|
|
@@ -9455,23 +10740,16 @@ function createReplyToThread(deps) {
|
|
|
9455
10740
|
actor.userId,
|
|
9456
10741
|
providerDisplayName
|
|
9457
10742
|
);
|
|
9458
|
-
const footer = buildSlackReplyFooter({ conversationId });
|
|
9459
10743
|
try {
|
|
10744
|
+
await beforeFirstResponsePost();
|
|
9460
10745
|
if (channelId && threadTs) {
|
|
9461
|
-
await
|
|
9462
|
-
beforePost: beforeFirstResponsePost,
|
|
10746
|
+
await sendSlackReply({
|
|
9463
10747
|
channelId,
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
text,
|
|
9468
|
-
stage: "thread_reply"
|
|
9469
|
-
}
|
|
9470
|
-
],
|
|
9471
|
-
footer
|
|
10748
|
+
conversationId,
|
|
10749
|
+
text,
|
|
10750
|
+
threadTs
|
|
9472
10751
|
});
|
|
9473
10752
|
} else {
|
|
9474
|
-
await beforeFirstResponsePost();
|
|
9475
10753
|
await thread.post(buildSlackOutputMessage(text));
|
|
9476
10754
|
}
|
|
9477
10755
|
} catch (error) {
|
|
@@ -9767,7 +11045,7 @@ function createReplyToThread(deps) {
|
|
|
9767
11045
|
});
|
|
9768
11046
|
}
|
|
9769
11047
|
if (actor?.userName) {
|
|
9770
|
-
setTags({
|
|
11048
|
+
setTags({ userName: actor.userName });
|
|
9771
11049
|
}
|
|
9772
11050
|
const turnAttachments = collectTurnAttachments(
|
|
9773
11051
|
message,
|
|
@@ -9793,32 +11071,6 @@ function createReplyToThread(deps) {
|
|
|
9793
11071
|
const compactingStatus = {
|
|
9794
11072
|
text: "Compacting context"
|
|
9795
11073
|
};
|
|
9796
|
-
const postThreadReply = async (payload, stage) => {
|
|
9797
|
-
await beforeFirstResponsePost();
|
|
9798
|
-
try {
|
|
9799
|
-
return await thread.post(payload);
|
|
9800
|
-
} catch (error) {
|
|
9801
|
-
if (isRetryableSlackPostError(error)) {
|
|
9802
|
-
throw new RetryableDeliveryError(error);
|
|
9803
|
-
}
|
|
9804
|
-
const eventId = logException(
|
|
9805
|
-
error,
|
|
9806
|
-
"slack_thread_post_failed",
|
|
9807
|
-
turnTraceContext,
|
|
9808
|
-
{
|
|
9809
|
-
"app.slack.reply_stage": stage,
|
|
9810
|
-
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
9811
|
-
...getSlackErrorObservabilityAttributes(error)
|
|
9812
|
-
},
|
|
9813
|
-
"Failed to post Slack thread reply"
|
|
9814
|
-
);
|
|
9815
|
-
throw new ConversationTurnBoundaryError({
|
|
9816
|
-
cause: error,
|
|
9817
|
-
...eventId ? { eventId } : {},
|
|
9818
|
-
failureCode: "delivery_failed"
|
|
9819
|
-
});
|
|
9820
|
-
}
|
|
9821
|
-
};
|
|
9822
11074
|
let persistedAtLeastOnce = false;
|
|
9823
11075
|
let shouldPersistFailureState = true;
|
|
9824
11076
|
let runResultHandled = false;
|
|
@@ -9838,18 +11090,45 @@ function createReplyToThread(deps) {
|
|
|
9838
11090
|
turnCompletionNotified = true;
|
|
9839
11091
|
};
|
|
9840
11092
|
const deliverAssistantMessage = async (assistantMessage) => {
|
|
9841
|
-
|
|
9842
|
-
if (posts.length === 0) {
|
|
11093
|
+
if (!assistantMessage.text.trim()) {
|
|
9843
11094
|
return;
|
|
9844
11095
|
}
|
|
9845
11096
|
boundaryFailureCode = "delivery_failed";
|
|
9846
11097
|
let slackTs;
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
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"
|
|
9851
11126
|
);
|
|
9852
|
-
|
|
11127
|
+
throw new ConversationTurnBoundaryError({
|
|
11128
|
+
cause: error,
|
|
11129
|
+
...eventId ? { eventId } : {},
|
|
11130
|
+
failureCode: "delivery_failed"
|
|
11131
|
+
});
|
|
9853
11132
|
}
|
|
9854
11133
|
assistantMessageDelivered = true;
|
|
9855
11134
|
const recordedMessageId = recordDeliveredAssistantMessage({
|
|
@@ -10181,9 +11460,9 @@ function createReplyToThread(deps) {
|
|
|
10181
11460
|
}
|
|
10182
11461
|
let reply = outcome.result;
|
|
10183
11462
|
const diagnosticsContext = {
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
11463
|
+
messageConversationId: threadId,
|
|
11464
|
+
userId: message.author.userId,
|
|
11465
|
+
destinationName: channelId,
|
|
10187
11466
|
runId,
|
|
10188
11467
|
assistantUserName: botConfig.userName,
|
|
10189
11468
|
modelId: reply.diagnostics.modelId
|
|
@@ -10233,13 +11512,6 @@ function createReplyToThread(deps) {
|
|
|
10233
11512
|
sliceId: 1,
|
|
10234
11513
|
messages: reply.piMessages,
|
|
10235
11514
|
modelId: reply.diagnostics.modelId,
|
|
10236
|
-
logContext: {
|
|
10237
|
-
threadId,
|
|
10238
|
-
actorId: slackActorId,
|
|
10239
|
-
channelId,
|
|
10240
|
-
runId,
|
|
10241
|
-
assistantUserName: botConfig.userName
|
|
10242
|
-
},
|
|
10243
11515
|
actor: executionActor,
|
|
10244
11516
|
surface: "slack"
|
|
10245
11517
|
});
|
|
@@ -10526,6 +11798,11 @@ async function refreshAssistantThreadContext(event) {
|
|
|
10526
11798
|
|
|
10527
11799
|
// src/chat/runtime/conversation-message.ts
|
|
10528
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
|
+
}
|
|
10529
11806
|
function resolveMessageText(args) {
|
|
10530
11807
|
const text = normalizeConversationText(args.text);
|
|
10531
11808
|
return text || NON_TEXT_MESSAGE_TEXT;
|
|
@@ -10550,6 +11827,7 @@ function toConversationMessage(args) {
|
|
|
10550
11827
|
},
|
|
10551
11828
|
meta: {
|
|
10552
11829
|
attachmentCount: args.entry.attachments.length,
|
|
11830
|
+
eventType: resourceEventType(args.entry),
|
|
10553
11831
|
explicitMention: args.explicitMention,
|
|
10554
11832
|
imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
|
|
10555
11833
|
imagesHydrated: !messageHasPotentialImageAttachment,
|
|
@@ -10766,6 +12044,7 @@ function createSlackRuntime(options) {
|
|
|
10766
12044
|
});
|
|
10767
12045
|
return createSlackTurnRuntime({
|
|
10768
12046
|
assistantUserName: botConfig.userName,
|
|
12047
|
+
cancelEventSubscriptions: cancelSubscriptions,
|
|
10769
12048
|
modelId: standardModelId(botConfig),
|
|
10770
12049
|
now: options.now ?? (() => Date.now()),
|
|
10771
12050
|
getThreadId,
|
|
@@ -11155,7 +12434,7 @@ async function continueSlackAgentRun(payload, options) {
|
|
|
11155
12434
|
}
|
|
11156
12435
|
}
|
|
11157
12436
|
},
|
|
11158
|
-
|
|
12437
|
+
commitResult: async (reply) => {
|
|
11159
12438
|
await persistCompletedReplyState2({
|
|
11160
12439
|
sessionRecord: activeSessionRecord,
|
|
11161
12440
|
reply
|
|
@@ -11482,6 +12761,9 @@ function renderResourceEventNotificationText(subscription, event) {
|
|
|
11482
12761
|
"Handling:",
|
|
11483
12762
|
"- This is a subscribed conversation update, not a user-authored command.",
|
|
11484
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.",
|
|
11485
12767
|
"",
|
|
11486
12768
|
"Subscription:",
|
|
11487
12769
|
`- resource: ${subscription.label}`,
|
|
@@ -11988,6 +13270,7 @@ async function createApp(options) {
|
|
|
11988
13270
|
return app;
|
|
11989
13271
|
}
|
|
11990
13272
|
export {
|
|
13273
|
+
JUNIOR_VERSION,
|
|
11991
13274
|
createApp,
|
|
11992
13275
|
defineJuniorPlugins
|
|
11993
13276
|
};
|