@sentry/junior 0.85.0 → 0.87.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-AV7CHLUO.js → agent-hooks-NWOUB3UR.js} +7 -7
- package/dist/app.js +779 -356
- package/dist/chat/conversation-privacy.d.ts +12 -2
- package/dist/chat/conversations/sql/migrations.d.ts +1 -1
- package/dist/chat/conversations/sql/store.d.ts +9 -1
- package/dist/chat/conversations/store.d.ts +13 -0
- package/dist/chat/ingress/message-changed.d.ts +2 -0
- package/dist/chat/no-reply.d.ts +7 -0
- package/dist/chat/respond.d.ts +7 -1
- package/dist/chat/runtime/reply-executor.d.ts +2 -10
- package/dist/chat/runtime/slack-runtime.d.ts +5 -4
- package/dist/chat/runtime/turn.d.ts +7 -0
- package/dist/chat/services/persist-retry.d.ts +2 -0
- package/dist/chat/services/reply-delivery-plan.d.ts +0 -4
- package/dist/chat/services/turn-session-record.d.ts +31 -2
- package/dist/chat/slack/client.d.ts +33 -2
- package/dist/chat/slack/conversation-context.d.ts +15 -0
- package/dist/chat/state/turn-session.d.ts +9 -0
- package/dist/chat/task-execution/queue.d.ts +1 -1
- package/dist/chat/task-execution/slack-work.d.ts +10 -2
- package/dist/chat/task-execution/state.d.ts +44 -4
- package/dist/chat/task-execution/store.d.ts +21 -4
- package/dist/chat/task-execution/worker.d.ts +11 -4
- package/dist/chat/tools/slack/channel-access.d.ts +29 -0
- package/dist/chat/tools/slack/thread-read.d.ts +4 -1
- package/dist/{chunk-UVNV67EC.js → chunk-2NFV5FMB.js} +4 -4
- package/dist/{chunk-QTTTRBNB.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-ENEWQMRJ.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-GY7L6VCT.js → chunk-ACJJJEGG.js} +35 -17
- package/dist/{chunk-MHEV3T2Y.js → chunk-B6FQPS7A.js} +953 -795
- package/dist/{chunk-MTE5NRPJ.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-EENOFKDN.js → chunk-ENPSU7L7.js} +165 -27
- package/dist/{chunk-RUB77TGS.js → chunk-GB5DFM4D.js} +1 -1
- package/dist/{chunk-C2PSUWK3.js → chunk-GGD6WK6V.js} +124 -25
- package/dist/{chunk-SSUEWAVS.js → chunk-JRXCSSSU.js} +19 -4
- package/dist/{chunk-DR75T7J3.js → chunk-L7OHKDOX.js} +9 -5
- package/dist/{chunk-FYPO4K7C.js → chunk-RIB3M6YA.js} +2 -2
- package/dist/{chunk-BU2AWPEN.js → chunk-ZU2ALUVQ.js} +3 -2
- package/dist/cli/chat.js +4 -4
- package/dist/cli/plugins.js +7 -7
- package/dist/cli/snapshot-warmup.js +4 -4
- package/dist/cli/upgrade.js +10 -7
- package/dist/{db-S3HYBLUS.js → db-SZVUU7RB.js} +4 -4
- package/dist/instrumentation.js +1 -1
- package/dist/nitro.js +4 -4
- package/dist/reporting/conversations.d.ts +2 -1
- package/dist/reporting.js +95 -30
- package/dist/{runner-IMQH6V2B.js → runner-DMP3IQNM.js} +27 -13
- package/package.json +6 -6
package/dist/app.js
CHANGED
|
@@ -3,27 +3,31 @@ import {
|
|
|
3
3
|
PluginCredentialFailureError,
|
|
4
4
|
SANDBOX_EGRESS_PROXY_PATH,
|
|
5
5
|
TurnInputCommitLostError,
|
|
6
|
+
TurnInputDeferredError,
|
|
6
7
|
addReactionToMessage,
|
|
7
8
|
appendSlackLegacyAttachmentText,
|
|
8
9
|
applyPendingAuthUpdate,
|
|
9
10
|
buildConversationContext,
|
|
10
11
|
buildDeliveredTurnStatePatch,
|
|
11
12
|
buildDeterministicTurnId,
|
|
13
|
+
buildSteeringPiMessage,
|
|
12
14
|
clearPendingAuth,
|
|
13
15
|
coerceThreadArtifactsState,
|
|
16
|
+
completeDeliveredTurn,
|
|
14
17
|
createConversationMemoryService,
|
|
15
18
|
createUserTokenStore,
|
|
16
19
|
deleteMcpAuthSession,
|
|
17
|
-
deleteSlackMessage,
|
|
18
20
|
deliverResourceEventSubscription,
|
|
19
21
|
estimateTextTokens,
|
|
20
22
|
executeCredentialedEgressRequest,
|
|
23
|
+
finalizeFailedTurnReply,
|
|
21
24
|
finalizeMcpAuthorization,
|
|
22
25
|
findMatchingResourceEventSubscriptions,
|
|
23
26
|
formatProviderLabel,
|
|
24
27
|
generateAssistantReply,
|
|
25
28
|
generateConversationId,
|
|
26
29
|
getAgentContextCompactionTriggerTokens,
|
|
30
|
+
getAgentTurnDiagnosticsAttributes,
|
|
27
31
|
getChannelConfigurationService,
|
|
28
32
|
getChannelConfigurationServiceById,
|
|
29
33
|
getConfigDefaults,
|
|
@@ -37,9 +41,9 @@ import {
|
|
|
37
41
|
isCooperativeTurnYieldError,
|
|
38
42
|
isHumanConversationMessage,
|
|
39
43
|
isPendingAuthLatestRequest,
|
|
40
|
-
isRedundantReactionAckText,
|
|
41
44
|
isRetryableTurnError,
|
|
42
45
|
isTurnInputCommitLostError,
|
|
46
|
+
isTurnInputDeferredError,
|
|
43
47
|
listThreadReplies,
|
|
44
48
|
markConversationMessage,
|
|
45
49
|
markTurnClosed,
|
|
@@ -49,12 +53,15 @@ import {
|
|
|
49
53
|
normalizeSandboxEgressTracePropagationDomains,
|
|
50
54
|
parseOAuthStatePayload,
|
|
51
55
|
parseSandboxEgressCredentialToken,
|
|
56
|
+
persistCompletedSessionRecord,
|
|
52
57
|
persistThreadState,
|
|
53
58
|
persistThreadStateById,
|
|
59
|
+
persistYieldSessionRecord,
|
|
54
60
|
postSlackMessage,
|
|
55
61
|
processPluginTask,
|
|
56
62
|
removeReactionFromMessage,
|
|
57
63
|
renderSlackLegacyAttachmentText,
|
|
64
|
+
requireTurnFailureEventId,
|
|
58
65
|
resolveBaseUrl,
|
|
59
66
|
resolvePluginOAuthAccount,
|
|
60
67
|
resolveSandboxEgressProviderForHost,
|
|
@@ -68,37 +75,41 @@ import {
|
|
|
68
75
|
updateConversationStats,
|
|
69
76
|
uploadFilesToThread,
|
|
70
77
|
upsertConversationMessage
|
|
71
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-B6FQPS7A.js";
|
|
72
79
|
import {
|
|
73
80
|
CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
|
|
74
81
|
CONVERSATION_WORK_STALE_ENQUEUE_MS,
|
|
82
|
+
ackMessages,
|
|
75
83
|
appendInboundMessage,
|
|
76
84
|
checkInConversationWork,
|
|
77
85
|
clearConsumedConversationWake,
|
|
78
86
|
clearExpiredConversationLease,
|
|
79
87
|
completeConversationWork,
|
|
88
|
+
deadLetterAttempt,
|
|
80
89
|
drainConversationMailbox,
|
|
81
90
|
getConversation,
|
|
82
91
|
getConversationWorkState,
|
|
92
|
+
isFinalAttempt,
|
|
93
|
+
isInvalidConversationRecordError,
|
|
83
94
|
listActiveConversationIds,
|
|
84
|
-
markConversationMessagesInjected,
|
|
85
95
|
markConversationWorkEnqueued,
|
|
96
|
+
recordAttemptFailure,
|
|
86
97
|
releaseConversationWork,
|
|
87
98
|
removeActiveConversation,
|
|
88
99
|
requestConversationContinuation,
|
|
89
100
|
requestConversationWork,
|
|
90
101
|
startConversationWork
|
|
91
|
-
} from "./chunk-
|
|
102
|
+
} from "./chunk-ENPSU7L7.js";
|
|
92
103
|
import {
|
|
93
104
|
JUNIOR_THREAD_STATE_TTL_MS,
|
|
94
105
|
coerceThreadConversationState
|
|
95
106
|
} from "./chunk-66NX7MNW.js";
|
|
96
|
-
import "./chunk-
|
|
107
|
+
import "./chunk-RIB3M6YA.js";
|
|
97
108
|
import {
|
|
98
109
|
getVercelConversationWorkQueue,
|
|
99
110
|
resolveConversationWorkQueueTopic,
|
|
100
111
|
verifyConversationQueueMessage
|
|
101
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-6O5UI3RG.js";
|
|
102
113
|
import {
|
|
103
114
|
PLUGIN_TASK_QUEUE_TOPIC,
|
|
104
115
|
createVercelQueueClient,
|
|
@@ -107,18 +118,18 @@ import {
|
|
|
107
118
|
import {
|
|
108
119
|
GET,
|
|
109
120
|
buildSentryConversationUrl,
|
|
121
|
+
conversationVisibilityFromSlackChannelType,
|
|
110
122
|
initConversationContext,
|
|
111
123
|
resolveSlackChannelTypeFromMessage,
|
|
112
124
|
resolveSlackConversationContext,
|
|
113
125
|
setConversationTitle
|
|
114
|
-
} from "./chunk-
|
|
126
|
+
} from "./chunk-JRXCSSSU.js";
|
|
115
127
|
import {
|
|
116
128
|
abandonAgentTurnSessionRecord,
|
|
117
129
|
buildSlackOutputMessage,
|
|
118
130
|
commitMessages,
|
|
119
131
|
failAgentTurnSessionRecord,
|
|
120
132
|
getAgentTurnSessionRecord,
|
|
121
|
-
getInterruptionMarker,
|
|
122
133
|
listAgentTurnSessionSummariesForConversation,
|
|
123
134
|
loadProjection,
|
|
124
135
|
normalizeSlackStatusText,
|
|
@@ -126,7 +137,7 @@ import {
|
|
|
126
137
|
recordAuthorizationCompleted,
|
|
127
138
|
splitSlackReplyText,
|
|
128
139
|
truncateStatusText
|
|
129
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-ACJJJEGG.js";
|
|
130
141
|
import {
|
|
131
142
|
validatePluginEgressCredentialHooks,
|
|
132
143
|
validatePluginRegistrations
|
|
@@ -146,20 +157,20 @@ import {
|
|
|
146
157
|
setPlugins,
|
|
147
158
|
validatePlugins,
|
|
148
159
|
verifySlackDirectCredentialSubject
|
|
149
|
-
} from "./chunk-
|
|
160
|
+
} from "./chunk-2NFV5FMB.js";
|
|
150
161
|
import {
|
|
151
162
|
createPluginLogger,
|
|
152
163
|
createPluginState
|
|
153
|
-
} from "./chunk-
|
|
164
|
+
} from "./chunk-BRSQQRG6.js";
|
|
154
165
|
import {
|
|
155
166
|
getConversationStore,
|
|
156
167
|
getDb
|
|
157
|
-
} from "./chunk-
|
|
168
|
+
} from "./chunk-6I6HBOQM.js";
|
|
158
169
|
import "./chunk-G3E7SCME.js";
|
|
159
170
|
import {
|
|
160
171
|
acquireActiveLock,
|
|
161
172
|
getStateAdapter
|
|
162
|
-
} from "./chunk-
|
|
173
|
+
} from "./chunk-GB5DFM4D.js";
|
|
163
174
|
import {
|
|
164
175
|
SlackActionError,
|
|
165
176
|
createSlackDestination,
|
|
@@ -167,15 +178,17 @@ import {
|
|
|
167
178
|
downloadPrivateSlackFile,
|
|
168
179
|
getHeaderString,
|
|
169
180
|
getSlackClient,
|
|
181
|
+
getWorkspaceTeamId,
|
|
170
182
|
isDmChannel,
|
|
171
183
|
normalizeSlackConversationId,
|
|
172
184
|
parseDestination,
|
|
173
185
|
requireSlackDestination,
|
|
186
|
+
runWithSlackInstallationToken,
|
|
187
|
+
runWithWorkspaceTeamId,
|
|
174
188
|
sameDestination
|
|
175
|
-
} from "./chunk-
|
|
189
|
+
} from "./chunk-GGD6WK6V.js";
|
|
176
190
|
import {
|
|
177
191
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
178
|
-
GEN_AI_PROVIDER_NAME,
|
|
179
192
|
botConfig,
|
|
180
193
|
completeObject,
|
|
181
194
|
completeText,
|
|
@@ -193,12 +206,12 @@ import {
|
|
|
193
206
|
stripRuntimeTurnContext,
|
|
194
207
|
trimTrailingAssistantMessages,
|
|
195
208
|
unwrapCurrentInstruction
|
|
196
|
-
} from "./chunk-
|
|
209
|
+
} from "./chunk-ZU2ALUVQ.js";
|
|
197
210
|
import {
|
|
198
211
|
parseSlackThreadId,
|
|
199
212
|
resolveSlackChannelIdFromMessage,
|
|
200
213
|
resolveSlackChannelIdFromThreadId
|
|
201
|
-
} from "./chunk-
|
|
214
|
+
} from "./chunk-L7OHKDOX.js";
|
|
202
215
|
import {
|
|
203
216
|
discoverSkills
|
|
204
217
|
} from "./chunk-64ACM6AP.js";
|
|
@@ -495,96 +508,25 @@ async function postSlackApiReplyPosts(args) {
|
|
|
495
508
|
return lastPostedMessageTs;
|
|
496
509
|
}
|
|
497
510
|
|
|
498
|
-
// src/chat/services/
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
return eventId;
|
|
504
|
-
}
|
|
505
|
-
function getExecutionFailureReason(reply) {
|
|
506
|
-
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
507
|
-
if (errorMessage) {
|
|
508
|
-
return errorMessage;
|
|
509
|
-
}
|
|
510
|
-
if (reply.diagnostics.toolErrorCount > 0) {
|
|
511
|
-
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
512
|
-
}
|
|
513
|
-
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
514
|
-
return "assistant returned no text";
|
|
515
|
-
}
|
|
516
|
-
return "empty assistant turn";
|
|
517
|
-
}
|
|
518
|
-
function getFailureCapture(reply) {
|
|
519
|
-
if (reply.diagnostics.outcome === "provider_error") {
|
|
520
|
-
return {
|
|
521
|
-
eventName: "agent_turn_provider_error",
|
|
522
|
-
error: reply.diagnostics.providerError ?? new Error(
|
|
523
|
-
reply.diagnostics.errorMessage ?? "Provider error without explicit message"
|
|
524
|
-
),
|
|
525
|
-
attributes: {},
|
|
526
|
-
body: "Agent turn failed with provider error"
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
const failureReason = getExecutionFailureReason(reply);
|
|
530
|
-
return {
|
|
531
|
-
eventName: "agent_turn_execution_failure",
|
|
532
|
-
error: new Error(`Agent turn execution failure: ${failureReason}`),
|
|
533
|
-
attributes: {
|
|
534
|
-
"app.ai.execution_failure_reason": failureReason
|
|
535
|
-
},
|
|
536
|
-
body: "Agent turn completed with execution failure"
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
function getAgentTurnDiagnosticsAttributes(reply) {
|
|
540
|
-
return {
|
|
541
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
542
|
-
"gen_ai.operation.name": "invoke_agent",
|
|
543
|
-
"app.ai.outcome": reply.diagnostics.outcome,
|
|
544
|
-
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
545
|
-
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
546
|
-
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
547
|
-
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
548
|
-
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
549
|
-
...reply.diagnostics.thinkingLevel ? {
|
|
550
|
-
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
551
|
-
} : {},
|
|
552
|
-
...reply.diagnostics.stopReason ? {
|
|
553
|
-
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
554
|
-
} : {},
|
|
555
|
-
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
556
|
-
};
|
|
511
|
+
// src/chat/services/persist-retry.ts
|
|
512
|
+
var PERSIST_ATTEMPTS = 3;
|
|
513
|
+
function sleep(ms) {
|
|
514
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
557
515
|
}
|
|
558
|
-
function
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
...getAgentTurnDiagnosticsAttributes(args.reply),
|
|
570
|
-
...args.attributes,
|
|
571
|
-
...capture.attributes
|
|
572
|
-
},
|
|
573
|
-
capture.body
|
|
574
|
-
),
|
|
575
|
-
capture.eventName
|
|
576
|
-
);
|
|
577
|
-
const providerPartialText = args.reply.diagnostics.outcome === "provider_error" ? args.reply.text.trim() : "";
|
|
578
|
-
return {
|
|
579
|
-
...args.reply,
|
|
580
|
-
text: providerPartialText ? `${providerPartialText}${getInterruptionMarker()}` : buildTurnFailureResponse(eventId),
|
|
581
|
-
deliveryMode: "thread",
|
|
582
|
-
deliveryPlan: {
|
|
583
|
-
mode: "thread",
|
|
584
|
-
postThreadText: true,
|
|
585
|
-
attachFiles: args.reply.files && args.reply.files.length > 0 ? "inline" : "none"
|
|
516
|
+
async function persistWithRetry(persist) {
|
|
517
|
+
let lastError;
|
|
518
|
+
for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
|
|
519
|
+
try {
|
|
520
|
+
await persist();
|
|
521
|
+
return;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
lastError = error;
|
|
524
|
+
if (attempt < PERSIST_ATTEMPTS) {
|
|
525
|
+
await sleep(attempt * 100);
|
|
526
|
+
}
|
|
586
527
|
}
|
|
587
|
-
}
|
|
528
|
+
}
|
|
529
|
+
throw lastError;
|
|
588
530
|
}
|
|
589
531
|
|
|
590
532
|
// src/chat/agent-dispatch/signing.ts
|
|
@@ -1035,6 +977,16 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1035
977
|
}
|
|
1036
978
|
let dispatch = claimedDispatch;
|
|
1037
979
|
const conversationId = getDispatchConversationId(dispatch);
|
|
980
|
+
const turnId = getDispatchTurnId(dispatch.id);
|
|
981
|
+
const logContext = {
|
|
982
|
+
conversationId,
|
|
983
|
+
slackThreadId: conversationId,
|
|
984
|
+
slackChannelId: dispatch.destination.channelId,
|
|
985
|
+
runId: dispatch.id,
|
|
986
|
+
actorType: dispatch.actor.type,
|
|
987
|
+
actorId: dispatch.actor.id,
|
|
988
|
+
assistantUserName: botConfig.userName
|
|
989
|
+
};
|
|
1038
990
|
const destinationLockId = getDispatchDestinationLockId(dispatch.destination);
|
|
1039
991
|
const stateAdapter = getStateAdapter();
|
|
1040
992
|
await stateAdapter.connect();
|
|
@@ -1119,7 +1071,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1119
1071
|
correlation: {
|
|
1120
1072
|
conversationId,
|
|
1121
1073
|
threadId: conversationId,
|
|
1122
|
-
turnId
|
|
1074
|
+
turnId,
|
|
1123
1075
|
runId: dispatch.id,
|
|
1124
1076
|
channelId: dispatch.destination.channelId,
|
|
1125
1077
|
teamId: dispatch.destination.teamId
|
|
@@ -1159,13 +1111,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1159
1111
|
reply,
|
|
1160
1112
|
logException,
|
|
1161
1113
|
context: {
|
|
1162
|
-
|
|
1163
|
-
slackThreadId: conversationId,
|
|
1164
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1165
|
-
runId: dispatch.id,
|
|
1166
|
-
actorType: dispatch.actor.type,
|
|
1167
|
-
actorId: dispatch.actor.id,
|
|
1168
|
-
assistantUserName: botConfig.userName,
|
|
1114
|
+
...logContext,
|
|
1169
1115
|
modelId: reply.diagnostics.modelId
|
|
1170
1116
|
}
|
|
1171
1117
|
});
|
|
@@ -1199,13 +1145,45 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1199
1145
|
});
|
|
1200
1146
|
updateConversationStats(conversation);
|
|
1201
1147
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : artifacts;
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1148
|
+
try {
|
|
1149
|
+
await persistWithRetry(
|
|
1150
|
+
() => persistRuntimePatch({
|
|
1151
|
+
threadId: conversationId,
|
|
1152
|
+
conversation,
|
|
1153
|
+
artifacts: nextArtifacts,
|
|
1154
|
+
sandboxId: reply.sandboxId ?? sandboxId,
|
|
1155
|
+
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash ?? sandboxDependencyProfileHash
|
|
1156
|
+
})
|
|
1157
|
+
);
|
|
1158
|
+
} catch (persistError) {
|
|
1159
|
+
logException(
|
|
1160
|
+
persistError,
|
|
1161
|
+
"agent_dispatch_post_delivery_persist_failed",
|
|
1162
|
+
logContext,
|
|
1163
|
+
{},
|
|
1164
|
+
"Failed to persist delivered dispatch state after Slack accepted the reply"
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
if (reply.piMessages?.length) {
|
|
1168
|
+
await completeDeliveredTurn({
|
|
1169
|
+
conversationId,
|
|
1170
|
+
sessionId: turnId,
|
|
1171
|
+
sliceId: 1,
|
|
1172
|
+
messages: reply.piMessages,
|
|
1173
|
+
durationMs: reply.diagnostics.durationMs,
|
|
1174
|
+
usage: reply.diagnostics.usage,
|
|
1175
|
+
destination: dispatch.destination,
|
|
1176
|
+
source: dispatch.source,
|
|
1177
|
+
surface: "api",
|
|
1178
|
+
logContext: {
|
|
1179
|
+
threadId: conversationId,
|
|
1180
|
+
channelId: dispatch.destination.channelId,
|
|
1181
|
+
runId: dispatch.id,
|
|
1182
|
+
assistantUserName: botConfig.userName,
|
|
1183
|
+
modelId: reply.diagnostics.modelId
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1209
1187
|
dispatch = await markDispatch({
|
|
1210
1188
|
dispatch,
|
|
1211
1189
|
status: failure ? "failed" : "completed",
|
|
@@ -1216,21 +1194,13 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1216
1194
|
try {
|
|
1217
1195
|
await scheduleCompletedTasks({
|
|
1218
1196
|
conversationId,
|
|
1219
|
-
sessionId:
|
|
1197
|
+
sessionId: turnId
|
|
1220
1198
|
});
|
|
1221
1199
|
} catch (error) {
|
|
1222
1200
|
logException(
|
|
1223
1201
|
error,
|
|
1224
1202
|
"plugin_session_completed_task_schedule_failed",
|
|
1225
|
-
|
|
1226
|
-
conversationId,
|
|
1227
|
-
slackThreadId: conversationId,
|
|
1228
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1229
|
-
runId: dispatch.id,
|
|
1230
|
-
actorType: dispatch.actor.type,
|
|
1231
|
-
actorId: dispatch.actor.id,
|
|
1232
|
-
assistantUserName: botConfig.userName
|
|
1233
|
-
},
|
|
1203
|
+
logContext,
|
|
1234
1204
|
{},
|
|
1235
1205
|
"Plugin session.completed task scheduling failed"
|
|
1236
1206
|
);
|
|
@@ -1279,13 +1249,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1279
1249
|
error,
|
|
1280
1250
|
"agent_dispatch_run_failed",
|
|
1281
1251
|
{
|
|
1282
|
-
|
|
1283
|
-
slackThreadId: conversationId,
|
|
1284
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1285
|
-
runId: dispatch.id,
|
|
1286
|
-
actorType: dispatch.actor.type,
|
|
1287
|
-
actorId: dispatch.actor.id,
|
|
1288
|
-
assistantUserName: botConfig.userName,
|
|
1252
|
+
...logContext,
|
|
1289
1253
|
modelId: botConfig.modelId
|
|
1290
1254
|
},
|
|
1291
1255
|
{},
|
|
@@ -1506,8 +1470,8 @@ async function drainConversationMailbox2(args) {
|
|
|
1506
1470
|
}
|
|
1507
1471
|
return result;
|
|
1508
1472
|
}
|
|
1509
|
-
async function
|
|
1510
|
-
const result = await
|
|
1473
|
+
async function ackMessages2(args) {
|
|
1474
|
+
const result = await ackMessages(args);
|
|
1511
1475
|
await recordExecutionMetadata(args);
|
|
1512
1476
|
return result;
|
|
1513
1477
|
}
|
|
@@ -1526,6 +1490,18 @@ async function completeConversationWork2(args) {
|
|
|
1526
1490
|
await recordExecutionMetadata(args);
|
|
1527
1491
|
return result;
|
|
1528
1492
|
}
|
|
1493
|
+
async function recordAttemptFailure2(args) {
|
|
1494
|
+
const result = await recordAttemptFailure(args);
|
|
1495
|
+
if (result.status === "recorded") {
|
|
1496
|
+
await recordExecutionMetadata(args);
|
|
1497
|
+
}
|
|
1498
|
+
return result;
|
|
1499
|
+
}
|
|
1500
|
+
async function deadLetterAttempt2(args) {
|
|
1501
|
+
const result = await deadLetterAttempt(args);
|
|
1502
|
+
await recordExecutionMetadata(args);
|
|
1503
|
+
return result;
|
|
1504
|
+
}
|
|
1529
1505
|
async function clearExpiredConversationLease2(args) {
|
|
1530
1506
|
const result = await clearExpiredConversationLease(args);
|
|
1531
1507
|
await recordExecutionMetadata(args);
|
|
@@ -1593,6 +1569,10 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
1593
1569
|
await args.adapter.initialize({
|
|
1594
1570
|
getState: () => state
|
|
1595
1571
|
});
|
|
1572
|
+
const internals = args.adapter;
|
|
1573
|
+
if (internals.defaultBotTokenProvider && !args.adapter.botUserId) {
|
|
1574
|
+
throw new Error("Slack adapter initialized without a resolved bot user id");
|
|
1575
|
+
}
|
|
1596
1576
|
initializedAdapters.add(args.adapter);
|
|
1597
1577
|
}
|
|
1598
1578
|
function verifySlackSignature(args) {
|
|
@@ -1640,7 +1620,7 @@ async function runWithSlackInstallation(args) {
|
|
|
1640
1620
|
enterpriseId: args.installation.enterpriseId,
|
|
1641
1621
|
isEnterpriseInstall: args.installation.isEnterpriseInstall
|
|
1642
1622
|
},
|
|
1643
|
-
args.task
|
|
1623
|
+
() => runWithSlackInstallationToken(tokenContext.token, args.task)
|
|
1644
1624
|
);
|
|
1645
1625
|
}
|
|
1646
1626
|
|
|
@@ -2014,14 +1994,22 @@ function createSlackConversationWorker(options) {
|
|
|
2014
1994
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
2015
1995
|
const state = getConnectedState2(options.state);
|
|
2016
1996
|
await state.connect();
|
|
2017
|
-
const records = getPendingRecords(
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
state
|
|
2021
|
-
})
|
|
2022
|
-
);
|
|
1997
|
+
const records = getPendingRecords({
|
|
1998
|
+
execution: { pendingMessages: [...context.attempt.messages] }
|
|
1999
|
+
});
|
|
2023
2000
|
if (records.length === 0) {
|
|
2024
|
-
|
|
2001
|
+
const destination = requireSlackDestination(
|
|
2002
|
+
context.destination,
|
|
2003
|
+
"Slack continuation recovery"
|
|
2004
|
+
);
|
|
2005
|
+
await runWithSlackInstallation({
|
|
2006
|
+
adapter,
|
|
2007
|
+
installation: { teamId: destination.teamId },
|
|
2008
|
+
state,
|
|
2009
|
+
task: async () => {
|
|
2010
|
+
await options.resumeAwaitingContinuation(context.conversationId);
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
2025
2013
|
return { status: "completed" };
|
|
2026
2014
|
}
|
|
2027
2015
|
const latestRecord = records[records.length - 1];
|
|
@@ -2071,33 +2059,22 @@ function createSlackConversationWorker(options) {
|
|
|
2071
2059
|
skipped,
|
|
2072
2060
|
totalSinceLastHandler: messages.length
|
|
2073
2061
|
};
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const markInitialMessagesInjected = async () => {
|
|
2079
|
-
if (initialMessagesPersisted) {
|
|
2080
|
-
return true;
|
|
2062
|
+
let initialMessagesAcked = false;
|
|
2063
|
+
const ack = async () => {
|
|
2064
|
+
if (initialMessagesAcked) {
|
|
2065
|
+
return;
|
|
2081
2066
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
conversationStore: options.conversationStore,
|
|
2087
|
-
state
|
|
2088
|
-
});
|
|
2089
|
-
initialMessagesPersisted = marked;
|
|
2090
|
-
return marked;
|
|
2091
|
-
};
|
|
2092
|
-
const onInputCommitted = async () => {
|
|
2093
|
-
if (!await markInitialMessagesInjected()) {
|
|
2067
|
+
try {
|
|
2068
|
+
await context.attempt.ack();
|
|
2069
|
+
initialMessagesAcked = true;
|
|
2070
|
+
} catch {
|
|
2094
2071
|
throw new TurnInputCommitLostError(
|
|
2095
|
-
`Conversation work lease lost before Slack
|
|
2072
|
+
`Conversation work lease lost before Slack inbox ack for ${context.conversationId}`
|
|
2096
2073
|
);
|
|
2097
2074
|
}
|
|
2098
2075
|
};
|
|
2099
|
-
const drainSteeringMessages = async (
|
|
2100
|
-
await context.
|
|
2076
|
+
const drainSteeringMessages = async (accept) => {
|
|
2077
|
+
await context.attempt.drain(async (pendingRecords) => {
|
|
2101
2078
|
const messages2 = pendingRecords.map((record) => {
|
|
2102
2079
|
const metadata = record.input.metadata;
|
|
2103
2080
|
if (!isSlackMetadata(metadata)) {
|
|
@@ -2112,7 +2089,7 @@ function createSlackConversationWorker(options) {
|
|
|
2112
2089
|
message
|
|
2113
2090
|
};
|
|
2114
2091
|
});
|
|
2115
|
-
return await
|
|
2092
|
+
return await accept(messages2);
|
|
2116
2093
|
});
|
|
2117
2094
|
};
|
|
2118
2095
|
try {
|
|
@@ -2121,7 +2098,8 @@ function createSlackConversationWorker(options) {
|
|
|
2121
2098
|
destination: context.destination,
|
|
2122
2099
|
messageContext,
|
|
2123
2100
|
drainSteeringMessages,
|
|
2124
|
-
|
|
2101
|
+
ack,
|
|
2102
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2125
2103
|
shouldYield: context.shouldYield
|
|
2126
2104
|
});
|
|
2127
2105
|
return;
|
|
@@ -2130,10 +2108,14 @@ function createSlackConversationWorker(options) {
|
|
|
2130
2108
|
destination: context.destination,
|
|
2131
2109
|
messageContext,
|
|
2132
2110
|
drainSteeringMessages,
|
|
2133
|
-
|
|
2111
|
+
ack,
|
|
2112
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2134
2113
|
shouldYield: context.shouldYield
|
|
2135
2114
|
});
|
|
2136
2115
|
} catch (error) {
|
|
2116
|
+
if (isTurnInputDeferredError(error)) {
|
|
2117
|
+
return { status: "deferred" };
|
|
2118
|
+
}
|
|
2137
2119
|
if (isCooperativeTurnYieldError(error)) {
|
|
2138
2120
|
return { status: "yielded" };
|
|
2139
2121
|
}
|
|
@@ -2144,7 +2126,7 @@ function createSlackConversationWorker(options) {
|
|
|
2144
2126
|
}
|
|
2145
2127
|
}
|
|
2146
2128
|
});
|
|
2147
|
-
if (turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2129
|
+
if (turnResult?.status === "deferred" || turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2148
2130
|
return turnResult;
|
|
2149
2131
|
}
|
|
2150
2132
|
return { status: "completed" };
|
|
@@ -2616,11 +2598,29 @@ async function recoverConversationWork(args) {
|
|
|
2616
2598
|
state: args.state
|
|
2617
2599
|
});
|
|
2618
2600
|
for (const conversationId of ids) {
|
|
2601
|
+
let work;
|
|
2619
2602
|
try {
|
|
2620
|
-
|
|
2603
|
+
work = await getConversationWorkState2({
|
|
2621
2604
|
conversationId,
|
|
2622
2605
|
state: args.state
|
|
2623
2606
|
});
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
logException(
|
|
2609
|
+
error,
|
|
2610
|
+
"conversation_work_recovery_failed",
|
|
2611
|
+
{ conversationId },
|
|
2612
|
+
{},
|
|
2613
|
+
"Conversation work heartbeat recovery failed"
|
|
2614
|
+
);
|
|
2615
|
+
if (isInvalidConversationRecordError(error)) {
|
|
2616
|
+
await removeActiveConversation2({
|
|
2617
|
+
conversationId,
|
|
2618
|
+
state: args.state
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
try {
|
|
2624
2624
|
if (!work) {
|
|
2625
2625
|
await removeActiveConversation2({
|
|
2626
2626
|
conversationId,
|
|
@@ -3529,17 +3529,6 @@ function isSlackTitlePermissionError(error) {
|
|
|
3529
3529
|
return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
|
|
3530
3530
|
}
|
|
3531
3531
|
|
|
3532
|
-
// src/chat/slack/workspace-context.ts
|
|
3533
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3534
|
-
var workspaceTeamIdStorage = new AsyncLocalStorage();
|
|
3535
|
-
function runWithWorkspaceTeamId(teamId, fn) {
|
|
3536
|
-
if (!teamId) return fn();
|
|
3537
|
-
return workspaceTeamIdStorage.run(teamId, fn);
|
|
3538
|
-
}
|
|
3539
|
-
function getWorkspaceTeamId() {
|
|
3540
|
-
return workspaceTeamIdStorage.getStore();
|
|
3541
|
-
}
|
|
3542
|
-
|
|
3543
3532
|
// src/chat/runtime/thread-context.ts
|
|
3544
3533
|
function escapeRegExp(value) {
|
|
3545
3534
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3734,8 +3723,8 @@ function buildAuthPauseResponse(slackUserId, providerDisplayName) {
|
|
|
3734
3723
|
}
|
|
3735
3724
|
|
|
3736
3725
|
// src/chat/runtime/request-deadline.ts
|
|
3737
|
-
import { AsyncLocalStorage
|
|
3738
|
-
var requestDeadlineStorage = new
|
|
3726
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3727
|
+
var requestDeadlineStorage = new AsyncLocalStorage();
|
|
3739
3728
|
function createTurnRequestDeadline(startedAtMs) {
|
|
3740
3729
|
const requestBudgetMs = Math.max(
|
|
3741
3730
|
1,
|
|
@@ -4014,6 +4003,27 @@ async function resumeSlackTurn(args) {
|
|
|
4014
4003
|
footer
|
|
4015
4004
|
});
|
|
4016
4005
|
finalReplyDelivered = true;
|
|
4006
|
+
if (replyContext.correlation?.conversationId && replyContext.correlation.turnId && reply.piMessages?.length) {
|
|
4007
|
+
await persistCompletedSessionRecord({
|
|
4008
|
+
conversationId: replyContext.correlation.conversationId,
|
|
4009
|
+
sessionId: replyContext.correlation.turnId,
|
|
4010
|
+
allMessages: reply.piMessages,
|
|
4011
|
+
currentDurationMs: reply.diagnostics.durationMs,
|
|
4012
|
+
currentUsage: reply.diagnostics.usage,
|
|
4013
|
+
destination: replyContext.destination,
|
|
4014
|
+
source: replyContext.source,
|
|
4015
|
+
requester: replyContext.requester,
|
|
4016
|
+
surface: "slack",
|
|
4017
|
+
logContext: {
|
|
4018
|
+
threadId: replyContext.correlation.threadId,
|
|
4019
|
+
requesterId: replyContext.requester?.userId,
|
|
4020
|
+
channelId: runArgs.channelId,
|
|
4021
|
+
runId: replyContext.correlation.runId,
|
|
4022
|
+
assistantUserName: botConfig.userName,
|
|
4023
|
+
modelId: reply.diagnostics.modelId
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4017
4027
|
await runArgs.onSuccess?.(reply);
|
|
4018
4028
|
if (reply.diagnostics.outcome === "success" && replyContext.correlation?.conversationId && replyContext.correlation.turnId) {
|
|
4019
4029
|
try {
|
|
@@ -5775,14 +5785,18 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5775
5785
|
const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
|
|
5776
5786
|
const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
|
|
5777
5787
|
const sourceTeam = typeof event.message.source_team === "string" ? event.message.source_team : void 0;
|
|
5788
|
+
const botId = typeof event.message.bot_id === "string" ? event.message.bot_id : void 0;
|
|
5789
|
+
const channelType = typeof event.channel_type === "string" ? event.channel_type : void 0;
|
|
5778
5790
|
const raw = {
|
|
5779
5791
|
channel: channelId,
|
|
5792
|
+
...channelType ? { channel_type: channelType } : {},
|
|
5780
5793
|
ts: messageTs,
|
|
5781
5794
|
thread_ts: threadTs,
|
|
5782
5795
|
user: userId,
|
|
5783
5796
|
...teamId ? { team_id: teamId } : {},
|
|
5784
5797
|
...userTeam ? { user_team: userTeam } : {},
|
|
5785
|
-
...sourceTeam ? { source_team: sourceTeam } : {}
|
|
5798
|
+
...sourceTeam ? { source_team: sourceTeam } : {},
|
|
5799
|
+
...botId ? { bot_id: botId } : {}
|
|
5786
5800
|
};
|
|
5787
5801
|
const message = new Message2({
|
|
5788
5802
|
id: getEditedMentionMessageId(messageTs),
|
|
@@ -5798,8 +5812,11 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5798
5812
|
// Raw message_changed payloads do not include profile fields.
|
|
5799
5813
|
userName: "",
|
|
5800
5814
|
fullName: "",
|
|
5801
|
-
|
|
5802
|
-
|
|
5815
|
+
// Mirror the fresh-message parse so the shared ingress author gate
|
|
5816
|
+
// applies: synthesized flags must never launder a bot-authored or
|
|
5817
|
+
// self-authored edit into a routable user message.
|
|
5818
|
+
isBot: Boolean(botId),
|
|
5819
|
+
isMe: userId === botUserId
|
|
5803
5820
|
}
|
|
5804
5821
|
});
|
|
5805
5822
|
Object.defineProperty(message, "adapter", {
|
|
@@ -6055,14 +6072,16 @@ async function handleMessageChanged(args) {
|
|
|
6055
6072
|
}
|
|
6056
6073
|
const botUserId = args.adapter.botUserId;
|
|
6057
6074
|
if (!botUserId) {
|
|
6058
|
-
|
|
6075
|
+
throw new Error(
|
|
6076
|
+
"Slack bot identity is unresolved; cannot classify message_changed event"
|
|
6077
|
+
);
|
|
6059
6078
|
}
|
|
6060
6079
|
const result = extractMessageChangedMention(
|
|
6061
6080
|
args.body,
|
|
6062
6081
|
botUserId,
|
|
6063
6082
|
args.adapter
|
|
6064
6083
|
);
|
|
6065
|
-
if (!result) {
|
|
6084
|
+
if (!result || shouldIgnoreMessage(result.message)) {
|
|
6066
6085
|
return true;
|
|
6067
6086
|
}
|
|
6068
6087
|
await persistSlackMessage({
|
|
@@ -6374,12 +6393,12 @@ async function handleSlackWebhook(args) {
|
|
|
6374
6393
|
try {
|
|
6375
6394
|
await eventTask;
|
|
6376
6395
|
} catch (error) {
|
|
6377
|
-
if (
|
|
6378
|
-
logException(error, "
|
|
6379
|
-
|
|
6396
|
+
if (error instanceof SlackEventPersistenceError) {
|
|
6397
|
+
logException(error.cause, "slack_event_persist_failed");
|
|
6398
|
+
} else {
|
|
6399
|
+
logException(error, "slack_event_routing_failed");
|
|
6380
6400
|
}
|
|
6381
|
-
|
|
6382
|
-
return new Response("Slack event persistence failed", { status: 503 });
|
|
6401
|
+
return new Response("Slack event handling failed", { status: 503 });
|
|
6383
6402
|
}
|
|
6384
6403
|
} else {
|
|
6385
6404
|
enqueue(
|
|
@@ -6529,6 +6548,33 @@ async function requestLostLeaseRecovery(args) {
|
|
|
6529
6548
|
state: args.options.state
|
|
6530
6549
|
});
|
|
6531
6550
|
}
|
|
6551
|
+
async function recordFailedDeliveryAttempt(args) {
|
|
6552
|
+
const failure = await recordAttemptFailure2({
|
|
6553
|
+
conversationId: args.conversationId,
|
|
6554
|
+
inboundMessageIds: args.messageIds,
|
|
6555
|
+
leaseToken: args.leaseToken,
|
|
6556
|
+
conversationStore: args.options.conversationStore,
|
|
6557
|
+
nowMs: args.nowMs,
|
|
6558
|
+
state: args.options.state
|
|
6559
|
+
});
|
|
6560
|
+
for (const message of failure.deadLetteredMessages) {
|
|
6561
|
+
logWarn(
|
|
6562
|
+
"conversation_work_dead_lettered",
|
|
6563
|
+
{ conversationId: args.conversationId },
|
|
6564
|
+
{
|
|
6565
|
+
"app.conversation.source": message.source,
|
|
6566
|
+
"app.inbound.attempt_count": message.attemptCount ?? 0,
|
|
6567
|
+
"app.inbound.message_id": message.inboundMessageId,
|
|
6568
|
+
"app.inbound.pending_count": failure.pendingCount
|
|
6569
|
+
},
|
|
6570
|
+
"Conversation work message consumed after exceeding the delivery attempt limit"
|
|
6571
|
+
);
|
|
6572
|
+
}
|
|
6573
|
+
return failure;
|
|
6574
|
+
}
|
|
6575
|
+
function isTerminalFailure(failure) {
|
|
6576
|
+
return failure.status === "recorded" && failure.deadLetteredMessages.length > 0 && failure.pendingCount === 0;
|
|
6577
|
+
}
|
|
6532
6578
|
function startLeaseCheckIn(args) {
|
|
6533
6579
|
const timer = setInterval(() => {
|
|
6534
6580
|
const nowMs = now2(args.options);
|
|
@@ -6566,10 +6612,22 @@ function startLeaseCheckIn(args) {
|
|
|
6566
6612
|
}
|
|
6567
6613
|
async function processConversationWork(message, options) {
|
|
6568
6614
|
const conversationId = message.conversationId;
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6615
|
+
let initial;
|
|
6616
|
+
try {
|
|
6617
|
+
initial = await getConversationWorkState2({
|
|
6618
|
+
conversationId,
|
|
6619
|
+
state: options.state
|
|
6620
|
+
});
|
|
6621
|
+
} catch (error) {
|
|
6622
|
+
if (isInvalidConversationRecordError(error)) {
|
|
6623
|
+
throw new ConversationQueueMessageRejectedError(
|
|
6624
|
+
"invalid_record",
|
|
6625
|
+
`Conversation record failed validation for ${conversationId}`,
|
|
6626
|
+
{ conversationId }
|
|
6627
|
+
);
|
|
6628
|
+
}
|
|
6629
|
+
throw error;
|
|
6630
|
+
}
|
|
6573
6631
|
if (!initial || countPendingConversationMessages(initial) === 0 && initial.execution.status === "idle" && !initial.execution.lease) {
|
|
6574
6632
|
if (initial) {
|
|
6575
6633
|
await clearConsumedConversationWake2({
|
|
@@ -6628,6 +6686,14 @@ async function processConversationWork(message, options) {
|
|
|
6628
6686
|
}
|
|
6629
6687
|
const startedAtMs = now2(options);
|
|
6630
6688
|
const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
|
|
6689
|
+
const leasedWork = await getConversationWorkState2({
|
|
6690
|
+
conversationId,
|
|
6691
|
+
state: options.state
|
|
6692
|
+
});
|
|
6693
|
+
const attemptMessages = leasedWork?.messages ?? initial.messages;
|
|
6694
|
+
const attemptMessageIds = attemptMessages.map(
|
|
6695
|
+
(message2) => message2.inboundMessageId
|
|
6696
|
+
);
|
|
6631
6697
|
let leaseLost = false;
|
|
6632
6698
|
const markLeaseLost = () => {
|
|
6633
6699
|
leaseLost = true;
|
|
@@ -6647,10 +6713,43 @@ async function processConversationWork(message, options) {
|
|
|
6647
6713
|
},
|
|
6648
6714
|
"Conversation work lease acquired"
|
|
6649
6715
|
);
|
|
6716
|
+
const drainInbox = (handle) => drainConversationMailbox2({
|
|
6717
|
+
conversationId,
|
|
6718
|
+
leaseToken: lease.leaseToken,
|
|
6719
|
+
conversationStore: options.conversationStore,
|
|
6720
|
+
handle,
|
|
6721
|
+
nowMs: now2(options),
|
|
6722
|
+
state: options.state
|
|
6723
|
+
});
|
|
6724
|
+
const ack = async () => {
|
|
6725
|
+
const acknowledged = await ackMessages2({
|
|
6726
|
+
conversationId,
|
|
6727
|
+
inboundMessageIds: attemptMessageIds,
|
|
6728
|
+
leaseToken: lease.leaseToken,
|
|
6729
|
+
conversationStore: options.conversationStore,
|
|
6730
|
+
nowMs: now2(options),
|
|
6731
|
+
state: options.state
|
|
6732
|
+
});
|
|
6733
|
+
if (!acknowledged) {
|
|
6734
|
+
markLeaseLost();
|
|
6735
|
+
throw new Error(
|
|
6736
|
+
`Conversation work lease lost before inbox ack for ${conversationId}`
|
|
6737
|
+
);
|
|
6738
|
+
}
|
|
6739
|
+
};
|
|
6650
6740
|
const workerContext = {
|
|
6741
|
+
attempt: {
|
|
6742
|
+
ack,
|
|
6743
|
+
conversationId,
|
|
6744
|
+
destination,
|
|
6745
|
+
drain: drainInbox,
|
|
6746
|
+
isFinalAttempt: attemptMessages.some(
|
|
6747
|
+
(message2) => isFinalAttempt(message2)
|
|
6748
|
+
),
|
|
6749
|
+
messages: attemptMessages
|
|
6750
|
+
},
|
|
6651
6751
|
conversationId,
|
|
6652
6752
|
destination,
|
|
6653
|
-
leaseToken: lease.leaseToken,
|
|
6654
6753
|
shouldYield: () => leaseLost || now2(options) >= softYieldDeadlineMs,
|
|
6655
6754
|
checkIn: async () => {
|
|
6656
6755
|
const checkedIn = await checkInConversationWork2({
|
|
@@ -6664,15 +6763,7 @@ async function processConversationWork(message, options) {
|
|
|
6664
6763
|
markLeaseLost();
|
|
6665
6764
|
}
|
|
6666
6765
|
return checkedIn;
|
|
6667
|
-
}
|
|
6668
|
-
drainMailbox: (inject) => drainConversationMailbox2({
|
|
6669
|
-
conversationId,
|
|
6670
|
-
leaseToken: lease.leaseToken,
|
|
6671
|
-
conversationStore: options.conversationStore,
|
|
6672
|
-
inject,
|
|
6673
|
-
nowMs: now2(options),
|
|
6674
|
-
state: options.state
|
|
6675
|
-
})
|
|
6766
|
+
}
|
|
6676
6767
|
};
|
|
6677
6768
|
try {
|
|
6678
6769
|
const result = await options.run(workerContext);
|
|
@@ -6739,6 +6830,51 @@ async function processConversationWork(message, options) {
|
|
|
6739
6830
|
);
|
|
6740
6831
|
return { status: "yielded" };
|
|
6741
6832
|
}
|
|
6833
|
+
if (result.status === "deferred") {
|
|
6834
|
+
const deferredNowMs = now2(options);
|
|
6835
|
+
const released = await releaseConversationWork2({
|
|
6836
|
+
conversationId,
|
|
6837
|
+
leaseToken: lease.leaseToken,
|
|
6838
|
+
conversationStore: options.conversationStore,
|
|
6839
|
+
nowMs: deferredNowMs,
|
|
6840
|
+
state: options.state
|
|
6841
|
+
});
|
|
6842
|
+
if (!released) {
|
|
6843
|
+
return { status: "lost_lease" };
|
|
6844
|
+
}
|
|
6845
|
+
const wake = await ensureConversationWake({
|
|
6846
|
+
conversationId,
|
|
6847
|
+
conversationStore: options.conversationStore,
|
|
6848
|
+
idempotencyKey: nudgeIdempotencyKey(
|
|
6849
|
+
"deferred",
|
|
6850
|
+
conversationId,
|
|
6851
|
+
deferredNowMs
|
|
6852
|
+
),
|
|
6853
|
+
nowMs: deferredNowMs,
|
|
6854
|
+
queue: options.queue,
|
|
6855
|
+
state: options.state
|
|
6856
|
+
});
|
|
6857
|
+
return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
|
|
6858
|
+
}
|
|
6859
|
+
if (attemptMessageIds.length > 0) {
|
|
6860
|
+
const failure = await recordFailedDeliveryAttempt({
|
|
6861
|
+
conversationId,
|
|
6862
|
+
leaseToken: lease.leaseToken,
|
|
6863
|
+
nowMs: now2(options),
|
|
6864
|
+
messageIds: attemptMessageIds,
|
|
6865
|
+
options
|
|
6866
|
+
});
|
|
6867
|
+
if (isTerminalFailure(failure)) {
|
|
6868
|
+
await deadLetterAttempt2({
|
|
6869
|
+
conversationId,
|
|
6870
|
+
leaseToken: lease.leaseToken,
|
|
6871
|
+
conversationStore: options.conversationStore,
|
|
6872
|
+
nowMs: now2(options),
|
|
6873
|
+
state: options.state
|
|
6874
|
+
});
|
|
6875
|
+
return { status: "failed" };
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6742
6878
|
const completion = await completeConversationWork2({
|
|
6743
6879
|
conversationId,
|
|
6744
6880
|
leaseToken: lease.leaseToken,
|
|
@@ -6776,53 +6912,62 @@ async function processConversationWork(message, options) {
|
|
|
6776
6912
|
return { status: "completed" };
|
|
6777
6913
|
} catch (error) {
|
|
6778
6914
|
const errorNowMs = now2(options);
|
|
6915
|
+
let recoveryRecorded = false;
|
|
6779
6916
|
try {
|
|
6780
|
-
const
|
|
6917
|
+
const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
|
|
6781
6918
|
conversationId,
|
|
6782
|
-
destination,
|
|
6783
6919
|
leaseToken: lease.leaseToken,
|
|
6784
|
-
conversationStore: options.conversationStore,
|
|
6785
6920
|
nowMs: errorNowMs,
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6921
|
+
messageIds: attemptMessageIds,
|
|
6922
|
+
options
|
|
6923
|
+
}) : void 0;
|
|
6924
|
+
if (failure && isTerminalFailure(failure)) {
|
|
6925
|
+
await deadLetterAttempt2({
|
|
6790
6926
|
conversationId,
|
|
6927
|
+
leaseToken: lease.leaseToken,
|
|
6791
6928
|
conversationStore: options.conversationStore,
|
|
6792
|
-
|
|
6793
|
-
|
|
6929
|
+
nowMs: errorNowMs,
|
|
6930
|
+
state: options.state
|
|
6931
|
+
});
|
|
6932
|
+
} else {
|
|
6933
|
+
const continuationMarked = await requestConversationContinuation2({
|
|
6934
|
+
conversationId,
|
|
6935
|
+
destination,
|
|
6936
|
+
leaseToken: lease.leaseToken,
|
|
6937
|
+
conversationStore: options.conversationStore,
|
|
6938
|
+
nowMs: errorNowMs,
|
|
6939
|
+
state: options.state
|
|
6940
|
+
});
|
|
6941
|
+
if (continuationMarked) {
|
|
6942
|
+
await ensureConversationWake({
|
|
6794
6943
|
conversationId,
|
|
6795
|
-
|
|
6796
|
-
|
|
6944
|
+
conversationStore: options.conversationStore,
|
|
6945
|
+
idempotencyKey: nudgeIdempotencyKey(
|
|
6946
|
+
"error",
|
|
6947
|
+
conversationId,
|
|
6948
|
+
errorNowMs
|
|
6949
|
+
),
|
|
6950
|
+
nowMs: errorNowMs,
|
|
6951
|
+
queue: options.queue,
|
|
6952
|
+
state: options.state
|
|
6953
|
+
});
|
|
6954
|
+
}
|
|
6955
|
+
await releaseConversationWork2({
|
|
6956
|
+
conversationId,
|
|
6957
|
+
leaseToken: lease.leaseToken,
|
|
6958
|
+
conversationStore: options.conversationStore,
|
|
6797
6959
|
nowMs: errorNowMs,
|
|
6798
|
-
queue: options.queue,
|
|
6799
6960
|
state: options.state
|
|
6800
6961
|
});
|
|
6801
6962
|
}
|
|
6802
|
-
|
|
6963
|
+
recoveryRecorded = true;
|
|
6964
|
+
} catch (recoveryError) {
|
|
6803
6965
|
logException(
|
|
6804
|
-
|
|
6966
|
+
recoveryError,
|
|
6805
6967
|
"conversation_work_requeue_failed",
|
|
6806
6968
|
{ conversationId },
|
|
6807
6969
|
{},
|
|
6808
|
-
"Conversation work
|
|
6809
|
-
);
|
|
6810
|
-
}
|
|
6811
|
-
try {
|
|
6812
|
-
await releaseConversationWork2({
|
|
6813
|
-
conversationId,
|
|
6814
|
-
leaseToken: lease.leaseToken,
|
|
6815
|
-
conversationStore: options.conversationStore,
|
|
6816
|
-
nowMs: errorNowMs,
|
|
6817
|
-
state: options.state
|
|
6818
|
-
});
|
|
6819
|
-
} catch (releaseError) {
|
|
6820
|
-
logException(
|
|
6821
|
-
releaseError,
|
|
6822
|
-
"conversation_work_release_failed",
|
|
6823
|
-
{ conversationId },
|
|
6824
|
-
{},
|
|
6825
|
-
"Conversation work release failed after runner error"
|
|
6970
|
+
"Conversation work recovery failed after runner error"
|
|
6826
6971
|
);
|
|
6827
6972
|
}
|
|
6828
6973
|
if (!isProviderRetryError(error)) {
|
|
@@ -6836,7 +6981,10 @@ async function processConversationWork(message, options) {
|
|
|
6836
6981
|
"Conversation work failed"
|
|
6837
6982
|
);
|
|
6838
6983
|
}
|
|
6839
|
-
|
|
6984
|
+
if (!recoveryRecorded) {
|
|
6985
|
+
throw error;
|
|
6986
|
+
}
|
|
6987
|
+
return { status: "failed" };
|
|
6840
6988
|
} finally {
|
|
6841
6989
|
clearInterval(timer);
|
|
6842
6990
|
}
|
|
@@ -7360,7 +7508,7 @@ function combineTurnText(queuedMessages, latestText) {
|
|
|
7360
7508
|
// src/chat/runtime/slack-runtime.ts
|
|
7361
7509
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
7362
7510
|
function shouldRethrowTurnControlError(error) {
|
|
7363
|
-
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isProviderRetryError(error);
|
|
7511
|
+
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isTurnInputDeferredError(error) || isProviderRetryError(error);
|
|
7364
7512
|
}
|
|
7365
7513
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
7366
7514
|
if (!args.decision?.shouldUnsubscribe) {
|
|
@@ -7394,13 +7542,13 @@ function createAcceptedSteeringDrain(hooks, options) {
|
|
|
7394
7542
|
if (!hooks.drainSteeringMessages) {
|
|
7395
7543
|
return void 0;
|
|
7396
7544
|
}
|
|
7397
|
-
return async (
|
|
7545
|
+
return async (accept, context) => {
|
|
7398
7546
|
let interruptedMessages;
|
|
7399
7547
|
await hooks.drainSteeringMessages(async (messages) => {
|
|
7400
7548
|
const selection = await options.selectMessages(messages, context);
|
|
7401
7549
|
await options.onSkipped?.(selection.skipped);
|
|
7402
7550
|
const interrupted = selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.message);
|
|
7403
|
-
await
|
|
7551
|
+
await accept(getQueuedMessagesFromSlackMessages(interrupted, options));
|
|
7404
7552
|
interruptedMessages = interrupted;
|
|
7405
7553
|
await options.onAcceptedForProcessing?.(interrupted);
|
|
7406
7554
|
return [
|
|
@@ -7567,7 +7715,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7567
7715
|
completedAtMs
|
|
7568
7716
|
});
|
|
7569
7717
|
}
|
|
7570
|
-
await args.
|
|
7718
|
+
await args.ack?.();
|
|
7571
7719
|
};
|
|
7572
7720
|
const selectAcceptedSteeringMessages = async (args) => {
|
|
7573
7721
|
const selected = [];
|
|
@@ -7653,6 +7801,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7653
7801
|
let processingReaction;
|
|
7654
7802
|
const skippedSteeringMessages = [];
|
|
7655
7803
|
let completed = false;
|
|
7804
|
+
let acked = false;
|
|
7656
7805
|
const onTurnCompleted = async () => {
|
|
7657
7806
|
completed = true;
|
|
7658
7807
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7701,8 +7850,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7701
7850
|
)
|
|
7702
7851
|
);
|
|
7703
7852
|
};
|
|
7704
|
-
const
|
|
7705
|
-
await hooks.
|
|
7853
|
+
const ack = async () => {
|
|
7854
|
+
await hooks.ack?.();
|
|
7855
|
+
acked = true;
|
|
7706
7856
|
await startQueuedProcessingReactions();
|
|
7707
7857
|
};
|
|
7708
7858
|
const drainSteeringMessages = createAcceptedSteeringDrain(hooks, {
|
|
@@ -7732,7 +7882,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7732
7882
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7733
7883
|
destination: hooks.destination,
|
|
7734
7884
|
queuedMessages,
|
|
7735
|
-
|
|
7885
|
+
ack,
|
|
7736
7886
|
onToolInvocation: toolInvocationHook,
|
|
7737
7887
|
onTurnCompleted,
|
|
7738
7888
|
drainSteeringMessages,
|
|
@@ -7786,6 +7936,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7786
7936
|
{},
|
|
7787
7937
|
"onNewMention failed"
|
|
7788
7938
|
);
|
|
7939
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
7940
|
+
return;
|
|
7941
|
+
}
|
|
7789
7942
|
if (!eventId) {
|
|
7790
7943
|
throw new Error(
|
|
7791
7944
|
"Sentry did not return an event ID for mention_handler_failed"
|
|
@@ -7823,6 +7976,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7823
7976
|
let processingReaction;
|
|
7824
7977
|
const skippedSteeringMessages = [];
|
|
7825
7978
|
let completed = false;
|
|
7979
|
+
let acked = false;
|
|
7826
7980
|
const onTurnCompleted = async () => {
|
|
7827
7981
|
completed = true;
|
|
7828
7982
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7889,7 +8043,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7889
8043
|
message,
|
|
7890
8044
|
decision: { shouldReply: false, reason },
|
|
7891
8045
|
context: threadContext,
|
|
7892
|
-
|
|
8046
|
+
ack: hooks.ack,
|
|
7893
8047
|
text: combinedText
|
|
7894
8048
|
});
|
|
7895
8049
|
return;
|
|
@@ -7926,7 +8080,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7926
8080
|
message,
|
|
7927
8081
|
decision,
|
|
7928
8082
|
context: threadContext,
|
|
7929
|
-
|
|
8083
|
+
ack: hooks.ack,
|
|
7930
8084
|
preparedState,
|
|
7931
8085
|
text: combinedText
|
|
7932
8086
|
});
|
|
@@ -7938,7 +8092,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7938
8092
|
message,
|
|
7939
8093
|
decision,
|
|
7940
8094
|
context: threadContext,
|
|
7941
|
-
|
|
8095
|
+
ack: hooks.ack,
|
|
7942
8096
|
preparedState,
|
|
7943
8097
|
text: combinedText
|
|
7944
8098
|
});
|
|
@@ -7983,8 +8137,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7983
8137
|
)
|
|
7984
8138
|
);
|
|
7985
8139
|
};
|
|
7986
|
-
const
|
|
7987
|
-
await hooks.
|
|
8140
|
+
const ack = async () => {
|
|
8141
|
+
await hooks.ack?.();
|
|
8142
|
+
acked = true;
|
|
7988
8143
|
await startQueuedProcessingReactions();
|
|
7989
8144
|
};
|
|
7990
8145
|
const toolInvocationHook = createToolInvocationHook(
|
|
@@ -7997,7 +8152,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7997
8152
|
preparedState,
|
|
7998
8153
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7999
8154
|
queuedMessages,
|
|
8000
|
-
|
|
8155
|
+
ack,
|
|
8001
8156
|
onToolInvocation: toolInvocationHook,
|
|
8002
8157
|
onTurnCompleted,
|
|
8003
8158
|
drainSteeringMessages,
|
|
@@ -8051,6 +8206,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8051
8206
|
{},
|
|
8052
8207
|
"onSubscribedMessage failed"
|
|
8053
8208
|
);
|
|
8209
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
8210
|
+
return;
|
|
8211
|
+
}
|
|
8054
8212
|
if (!eventId) {
|
|
8055
8213
|
throw new Error(
|
|
8056
8214
|
"Sentry did not return an event ID for subscribed_message_handler_failed"
|
|
@@ -9002,6 +9160,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
9002
9160
|
}
|
|
9003
9161
|
|
|
9004
9162
|
// src/chat/runtime/reply-executor.ts
|
|
9163
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
9005
9164
|
import { createSlackSource } from "@sentry/junior-plugin-api";
|
|
9006
9165
|
|
|
9007
9166
|
// src/chat/slack/message.ts
|
|
@@ -9134,6 +9293,9 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
|
|
|
9134
9293
|
}
|
|
9135
9294
|
|
|
9136
9295
|
// src/chat/runtime/reply-executor.ts
|
|
9296
|
+
async function persistThreadStateWithRetry(thread, patch) {
|
|
9297
|
+
await persistWithRetry(() => persistThreadState(thread, patch));
|
|
9298
|
+
}
|
|
9137
9299
|
function collectCanvasUrls(artifacts) {
|
|
9138
9300
|
return new Set(
|
|
9139
9301
|
[
|
|
@@ -9145,6 +9307,14 @@ function collectCanvasUrls(artifacts) {
|
|
|
9145
9307
|
function turnRequester(requester) {
|
|
9146
9308
|
return toStoredSlackRequester(requester);
|
|
9147
9309
|
}
|
|
9310
|
+
function parkedInputKey(message) {
|
|
9311
|
+
if (message.role !== "user") {
|
|
9312
|
+
return void 0;
|
|
9313
|
+
}
|
|
9314
|
+
const first = Array.isArray(message.content) ? message.content[0] : void 0;
|
|
9315
|
+
const text = first && typeof first === "object" && "text" in first ? String(first.text ?? "") : "";
|
|
9316
|
+
return `${message.timestamp}:${text}`;
|
|
9317
|
+
}
|
|
9148
9318
|
function isResourceEventMessage(message) {
|
|
9149
9319
|
const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
|
|
9150
9320
|
return raw?.event_type === "resource_event";
|
|
@@ -9226,6 +9396,7 @@ function createReplyToThread(deps) {
|
|
|
9226
9396
|
channelName,
|
|
9227
9397
|
channelType: slackChannelType
|
|
9228
9398
|
});
|
|
9399
|
+
const destinationVisibility = conversationVisibilityFromSlackChannelType(slackChannelType);
|
|
9229
9400
|
const threadTs = getThreadTs(threadId);
|
|
9230
9401
|
const assistantThreadContext = getAssistantThreadContext(message);
|
|
9231
9402
|
const messageTs = getMessageTs(message);
|
|
@@ -9238,7 +9409,8 @@ function createReplyToThread(deps) {
|
|
|
9238
9409
|
channelId: channelId ?? destination.channelId,
|
|
9239
9410
|
messageTs,
|
|
9240
9411
|
teamId,
|
|
9241
|
-
threadTs
|
|
9412
|
+
threadTs,
|
|
9413
|
+
type: destinationVisibility === "public" ? "pub" : "priv"
|
|
9242
9414
|
});
|
|
9243
9415
|
const runId = getRunId(thread, message);
|
|
9244
9416
|
const conversationId = threadId ?? runId;
|
|
@@ -9365,12 +9537,87 @@ function createReplyToThread(deps) {
|
|
|
9365
9537
|
}
|
|
9366
9538
|
};
|
|
9367
9539
|
let activeTurnId = preparedState.conversation.processing.activeTurnId;
|
|
9540
|
+
const resolveSteeringMessages = async (queuedMessages) => {
|
|
9541
|
+
return await Promise.all(
|
|
9542
|
+
queuedMessages.map(async (queued) => {
|
|
9543
|
+
const attachments = queued.message.attachments;
|
|
9544
|
+
return {
|
|
9545
|
+
text: queued.userText,
|
|
9546
|
+
timestampMs: queued.message.metadata.dateSent.getTime(),
|
|
9547
|
+
omittedImageAttachmentCount: !isVisionEnabled() && hasPotentialImageAttachment(attachments) ? countPotentialImageAttachments(attachments) : 0,
|
|
9548
|
+
userAttachments: await deps.resolveUserAttachments(
|
|
9549
|
+
attachments,
|
|
9550
|
+
{
|
|
9551
|
+
threadId,
|
|
9552
|
+
requesterId: isResourceEventMessage(queued.message) ? void 0 : queued.message.author.userId,
|
|
9553
|
+
channelId,
|
|
9554
|
+
runId,
|
|
9555
|
+
conversation: preparedState.conversation,
|
|
9556
|
+
messageTs: getSlackMessageTs(queued.message)
|
|
9557
|
+
}
|
|
9558
|
+
)
|
|
9559
|
+
};
|
|
9560
|
+
})
|
|
9561
|
+
);
|
|
9562
|
+
};
|
|
9563
|
+
const appendParkedTurnInput = async (parkedSessionId) => {
|
|
9564
|
+
if (!conversationId) {
|
|
9565
|
+
return true;
|
|
9566
|
+
}
|
|
9567
|
+
const parkedMessages = [
|
|
9568
|
+
...options.queuedMessages ?? [],
|
|
9569
|
+
{
|
|
9570
|
+
explicitMention: Boolean(
|
|
9571
|
+
options.explicitMention || message.isMention
|
|
9572
|
+
),
|
|
9573
|
+
message,
|
|
9574
|
+
rawText: currentText.rawText,
|
|
9575
|
+
userText: currentText.userText
|
|
9576
|
+
}
|
|
9577
|
+
].filter(
|
|
9578
|
+
// Redelivery of the parked turn's own message must not duplicate
|
|
9579
|
+
// the prompt that already started the session.
|
|
9580
|
+
(queued) => buildDeterministicTurnId(queued.message.id) !== parkedSessionId
|
|
9581
|
+
);
|
|
9582
|
+
if (parkedMessages.length === 0) {
|
|
9583
|
+
return true;
|
|
9584
|
+
}
|
|
9585
|
+
const stateAdapter = getStateAdapter();
|
|
9586
|
+
await stateAdapter.connect();
|
|
9587
|
+
const lock = await acquireActiveLock(stateAdapter, conversationId);
|
|
9588
|
+
if (!lock) {
|
|
9589
|
+
return false;
|
|
9590
|
+
}
|
|
9591
|
+
try {
|
|
9592
|
+
const piMessages = (await resolveSteeringMessages(parkedMessages)).map(buildSteeringPiMessage);
|
|
9593
|
+
const projection = await loadProjection({ conversationId });
|
|
9594
|
+
const appendedKeys = new Set(
|
|
9595
|
+
projection.map(parkedInputKey).filter((key) => key !== void 0)
|
|
9596
|
+
);
|
|
9597
|
+
const missing = piMessages.filter((piMessage) => {
|
|
9598
|
+
const key = parkedInputKey(piMessage);
|
|
9599
|
+
return key === void 0 || !appendedKeys.has(key);
|
|
9600
|
+
});
|
|
9601
|
+
if (missing.length === 0) {
|
|
9602
|
+
return true;
|
|
9603
|
+
}
|
|
9604
|
+
await commitMessages({
|
|
9605
|
+
conversationId,
|
|
9606
|
+
messages: [...projection, ...missing],
|
|
9607
|
+
requester: storedRequester,
|
|
9608
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
9609
|
+
});
|
|
9610
|
+
return true;
|
|
9611
|
+
} finally {
|
|
9612
|
+
await stateAdapter.releaseLock(lock);
|
|
9613
|
+
}
|
|
9614
|
+
};
|
|
9368
9615
|
if (preparedState.userMessageAlreadyReplied) {
|
|
9369
9616
|
await persistThreadState(thread, {
|
|
9370
9617
|
conversation: preparedState.conversation
|
|
9371
9618
|
});
|
|
9372
9619
|
await options.onTurnStatePersisted?.();
|
|
9373
|
-
await options.
|
|
9620
|
+
await options.ack?.();
|
|
9374
9621
|
await options.onTurnCompleted?.();
|
|
9375
9622
|
return;
|
|
9376
9623
|
}
|
|
@@ -9380,6 +9627,9 @@ function createReplyToThread(deps) {
|
|
|
9380
9627
|
sessionId: activeTurnId
|
|
9381
9628
|
});
|
|
9382
9629
|
if (resumeRequest) {
|
|
9630
|
+
if (!await appendParkedTurnInput(resumeRequest.sessionId)) {
|
|
9631
|
+
throw new TurnInputDeferredError();
|
|
9632
|
+
}
|
|
9383
9633
|
try {
|
|
9384
9634
|
await deps.services.scheduleAgentContinue(resumeRequest);
|
|
9385
9635
|
} catch (error) {
|
|
@@ -9400,7 +9650,7 @@ function createReplyToThread(deps) {
|
|
|
9400
9650
|
conversation: preparedState.conversation
|
|
9401
9651
|
});
|
|
9402
9652
|
await options.onTurnStatePersisted?.();
|
|
9403
|
-
await options.
|
|
9653
|
+
await options.ack?.();
|
|
9404
9654
|
return;
|
|
9405
9655
|
}
|
|
9406
9656
|
const sessionRecord = await getAgentTurnSessionRecord(
|
|
@@ -9409,27 +9659,34 @@ function createReplyToThread(deps) {
|
|
|
9409
9659
|
);
|
|
9410
9660
|
if (sessionRecord?.state === "awaiting_resume") {
|
|
9411
9661
|
if (sessionRecord.resumeReason === "auth") {
|
|
9412
|
-
await
|
|
9413
|
-
|
|
9662
|
+
await abandonAgentTurnSessionRecord({
|
|
9663
|
+
conversationId,
|
|
9664
|
+
sessionId: activeTurnId,
|
|
9665
|
+
errorMessage: "Auth-parked session superseded by a new user message"
|
|
9414
9666
|
});
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9667
|
+
markTurnClosed({
|
|
9668
|
+
conversation: preparedState.conversation,
|
|
9669
|
+
nowMs: Date.now(),
|
|
9670
|
+
sessionId: activeTurnId,
|
|
9671
|
+
updateConversationStats
|
|
9672
|
+
});
|
|
9673
|
+
activeTurnId = void 0;
|
|
9674
|
+
} else {
|
|
9675
|
+
await failAgentTurnSessionRecord({
|
|
9676
|
+
conversationId,
|
|
9677
|
+
expectedVersion: sessionRecord.version,
|
|
9678
|
+
sessionId: activeTurnId,
|
|
9679
|
+
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
9680
|
+
});
|
|
9681
|
+
markTurnFailed({
|
|
9682
|
+
conversation: preparedState.conversation,
|
|
9683
|
+
nowMs: Date.now(),
|
|
9684
|
+
sessionId: activeTurnId,
|
|
9685
|
+
markConversationMessage,
|
|
9686
|
+
updateConversationStats
|
|
9687
|
+
});
|
|
9688
|
+
activeTurnId = void 0;
|
|
9418
9689
|
}
|
|
9419
|
-
await failAgentTurnSessionRecord({
|
|
9420
|
-
conversationId,
|
|
9421
|
-
expectedVersion: sessionRecord.version,
|
|
9422
|
-
sessionId: activeTurnId,
|
|
9423
|
-
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
9424
|
-
});
|
|
9425
|
-
markTurnFailed({
|
|
9426
|
-
conversation: preparedState.conversation,
|
|
9427
|
-
nowMs: Date.now(),
|
|
9428
|
-
sessionId: activeTurnId,
|
|
9429
|
-
markConversationMessage,
|
|
9430
|
-
updateConversationStats
|
|
9431
|
-
});
|
|
9432
|
-
activeTurnId = void 0;
|
|
9433
9690
|
}
|
|
9434
9691
|
}
|
|
9435
9692
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
@@ -9465,7 +9722,7 @@ function createReplyToThread(deps) {
|
|
|
9465
9722
|
conversation: preparedState.conversation
|
|
9466
9723
|
});
|
|
9467
9724
|
await options.onTurnStatePersisted?.();
|
|
9468
|
-
await options.
|
|
9725
|
+
await options.ack?.();
|
|
9469
9726
|
return;
|
|
9470
9727
|
}
|
|
9471
9728
|
startActiveTurn({
|
|
@@ -9485,6 +9742,7 @@ function createReplyToThread(deps) {
|
|
|
9485
9742
|
surface: "slack",
|
|
9486
9743
|
requester,
|
|
9487
9744
|
destination,
|
|
9745
|
+
destinationVisibility,
|
|
9488
9746
|
source,
|
|
9489
9747
|
traceId: getActiveTraceId()
|
|
9490
9748
|
}).catch((error) => {
|
|
@@ -9601,8 +9859,10 @@ function createReplyToThread(deps) {
|
|
|
9601
9859
|
};
|
|
9602
9860
|
let persistedAtLeastOnce = false;
|
|
9603
9861
|
let shouldPersistFailureState = true;
|
|
9862
|
+
let finalReplyDelivered = false;
|
|
9604
9863
|
let latestArtifacts = preparedState.artifacts;
|
|
9605
9864
|
let assistantTitleArtifacts = {};
|
|
9865
|
+
const hasVisibleSlackDelivery = (post) => post.text.trim().length > 0 || Boolean(post.files?.length);
|
|
9606
9866
|
try {
|
|
9607
9867
|
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
9608
9868
|
conversationId,
|
|
@@ -9694,39 +9954,16 @@ function createReplyToThread(deps) {
|
|
|
9694
9954
|
);
|
|
9695
9955
|
});
|
|
9696
9956
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
9697
|
-
const
|
|
9698
|
-
|
|
9699
|
-
queuedMessages.map(async (queued) => {
|
|
9700
|
-
const attachments = queued.message.attachments;
|
|
9701
|
-
return {
|
|
9702
|
-
text: queued.userText,
|
|
9703
|
-
timestampMs: queued.message.metadata.dateSent.getTime(),
|
|
9704
|
-
omittedImageAttachmentCount: !isVisionEnabled() && hasPotentialImageAttachment(attachments) ? countPotentialImageAttachments(attachments) : 0,
|
|
9705
|
-
userAttachments: await deps.resolveUserAttachments(
|
|
9706
|
-
attachments,
|
|
9707
|
-
{
|
|
9708
|
-
threadId,
|
|
9709
|
-
requesterId: isResourceEventMessage(queued.message) ? void 0 : queued.message.author.userId,
|
|
9710
|
-
channelId,
|
|
9711
|
-
runId,
|
|
9712
|
-
conversation: preparedState.conversation,
|
|
9713
|
-
messageTs: getSlackMessageTs(queued.message)
|
|
9714
|
-
}
|
|
9715
|
-
)
|
|
9716
|
-
};
|
|
9717
|
-
})
|
|
9718
|
-
);
|
|
9719
|
-
};
|
|
9720
|
-
const drainSteeringMessages = options.drainSteeringMessages ? async (inject) => {
|
|
9721
|
-
let injectedMessages;
|
|
9957
|
+
const drainSteeringMessages = options.drainSteeringMessages ? async (accept) => {
|
|
9958
|
+
let acceptedMessages;
|
|
9722
9959
|
const drained = await options.drainSteeringMessages(
|
|
9723
9960
|
async (queuedMessages) => {
|
|
9724
|
-
|
|
9725
|
-
await
|
|
9961
|
+
acceptedMessages = await resolveSteeringMessages(queuedMessages);
|
|
9962
|
+
await accept(acceptedMessages);
|
|
9726
9963
|
},
|
|
9727
9964
|
{ conversationContext: preparedState.conversationContext }
|
|
9728
9965
|
);
|
|
9729
|
-
return
|
|
9966
|
+
return acceptedMessages ?? await resolveSteeringMessages(drained);
|
|
9730
9967
|
} : void 0;
|
|
9731
9968
|
let reply = await deps.services.generateAssistantReply(
|
|
9732
9969
|
effectiveUserText,
|
|
@@ -9792,7 +10029,7 @@ function createReplyToThread(deps) {
|
|
|
9792
10029
|
},
|
|
9793
10030
|
onStatus: (nextStatus) => status.update(nextStatus),
|
|
9794
10031
|
onToolInvocation: options.onToolInvocation,
|
|
9795
|
-
onInputCommitted: options.
|
|
10032
|
+
onInputCommitted: options.ack,
|
|
9796
10033
|
drainSteeringMessages,
|
|
9797
10034
|
shouldYield: options.shouldYield
|
|
9798
10035
|
}
|
|
@@ -9815,16 +10052,20 @@ function createReplyToThread(deps) {
|
|
|
9815
10052
|
});
|
|
9816
10053
|
}
|
|
9817
10054
|
const artifactStatePatch = reply.artifactStatePatch ? { ...reply.artifactStatePatch } : {};
|
|
9818
|
-
const reactionPerformed = reply.diagnostics.toolCalls.includes(
|
|
9819
|
-
"slackMessageAddReaction"
|
|
9820
|
-
);
|
|
9821
10055
|
const plannedPosts = planSlackReplyPosts({ reply });
|
|
9822
10056
|
const replyFooter = buildSlackReplyFooter({
|
|
9823
10057
|
conversationId
|
|
9824
10058
|
});
|
|
9825
10059
|
const shouldUseSlackFooter = Boolean(replyFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
9826
10060
|
if (plannedPosts.length > 0) {
|
|
9827
|
-
|
|
10061
|
+
const hasVisibleDelivery = plannedPosts.some(
|
|
10062
|
+
hasVisibleSlackDelivery
|
|
10063
|
+
);
|
|
10064
|
+
if (!hasVisibleDelivery) {
|
|
10065
|
+
throw new Error(
|
|
10066
|
+
"Slack final reply plan did not contain visible delivery"
|
|
10067
|
+
);
|
|
10068
|
+
}
|
|
9828
10069
|
if (shouldUseSlackFooter) {
|
|
9829
10070
|
const slackChannelId = channelId;
|
|
9830
10071
|
const slackThreadTs = threadTs;
|
|
@@ -9833,7 +10074,7 @@ function createReplyToThread(deps) {
|
|
|
9833
10074
|
"Slack footer delivery requires a concrete channel and thread timestamp"
|
|
9834
10075
|
);
|
|
9835
10076
|
}
|
|
9836
|
-
|
|
10077
|
+
await postSlackApiReplyPosts({
|
|
9837
10078
|
beforePost: beforeFirstResponsePost,
|
|
9838
10079
|
channelId: slackChannelId,
|
|
9839
10080
|
threadTs: slackThreadTs,
|
|
@@ -9854,30 +10095,22 @@ function createReplyToThread(deps) {
|
|
|
9854
10095
|
);
|
|
9855
10096
|
}
|
|
9856
10097
|
});
|
|
9857
|
-
if (sentMessageTs) {
|
|
9858
|
-
sent = {
|
|
9859
|
-
id: sentMessageTs,
|
|
9860
|
-
text: reply.text,
|
|
9861
|
-
delete: async () => {
|
|
9862
|
-
await deleteSlackMessage({
|
|
9863
|
-
channelId: slackChannelId,
|
|
9864
|
-
timestamp: sentMessageTs
|
|
9865
|
-
});
|
|
9866
|
-
}
|
|
9867
|
-
};
|
|
9868
|
-
}
|
|
9869
10098
|
} else {
|
|
9870
10099
|
for (const post of plannedPosts) {
|
|
9871
|
-
|
|
10100
|
+
if (!hasVisibleSlackDelivery(post)) {
|
|
10101
|
+
continue;
|
|
10102
|
+
}
|
|
10103
|
+
await postThreadReply(
|
|
9872
10104
|
buildSlackOutputMessage(post.text, post.files),
|
|
9873
10105
|
post.stage
|
|
9874
10106
|
);
|
|
9875
10107
|
}
|
|
9876
10108
|
}
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
10109
|
+
finalReplyDelivered = true;
|
|
10110
|
+
shouldPersistFailureState = false;
|
|
10111
|
+
} else {
|
|
10112
|
+
finalReplyDelivered = true;
|
|
10113
|
+
shouldPersistFailureState = false;
|
|
9881
10114
|
}
|
|
9882
10115
|
const completedState = buildDeliveredTurnStatePatch({
|
|
9883
10116
|
artifactStatePatch: {
|
|
@@ -9893,27 +10126,63 @@ function createReplyToThread(deps) {
|
|
|
9893
10126
|
if (completedState.artifacts) {
|
|
9894
10127
|
latestArtifacts = completedState.artifacts;
|
|
9895
10128
|
}
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
10129
|
+
try {
|
|
10130
|
+
if (conversationId && reply.piMessages?.length) {
|
|
10131
|
+
await completeDeliveredTurn({
|
|
10132
|
+
channelName,
|
|
10133
|
+
conversationId,
|
|
10134
|
+
durationMs: reply.diagnostics.durationMs,
|
|
10135
|
+
usage: reply.diagnostics.usage,
|
|
10136
|
+
destination,
|
|
10137
|
+
destinationVisibility,
|
|
10138
|
+
source,
|
|
10139
|
+
sessionId: turnId,
|
|
10140
|
+
sliceId: 1,
|
|
10141
|
+
messages: reply.piMessages,
|
|
10142
|
+
logContext: {
|
|
10143
|
+
threadId,
|
|
10144
|
+
requesterId: slackRequesterId,
|
|
10145
|
+
channelId,
|
|
10146
|
+
runId,
|
|
10147
|
+
assistantUserName: botConfig.userName,
|
|
10148
|
+
modelId: reply.diagnostics.modelId
|
|
10149
|
+
},
|
|
10150
|
+
requester,
|
|
10151
|
+
surface: "slack"
|
|
10152
|
+
});
|
|
10153
|
+
} else if (conversationId) {
|
|
10154
|
+
await recordAgentTurnSessionSummary({
|
|
10155
|
+
channelName,
|
|
10156
|
+
conversationId,
|
|
10157
|
+
cumulativeDurationMs: reply.diagnostics.durationMs,
|
|
10158
|
+
cumulativeUsage: reply.diagnostics.usage,
|
|
10159
|
+
sessionId: turnId,
|
|
10160
|
+
sliceId: 1,
|
|
10161
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
10162
|
+
state: "completed",
|
|
10163
|
+
requester,
|
|
10164
|
+
destination,
|
|
10165
|
+
destinationVisibility,
|
|
10166
|
+
source,
|
|
10167
|
+
traceId: getActiveTraceId()
|
|
10168
|
+
});
|
|
10169
|
+
}
|
|
10170
|
+
await persistThreadStateWithRetry(thread, {
|
|
10171
|
+
...completedState
|
|
9916
10172
|
});
|
|
10173
|
+
if (completedState.artifacts && (assistantTitleArtifacts.assistantTitle !== void 0 || assistantTitleArtifacts.assistantTitleSourceMessageId !== void 0) && (completedState.artifacts.assistantTitle !== assistantTitleArtifacts.assistantTitle || completedState.artifacts.assistantTitleSourceMessageId !== assistantTitleArtifacts.assistantTitleSourceMessageId)) {
|
|
10174
|
+
await persistThreadStateWithRetry(thread, {
|
|
10175
|
+
artifacts: latestArtifacts
|
|
10176
|
+
});
|
|
10177
|
+
}
|
|
10178
|
+
} catch (commitError) {
|
|
10179
|
+
logException(
|
|
10180
|
+
commitError,
|
|
10181
|
+
"slack_reply_post_delivery_commit_failed",
|
|
10182
|
+
turnTraceContext,
|
|
10183
|
+
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10184
|
+
"Post-delivery turn state persistence failed after Slack accepted the reply"
|
|
10185
|
+
);
|
|
9917
10186
|
}
|
|
9918
10187
|
preparedState.conversation = completedState.conversation;
|
|
9919
10188
|
persistedAtLeastOnce = true;
|
|
@@ -9947,6 +10216,17 @@ function createReplyToThread(deps) {
|
|
|
9947
10216
|
}
|
|
9948
10217
|
}
|
|
9949
10218
|
} catch (error) {
|
|
10219
|
+
if (finalReplyDelivered) {
|
|
10220
|
+
shouldPersistFailureState = false;
|
|
10221
|
+
logException(
|
|
10222
|
+
error,
|
|
10223
|
+
"slack_reply_post_delivery_commit_failed",
|
|
10224
|
+
turnTraceContext,
|
|
10225
|
+
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10226
|
+
"Post-delivery turn work failed after Slack accepted the reply"
|
|
10227
|
+
);
|
|
10228
|
+
return;
|
|
10229
|
+
}
|
|
9950
10230
|
if (isCooperativeTurnYieldError(error)) {
|
|
9951
10231
|
shouldPersistFailureState = false;
|
|
9952
10232
|
throw error;
|
|
@@ -10121,6 +10401,7 @@ function createReplyToThread(deps) {
|
|
|
10121
10401
|
state: "failed",
|
|
10122
10402
|
requester,
|
|
10123
10403
|
destination,
|
|
10404
|
+
destinationVisibility,
|
|
10124
10405
|
source,
|
|
10125
10406
|
traceId: getActiveTraceId()
|
|
10126
10407
|
});
|
|
@@ -10558,7 +10839,7 @@ function createJuniorSlackAdapter(config) {
|
|
|
10558
10839
|
|
|
10559
10840
|
// src/chat/runtime/agent-continue-runner.ts
|
|
10560
10841
|
var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
10561
|
-
function
|
|
10842
|
+
function sleep2(ms) {
|
|
10562
10843
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10563
10844
|
}
|
|
10564
10845
|
async function persistCompletedReplyState2(args) {
|
|
@@ -10645,6 +10926,28 @@ async function failContinuationStartup(args) {
|
|
|
10645
10926
|
);
|
|
10646
10927
|
}
|
|
10647
10928
|
}
|
|
10929
|
+
async function resolveContinuationRequester(args) {
|
|
10930
|
+
const stored = args.sessionRecordRequester;
|
|
10931
|
+
if (stored?.platform === "slack" && stored.teamId === args.teamId && stored.userId === args.userId) {
|
|
10932
|
+
return createSlackResumeRequester({
|
|
10933
|
+
requester: stored,
|
|
10934
|
+
teamId: args.teamId,
|
|
10935
|
+
userId: args.userId
|
|
10936
|
+
});
|
|
10937
|
+
}
|
|
10938
|
+
const work = await getConversationWorkState2({
|
|
10939
|
+
conversationId: args.conversationId
|
|
10940
|
+
});
|
|
10941
|
+
const workRequester = work?.requester;
|
|
10942
|
+
if (workRequester && workRequester.teamId === args.teamId && workRequester.slackUserId === args.userId) {
|
|
10943
|
+
return createSlackRequester(args.teamId, args.userId, {
|
|
10944
|
+
email: workRequester.email,
|
|
10945
|
+
fullName: workRequester.fullName,
|
|
10946
|
+
userName: workRequester.slackUserName
|
|
10947
|
+
});
|
|
10948
|
+
}
|
|
10949
|
+
return void 0;
|
|
10950
|
+
}
|
|
10648
10951
|
function isContinuationResume(summary) {
|
|
10649
10952
|
return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield");
|
|
10650
10953
|
}
|
|
@@ -10708,11 +11011,20 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10708
11011
|
payload.destination,
|
|
10709
11012
|
"Slack continuation"
|
|
10710
11013
|
);
|
|
10711
|
-
const requester =
|
|
10712
|
-
|
|
11014
|
+
const requester = await resolveContinuationRequester({
|
|
11015
|
+
conversationId: payload.conversationId,
|
|
11016
|
+
sessionRecordRequester: activeSessionRecord.requester,
|
|
10713
11017
|
teamId: destination.teamId,
|
|
10714
11018
|
userId: userMessage2.author.userId
|
|
10715
11019
|
});
|
|
11020
|
+
if (!requester) {
|
|
11021
|
+
await failStrandedSessionWithFallback({
|
|
11022
|
+
conversationId: payload.conversationId,
|
|
11023
|
+
errorMessage: "Stored Slack requester missing for continuation",
|
|
11024
|
+
sessionRecord: activeSessionRecord
|
|
11025
|
+
});
|
|
11026
|
+
return false;
|
|
11027
|
+
}
|
|
10716
11028
|
if (!activeSessionRecord.source) {
|
|
10717
11029
|
await failAgentTurnSessionRecord({
|
|
10718
11030
|
conversationId: payload.conversationId,
|
|
@@ -10822,8 +11134,119 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10822
11134
|
}
|
|
10823
11135
|
});
|
|
10824
11136
|
}
|
|
11137
|
+
async function failStrandedSessionWithFallback(args) {
|
|
11138
|
+
await failAgentTurnSessionRecord({
|
|
11139
|
+
conversationId: args.conversationId,
|
|
11140
|
+
expectedVersion: args.sessionRecord.version,
|
|
11141
|
+
sessionId: args.sessionRecord.sessionId,
|
|
11142
|
+
errorMessage: args.errorMessage
|
|
11143
|
+
});
|
|
11144
|
+
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11145
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11146
|
+
markTurnFailed({
|
|
11147
|
+
conversation,
|
|
11148
|
+
nowMs: Date.now(),
|
|
11149
|
+
sessionId: args.sessionRecord.sessionId,
|
|
11150
|
+
userMessageId: getTurnUserMessage(
|
|
11151
|
+
conversation,
|
|
11152
|
+
args.sessionRecord.sessionId
|
|
11153
|
+
)?.id,
|
|
11154
|
+
markConversationMessage,
|
|
11155
|
+
updateConversationStats
|
|
11156
|
+
});
|
|
11157
|
+
await persistThreadStateById(args.conversationId, { conversation });
|
|
11158
|
+
const thread = parseSlackThreadId(args.conversationId);
|
|
11159
|
+
if (!thread) {
|
|
11160
|
+
return;
|
|
11161
|
+
}
|
|
11162
|
+
const eventName = "agent_turn_stranded_session_failed";
|
|
11163
|
+
const eventId = logException(
|
|
11164
|
+
new Error(args.errorMessage),
|
|
11165
|
+
eventName,
|
|
11166
|
+
{ conversationId: args.conversationId },
|
|
11167
|
+
{
|
|
11168
|
+
"app.ai.conversation_id": args.conversationId,
|
|
11169
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
11170
|
+
},
|
|
11171
|
+
"Stranded running agent session terminally failed"
|
|
11172
|
+
);
|
|
11173
|
+
await postSlackMessage({
|
|
11174
|
+
channelId: thread.channelId,
|
|
11175
|
+
threadTs: thread.threadTs,
|
|
11176
|
+
text: buildTurnFailureResponse(
|
|
11177
|
+
requireTurnFailureEventId(eventId, eventName)
|
|
11178
|
+
)
|
|
11179
|
+
});
|
|
11180
|
+
}
|
|
11181
|
+
async function recoverStrandedRunningSession(args) {
|
|
11182
|
+
const stateAdapter = getStateAdapter();
|
|
11183
|
+
await stateAdapter.connect();
|
|
11184
|
+
const probe = await acquireActiveLock(stateAdapter, args.conversationId);
|
|
11185
|
+
if (!probe) {
|
|
11186
|
+
return false;
|
|
11187
|
+
}
|
|
11188
|
+
await stateAdapter.releaseLock(probe);
|
|
11189
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
11190
|
+
args.conversationId,
|
|
11191
|
+
args.summary.sessionId
|
|
11192
|
+
);
|
|
11193
|
+
if (!sessionRecord || sessionRecord.state !== "running") {
|
|
11194
|
+
return false;
|
|
11195
|
+
}
|
|
11196
|
+
const parked = await persistYieldSessionRecord({
|
|
11197
|
+
channelName: sessionRecord.channelName,
|
|
11198
|
+
conversationId: args.conversationId,
|
|
11199
|
+
sessionId: sessionRecord.sessionId,
|
|
11200
|
+
currentSliceId: sessionRecord.sliceId,
|
|
11201
|
+
destination: sessionRecord.destination,
|
|
11202
|
+
source: sessionRecord.source,
|
|
11203
|
+
messages: sessionRecord.piMessages,
|
|
11204
|
+
errorMessage: "Recovered running session after hard worker death",
|
|
11205
|
+
logContext: { modelId: botConfig.modelId },
|
|
11206
|
+
requester: sessionRecord.requester,
|
|
11207
|
+
surface: sessionRecord.surface
|
|
11208
|
+
});
|
|
11209
|
+
if (!parked) {
|
|
11210
|
+
await failStrandedSessionWithFallback({
|
|
11211
|
+
conversationId: args.conversationId,
|
|
11212
|
+
errorMessage: "Stranded running session had no resumable boundary after worker death",
|
|
11213
|
+
sessionRecord
|
|
11214
|
+
});
|
|
11215
|
+
return false;
|
|
11216
|
+
}
|
|
11217
|
+
const request = await getAwaitingAgentContinueRequest({
|
|
11218
|
+
conversationId: args.conversationId,
|
|
11219
|
+
sessionId: sessionRecord.sessionId
|
|
11220
|
+
});
|
|
11221
|
+
if (!request) {
|
|
11222
|
+
await failStrandedSessionWithFallback({
|
|
11223
|
+
conversationId: args.conversationId,
|
|
11224
|
+
errorMessage: "Stranded running session could not materialize continuation metadata",
|
|
11225
|
+
sessionRecord: parked
|
|
11226
|
+
});
|
|
11227
|
+
return false;
|
|
11228
|
+
}
|
|
11229
|
+
if (await continueSlackAgentRunWithLockRetry(request, args.options)) {
|
|
11230
|
+
return true;
|
|
11231
|
+
}
|
|
11232
|
+
await failUnresumableContinuation({
|
|
11233
|
+
conversationId: args.conversationId,
|
|
11234
|
+
expectedVersion: request.expectedVersion,
|
|
11235
|
+
summary: args.summary,
|
|
11236
|
+
errorMessage: "Awaiting agent continuation was stale before it could run"
|
|
11237
|
+
});
|
|
11238
|
+
return false;
|
|
11239
|
+
}
|
|
10825
11240
|
async function resumeAwaitingSlackContinuation(conversationId, options = {}) {
|
|
10826
11241
|
const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
|
|
11242
|
+
const newest = summaries[0];
|
|
11243
|
+
if (newest?.state === "running") {
|
|
11244
|
+
return await recoverStrandedRunningSession({
|
|
11245
|
+
conversationId,
|
|
11246
|
+
options,
|
|
11247
|
+
summary: newest
|
|
11248
|
+
});
|
|
11249
|
+
}
|
|
10827
11250
|
for (const summary of summaries) {
|
|
10828
11251
|
if (!isContinuationResume(summary)) {
|
|
10829
11252
|
continue;
|
|
@@ -10889,7 +11312,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options = {}) {
|
|
|
10889
11312
|
},
|
|
10890
11313
|
"Agent continuation lock was busy; retrying"
|
|
10891
11314
|
);
|
|
10892
|
-
await
|
|
11315
|
+
await sleep2(delayMs);
|
|
10893
11316
|
}
|
|
10894
11317
|
}
|
|
10895
11318
|
return true;
|