@sentry/junior 0.85.0 → 0.86.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 +788 -333
- 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/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/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-GY7L6VCT.js → chunk-37B2R2QJ.js} +15 -10
- package/dist/{chunk-QTTTRBNB.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-ENEWQMRJ.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-MTE5NRPJ.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-EENOFKDN.js → chunk-ENPSU7L7.js} +165 -27
- package/dist/{chunk-MHEV3T2Y.js → chunk-FPHA6GCQ.js} +876 -719
- 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-HEBRPNN2.js} +27 -13
- package/package.json +6 -6
package/dist/app.js
CHANGED
|
@@ -3,14 +3,17 @@ 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,
|
|
@@ -18,12 +21,14 @@ import {
|
|
|
18
21
|
deliverResourceEventSubscription,
|
|
19
22
|
estimateTextTokens,
|
|
20
23
|
executeCredentialedEgressRequest,
|
|
24
|
+
finalizeFailedTurnReply,
|
|
21
25
|
finalizeMcpAuthorization,
|
|
22
26
|
findMatchingResourceEventSubscriptions,
|
|
23
27
|
formatProviderLabel,
|
|
24
28
|
generateAssistantReply,
|
|
25
29
|
generateConversationId,
|
|
26
30
|
getAgentContextCompactionTriggerTokens,
|
|
31
|
+
getAgentTurnDiagnosticsAttributes,
|
|
27
32
|
getChannelConfigurationService,
|
|
28
33
|
getChannelConfigurationServiceById,
|
|
29
34
|
getConfigDefaults,
|
|
@@ -40,6 +45,7 @@ import {
|
|
|
40
45
|
isRedundantReactionAckText,
|
|
41
46
|
isRetryableTurnError,
|
|
42
47
|
isTurnInputCommitLostError,
|
|
48
|
+
isTurnInputDeferredError,
|
|
43
49
|
listThreadReplies,
|
|
44
50
|
markConversationMessage,
|
|
45
51
|
markTurnClosed,
|
|
@@ -49,12 +55,15 @@ import {
|
|
|
49
55
|
normalizeSandboxEgressTracePropagationDomains,
|
|
50
56
|
parseOAuthStatePayload,
|
|
51
57
|
parseSandboxEgressCredentialToken,
|
|
58
|
+
persistCompletedSessionRecord,
|
|
52
59
|
persistThreadState,
|
|
53
60
|
persistThreadStateById,
|
|
61
|
+
persistYieldSessionRecord,
|
|
54
62
|
postSlackMessage,
|
|
55
63
|
processPluginTask,
|
|
56
64
|
removeReactionFromMessage,
|
|
57
65
|
renderSlackLegacyAttachmentText,
|
|
66
|
+
requireTurnFailureEventId,
|
|
58
67
|
resolveBaseUrl,
|
|
59
68
|
resolvePluginOAuthAccount,
|
|
60
69
|
resolveSandboxEgressProviderForHost,
|
|
@@ -68,37 +77,41 @@ import {
|
|
|
68
77
|
updateConversationStats,
|
|
69
78
|
uploadFilesToThread,
|
|
70
79
|
upsertConversationMessage
|
|
71
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-FPHA6GCQ.js";
|
|
72
81
|
import {
|
|
73
82
|
CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
|
|
74
83
|
CONVERSATION_WORK_STALE_ENQUEUE_MS,
|
|
84
|
+
ackMessages,
|
|
75
85
|
appendInboundMessage,
|
|
76
86
|
checkInConversationWork,
|
|
77
87
|
clearConsumedConversationWake,
|
|
78
88
|
clearExpiredConversationLease,
|
|
79
89
|
completeConversationWork,
|
|
90
|
+
deadLetterAttempt,
|
|
80
91
|
drainConversationMailbox,
|
|
81
92
|
getConversation,
|
|
82
93
|
getConversationWorkState,
|
|
94
|
+
isFinalAttempt,
|
|
95
|
+
isInvalidConversationRecordError,
|
|
83
96
|
listActiveConversationIds,
|
|
84
|
-
markConversationMessagesInjected,
|
|
85
97
|
markConversationWorkEnqueued,
|
|
98
|
+
recordAttemptFailure,
|
|
86
99
|
releaseConversationWork,
|
|
87
100
|
removeActiveConversation,
|
|
88
101
|
requestConversationContinuation,
|
|
89
102
|
requestConversationWork,
|
|
90
103
|
startConversationWork
|
|
91
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-ENPSU7L7.js";
|
|
92
105
|
import {
|
|
93
106
|
JUNIOR_THREAD_STATE_TTL_MS,
|
|
94
107
|
coerceThreadConversationState
|
|
95
108
|
} from "./chunk-66NX7MNW.js";
|
|
96
|
-
import "./chunk-
|
|
109
|
+
import "./chunk-RIB3M6YA.js";
|
|
97
110
|
import {
|
|
98
111
|
getVercelConversationWorkQueue,
|
|
99
112
|
resolveConversationWorkQueueTopic,
|
|
100
113
|
verifyConversationQueueMessage
|
|
101
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-6O5UI3RG.js";
|
|
102
115
|
import {
|
|
103
116
|
PLUGIN_TASK_QUEUE_TOPIC,
|
|
104
117
|
createVercelQueueClient,
|
|
@@ -107,18 +120,18 @@ import {
|
|
|
107
120
|
import {
|
|
108
121
|
GET,
|
|
109
122
|
buildSentryConversationUrl,
|
|
123
|
+
conversationVisibilityFromSlackChannelType,
|
|
110
124
|
initConversationContext,
|
|
111
125
|
resolveSlackChannelTypeFromMessage,
|
|
112
126
|
resolveSlackConversationContext,
|
|
113
127
|
setConversationTitle
|
|
114
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-JRXCSSSU.js";
|
|
115
129
|
import {
|
|
116
130
|
abandonAgentTurnSessionRecord,
|
|
117
131
|
buildSlackOutputMessage,
|
|
118
132
|
commitMessages,
|
|
119
133
|
failAgentTurnSessionRecord,
|
|
120
134
|
getAgentTurnSessionRecord,
|
|
121
|
-
getInterruptionMarker,
|
|
122
135
|
listAgentTurnSessionSummariesForConversation,
|
|
123
136
|
loadProjection,
|
|
124
137
|
normalizeSlackStatusText,
|
|
@@ -126,7 +139,7 @@ import {
|
|
|
126
139
|
recordAuthorizationCompleted,
|
|
127
140
|
splitSlackReplyText,
|
|
128
141
|
truncateStatusText
|
|
129
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-37B2R2QJ.js";
|
|
130
143
|
import {
|
|
131
144
|
validatePluginEgressCredentialHooks,
|
|
132
145
|
validatePluginRegistrations
|
|
@@ -146,20 +159,20 @@ import {
|
|
|
146
159
|
setPlugins,
|
|
147
160
|
validatePlugins,
|
|
148
161
|
verifySlackDirectCredentialSubject
|
|
149
|
-
} from "./chunk-
|
|
162
|
+
} from "./chunk-2NFV5FMB.js";
|
|
150
163
|
import {
|
|
151
164
|
createPluginLogger,
|
|
152
165
|
createPluginState
|
|
153
|
-
} from "./chunk-
|
|
166
|
+
} from "./chunk-BRSQQRG6.js";
|
|
154
167
|
import {
|
|
155
168
|
getConversationStore,
|
|
156
169
|
getDb
|
|
157
|
-
} from "./chunk-
|
|
170
|
+
} from "./chunk-6I6HBOQM.js";
|
|
158
171
|
import "./chunk-G3E7SCME.js";
|
|
159
172
|
import {
|
|
160
173
|
acquireActiveLock,
|
|
161
174
|
getStateAdapter
|
|
162
|
-
} from "./chunk-
|
|
175
|
+
} from "./chunk-GB5DFM4D.js";
|
|
163
176
|
import {
|
|
164
177
|
SlackActionError,
|
|
165
178
|
createSlackDestination,
|
|
@@ -167,15 +180,17 @@ import {
|
|
|
167
180
|
downloadPrivateSlackFile,
|
|
168
181
|
getHeaderString,
|
|
169
182
|
getSlackClient,
|
|
183
|
+
getWorkspaceTeamId,
|
|
170
184
|
isDmChannel,
|
|
171
185
|
normalizeSlackConversationId,
|
|
172
186
|
parseDestination,
|
|
173
187
|
requireSlackDestination,
|
|
188
|
+
runWithSlackInstallationToken,
|
|
189
|
+
runWithWorkspaceTeamId,
|
|
174
190
|
sameDestination
|
|
175
|
-
} from "./chunk-
|
|
191
|
+
} from "./chunk-GGD6WK6V.js";
|
|
176
192
|
import {
|
|
177
193
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
178
|
-
GEN_AI_PROVIDER_NAME,
|
|
179
194
|
botConfig,
|
|
180
195
|
completeObject,
|
|
181
196
|
completeText,
|
|
@@ -193,12 +208,12 @@ import {
|
|
|
193
208
|
stripRuntimeTurnContext,
|
|
194
209
|
trimTrailingAssistantMessages,
|
|
195
210
|
unwrapCurrentInstruction
|
|
196
|
-
} from "./chunk-
|
|
211
|
+
} from "./chunk-ZU2ALUVQ.js";
|
|
197
212
|
import {
|
|
198
213
|
parseSlackThreadId,
|
|
199
214
|
resolveSlackChannelIdFromMessage,
|
|
200
215
|
resolveSlackChannelIdFromThreadId
|
|
201
|
-
} from "./chunk-
|
|
216
|
+
} from "./chunk-L7OHKDOX.js";
|
|
202
217
|
import {
|
|
203
218
|
discoverSkills
|
|
204
219
|
} from "./chunk-64ACM6AP.js";
|
|
@@ -495,96 +510,25 @@ async function postSlackApiReplyPosts(args) {
|
|
|
495
510
|
return lastPostedMessageTs;
|
|
496
511
|
}
|
|
497
512
|
|
|
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
|
-
};
|
|
513
|
+
// src/chat/services/persist-retry.ts
|
|
514
|
+
var PERSIST_ATTEMPTS = 3;
|
|
515
|
+
function sleep(ms) {
|
|
516
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
557
517
|
}
|
|
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"
|
|
518
|
+
async function persistWithRetry(persist) {
|
|
519
|
+
let lastError;
|
|
520
|
+
for (let attempt = 1; attempt <= PERSIST_ATTEMPTS; attempt += 1) {
|
|
521
|
+
try {
|
|
522
|
+
await persist();
|
|
523
|
+
return;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
lastError = error;
|
|
526
|
+
if (attempt < PERSIST_ATTEMPTS) {
|
|
527
|
+
await sleep(attempt * 100);
|
|
528
|
+
}
|
|
586
529
|
}
|
|
587
|
-
}
|
|
530
|
+
}
|
|
531
|
+
throw lastError;
|
|
588
532
|
}
|
|
589
533
|
|
|
590
534
|
// src/chat/agent-dispatch/signing.ts
|
|
@@ -1035,6 +979,16 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1035
979
|
}
|
|
1036
980
|
let dispatch = claimedDispatch;
|
|
1037
981
|
const conversationId = getDispatchConversationId(dispatch);
|
|
982
|
+
const turnId = getDispatchTurnId(dispatch.id);
|
|
983
|
+
const logContext = {
|
|
984
|
+
conversationId,
|
|
985
|
+
slackThreadId: conversationId,
|
|
986
|
+
slackChannelId: dispatch.destination.channelId,
|
|
987
|
+
runId: dispatch.id,
|
|
988
|
+
actorType: dispatch.actor.type,
|
|
989
|
+
actorId: dispatch.actor.id,
|
|
990
|
+
assistantUserName: botConfig.userName
|
|
991
|
+
};
|
|
1038
992
|
const destinationLockId = getDispatchDestinationLockId(dispatch.destination);
|
|
1039
993
|
const stateAdapter = getStateAdapter();
|
|
1040
994
|
await stateAdapter.connect();
|
|
@@ -1119,7 +1073,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1119
1073
|
correlation: {
|
|
1120
1074
|
conversationId,
|
|
1121
1075
|
threadId: conversationId,
|
|
1122
|
-
turnId
|
|
1076
|
+
turnId,
|
|
1123
1077
|
runId: dispatch.id,
|
|
1124
1078
|
channelId: dispatch.destination.channelId,
|
|
1125
1079
|
teamId: dispatch.destination.teamId
|
|
@@ -1159,13 +1113,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1159
1113
|
reply,
|
|
1160
1114
|
logException,
|
|
1161
1115
|
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,
|
|
1116
|
+
...logContext,
|
|
1169
1117
|
modelId: reply.diagnostics.modelId
|
|
1170
1118
|
}
|
|
1171
1119
|
});
|
|
@@ -1199,13 +1147,45 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1199
1147
|
});
|
|
1200
1148
|
updateConversationStats(conversation);
|
|
1201
1149
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : artifacts;
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1150
|
+
try {
|
|
1151
|
+
await persistWithRetry(
|
|
1152
|
+
() => persistRuntimePatch({
|
|
1153
|
+
threadId: conversationId,
|
|
1154
|
+
conversation,
|
|
1155
|
+
artifacts: nextArtifacts,
|
|
1156
|
+
sandboxId: reply.sandboxId ?? sandboxId,
|
|
1157
|
+
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash ?? sandboxDependencyProfileHash
|
|
1158
|
+
})
|
|
1159
|
+
);
|
|
1160
|
+
} catch (persistError) {
|
|
1161
|
+
logException(
|
|
1162
|
+
persistError,
|
|
1163
|
+
"agent_dispatch_post_delivery_persist_failed",
|
|
1164
|
+
logContext,
|
|
1165
|
+
{},
|
|
1166
|
+
"Failed to persist delivered dispatch state after Slack accepted the reply"
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
if (reply.piMessages?.length) {
|
|
1170
|
+
await completeDeliveredTurn({
|
|
1171
|
+
conversationId,
|
|
1172
|
+
sessionId: turnId,
|
|
1173
|
+
sliceId: 1,
|
|
1174
|
+
messages: reply.piMessages,
|
|
1175
|
+
durationMs: reply.diagnostics.durationMs,
|
|
1176
|
+
usage: reply.diagnostics.usage,
|
|
1177
|
+
destination: dispatch.destination,
|
|
1178
|
+
source: dispatch.source,
|
|
1179
|
+
surface: "api",
|
|
1180
|
+
logContext: {
|
|
1181
|
+
threadId: conversationId,
|
|
1182
|
+
channelId: dispatch.destination.channelId,
|
|
1183
|
+
runId: dispatch.id,
|
|
1184
|
+
assistantUserName: botConfig.userName,
|
|
1185
|
+
modelId: reply.diagnostics.modelId
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1209
1189
|
dispatch = await markDispatch({
|
|
1210
1190
|
dispatch,
|
|
1211
1191
|
status: failure ? "failed" : "completed",
|
|
@@ -1216,21 +1196,13 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1216
1196
|
try {
|
|
1217
1197
|
await scheduleCompletedTasks({
|
|
1218
1198
|
conversationId,
|
|
1219
|
-
sessionId:
|
|
1199
|
+
sessionId: turnId
|
|
1220
1200
|
});
|
|
1221
1201
|
} catch (error) {
|
|
1222
1202
|
logException(
|
|
1223
1203
|
error,
|
|
1224
1204
|
"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
|
-
},
|
|
1205
|
+
logContext,
|
|
1234
1206
|
{},
|
|
1235
1207
|
"Plugin session.completed task scheduling failed"
|
|
1236
1208
|
);
|
|
@@ -1279,13 +1251,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1279
1251
|
error,
|
|
1280
1252
|
"agent_dispatch_run_failed",
|
|
1281
1253
|
{
|
|
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,
|
|
1254
|
+
...logContext,
|
|
1289
1255
|
modelId: botConfig.modelId
|
|
1290
1256
|
},
|
|
1291
1257
|
{},
|
|
@@ -1506,8 +1472,8 @@ async function drainConversationMailbox2(args) {
|
|
|
1506
1472
|
}
|
|
1507
1473
|
return result;
|
|
1508
1474
|
}
|
|
1509
|
-
async function
|
|
1510
|
-
const result = await
|
|
1475
|
+
async function ackMessages2(args) {
|
|
1476
|
+
const result = await ackMessages(args);
|
|
1511
1477
|
await recordExecutionMetadata(args);
|
|
1512
1478
|
return result;
|
|
1513
1479
|
}
|
|
@@ -1526,6 +1492,18 @@ async function completeConversationWork2(args) {
|
|
|
1526
1492
|
await recordExecutionMetadata(args);
|
|
1527
1493
|
return result;
|
|
1528
1494
|
}
|
|
1495
|
+
async function recordAttemptFailure2(args) {
|
|
1496
|
+
const result = await recordAttemptFailure(args);
|
|
1497
|
+
if (result.status === "recorded") {
|
|
1498
|
+
await recordExecutionMetadata(args);
|
|
1499
|
+
}
|
|
1500
|
+
return result;
|
|
1501
|
+
}
|
|
1502
|
+
async function deadLetterAttempt2(args) {
|
|
1503
|
+
const result = await deadLetterAttempt(args);
|
|
1504
|
+
await recordExecutionMetadata(args);
|
|
1505
|
+
return result;
|
|
1506
|
+
}
|
|
1529
1507
|
async function clearExpiredConversationLease2(args) {
|
|
1530
1508
|
const result = await clearExpiredConversationLease(args);
|
|
1531
1509
|
await recordExecutionMetadata(args);
|
|
@@ -1593,6 +1571,10 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
1593
1571
|
await args.adapter.initialize({
|
|
1594
1572
|
getState: () => state
|
|
1595
1573
|
});
|
|
1574
|
+
const internals = args.adapter;
|
|
1575
|
+
if (internals.defaultBotTokenProvider && !args.adapter.botUserId) {
|
|
1576
|
+
throw new Error("Slack adapter initialized without a resolved bot user id");
|
|
1577
|
+
}
|
|
1596
1578
|
initializedAdapters.add(args.adapter);
|
|
1597
1579
|
}
|
|
1598
1580
|
function verifySlackSignature(args) {
|
|
@@ -1640,7 +1622,7 @@ async function runWithSlackInstallation(args) {
|
|
|
1640
1622
|
enterpriseId: args.installation.enterpriseId,
|
|
1641
1623
|
isEnterpriseInstall: args.installation.isEnterpriseInstall
|
|
1642
1624
|
},
|
|
1643
|
-
args.task
|
|
1625
|
+
() => runWithSlackInstallationToken(tokenContext.token, args.task)
|
|
1644
1626
|
);
|
|
1645
1627
|
}
|
|
1646
1628
|
|
|
@@ -2014,14 +1996,22 @@ function createSlackConversationWorker(options) {
|
|
|
2014
1996
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
2015
1997
|
const state = getConnectedState2(options.state);
|
|
2016
1998
|
await state.connect();
|
|
2017
|
-
const records = getPendingRecords(
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
state
|
|
2021
|
-
})
|
|
2022
|
-
);
|
|
1999
|
+
const records = getPendingRecords({
|
|
2000
|
+
execution: { pendingMessages: [...context.attempt.messages] }
|
|
2001
|
+
});
|
|
2023
2002
|
if (records.length === 0) {
|
|
2024
|
-
|
|
2003
|
+
const destination = requireSlackDestination(
|
|
2004
|
+
context.destination,
|
|
2005
|
+
"Slack continuation recovery"
|
|
2006
|
+
);
|
|
2007
|
+
await runWithSlackInstallation({
|
|
2008
|
+
adapter,
|
|
2009
|
+
installation: { teamId: destination.teamId },
|
|
2010
|
+
state,
|
|
2011
|
+
task: async () => {
|
|
2012
|
+
await options.resumeAwaitingContinuation(context.conversationId);
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2025
2015
|
return { status: "completed" };
|
|
2026
2016
|
}
|
|
2027
2017
|
const latestRecord = records[records.length - 1];
|
|
@@ -2071,33 +2061,22 @@ function createSlackConversationWorker(options) {
|
|
|
2071
2061
|
skipped,
|
|
2072
2062
|
totalSinceLastHandler: messages.length
|
|
2073
2063
|
};
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const markInitialMessagesInjected = async () => {
|
|
2079
|
-
if (initialMessagesPersisted) {
|
|
2080
|
-
return true;
|
|
2064
|
+
let initialMessagesAcked = false;
|
|
2065
|
+
const ack = async () => {
|
|
2066
|
+
if (initialMessagesAcked) {
|
|
2067
|
+
return;
|
|
2081
2068
|
}
|
|
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()) {
|
|
2069
|
+
try {
|
|
2070
|
+
await context.attempt.ack();
|
|
2071
|
+
initialMessagesAcked = true;
|
|
2072
|
+
} catch {
|
|
2094
2073
|
throw new TurnInputCommitLostError(
|
|
2095
|
-
`Conversation work lease lost before Slack
|
|
2074
|
+
`Conversation work lease lost before Slack inbox ack for ${context.conversationId}`
|
|
2096
2075
|
);
|
|
2097
2076
|
}
|
|
2098
2077
|
};
|
|
2099
|
-
const drainSteeringMessages = async (
|
|
2100
|
-
await context.
|
|
2078
|
+
const drainSteeringMessages = async (accept) => {
|
|
2079
|
+
await context.attempt.drain(async (pendingRecords) => {
|
|
2101
2080
|
const messages2 = pendingRecords.map((record) => {
|
|
2102
2081
|
const metadata = record.input.metadata;
|
|
2103
2082
|
if (!isSlackMetadata(metadata)) {
|
|
@@ -2112,7 +2091,7 @@ function createSlackConversationWorker(options) {
|
|
|
2112
2091
|
message
|
|
2113
2092
|
};
|
|
2114
2093
|
});
|
|
2115
|
-
return await
|
|
2094
|
+
return await accept(messages2);
|
|
2116
2095
|
});
|
|
2117
2096
|
};
|
|
2118
2097
|
try {
|
|
@@ -2121,7 +2100,8 @@ function createSlackConversationWorker(options) {
|
|
|
2121
2100
|
destination: context.destination,
|
|
2122
2101
|
messageContext,
|
|
2123
2102
|
drainSteeringMessages,
|
|
2124
|
-
|
|
2103
|
+
ack,
|
|
2104
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2125
2105
|
shouldYield: context.shouldYield
|
|
2126
2106
|
});
|
|
2127
2107
|
return;
|
|
@@ -2130,10 +2110,14 @@ function createSlackConversationWorker(options) {
|
|
|
2130
2110
|
destination: context.destination,
|
|
2131
2111
|
messageContext,
|
|
2132
2112
|
drainSteeringMessages,
|
|
2133
|
-
|
|
2113
|
+
ack,
|
|
2114
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2134
2115
|
shouldYield: context.shouldYield
|
|
2135
2116
|
});
|
|
2136
2117
|
} catch (error) {
|
|
2118
|
+
if (isTurnInputDeferredError(error)) {
|
|
2119
|
+
return { status: "deferred" };
|
|
2120
|
+
}
|
|
2137
2121
|
if (isCooperativeTurnYieldError(error)) {
|
|
2138
2122
|
return { status: "yielded" };
|
|
2139
2123
|
}
|
|
@@ -2144,7 +2128,7 @@ function createSlackConversationWorker(options) {
|
|
|
2144
2128
|
}
|
|
2145
2129
|
}
|
|
2146
2130
|
});
|
|
2147
|
-
if (turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2131
|
+
if (turnResult?.status === "deferred" || turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2148
2132
|
return turnResult;
|
|
2149
2133
|
}
|
|
2150
2134
|
return { status: "completed" };
|
|
@@ -2616,11 +2600,29 @@ async function recoverConversationWork(args) {
|
|
|
2616
2600
|
state: args.state
|
|
2617
2601
|
});
|
|
2618
2602
|
for (const conversationId of ids) {
|
|
2603
|
+
let work;
|
|
2619
2604
|
try {
|
|
2620
|
-
|
|
2605
|
+
work = await getConversationWorkState2({
|
|
2621
2606
|
conversationId,
|
|
2622
2607
|
state: args.state
|
|
2623
2608
|
});
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
logException(
|
|
2611
|
+
error,
|
|
2612
|
+
"conversation_work_recovery_failed",
|
|
2613
|
+
{ conversationId },
|
|
2614
|
+
{},
|
|
2615
|
+
"Conversation work heartbeat recovery failed"
|
|
2616
|
+
);
|
|
2617
|
+
if (isInvalidConversationRecordError(error)) {
|
|
2618
|
+
await removeActiveConversation2({
|
|
2619
|
+
conversationId,
|
|
2620
|
+
state: args.state
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
try {
|
|
2624
2626
|
if (!work) {
|
|
2625
2627
|
await removeActiveConversation2({
|
|
2626
2628
|
conversationId,
|
|
@@ -3529,17 +3531,6 @@ function isSlackTitlePermissionError(error) {
|
|
|
3529
3531
|
return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
|
|
3530
3532
|
}
|
|
3531
3533
|
|
|
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
3534
|
// src/chat/runtime/thread-context.ts
|
|
3544
3535
|
function escapeRegExp(value) {
|
|
3545
3536
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3734,8 +3725,8 @@ function buildAuthPauseResponse(slackUserId, providerDisplayName) {
|
|
|
3734
3725
|
}
|
|
3735
3726
|
|
|
3736
3727
|
// src/chat/runtime/request-deadline.ts
|
|
3737
|
-
import { AsyncLocalStorage
|
|
3738
|
-
var requestDeadlineStorage = new
|
|
3728
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3729
|
+
var requestDeadlineStorage = new AsyncLocalStorage();
|
|
3739
3730
|
function createTurnRequestDeadline(startedAtMs) {
|
|
3740
3731
|
const requestBudgetMs = Math.max(
|
|
3741
3732
|
1,
|
|
@@ -4014,6 +4005,27 @@ async function resumeSlackTurn(args) {
|
|
|
4014
4005
|
footer
|
|
4015
4006
|
});
|
|
4016
4007
|
finalReplyDelivered = true;
|
|
4008
|
+
if (replyContext.correlation?.conversationId && replyContext.correlation.turnId && reply.piMessages?.length) {
|
|
4009
|
+
await persistCompletedSessionRecord({
|
|
4010
|
+
conversationId: replyContext.correlation.conversationId,
|
|
4011
|
+
sessionId: replyContext.correlation.turnId,
|
|
4012
|
+
allMessages: reply.piMessages,
|
|
4013
|
+
currentDurationMs: reply.diagnostics.durationMs,
|
|
4014
|
+
currentUsage: reply.diagnostics.usage,
|
|
4015
|
+
destination: replyContext.destination,
|
|
4016
|
+
source: replyContext.source,
|
|
4017
|
+
requester: replyContext.requester,
|
|
4018
|
+
surface: "slack",
|
|
4019
|
+
logContext: {
|
|
4020
|
+
threadId: replyContext.correlation.threadId,
|
|
4021
|
+
requesterId: replyContext.requester?.userId,
|
|
4022
|
+
channelId: runArgs.channelId,
|
|
4023
|
+
runId: replyContext.correlation.runId,
|
|
4024
|
+
assistantUserName: botConfig.userName,
|
|
4025
|
+
modelId: reply.diagnostics.modelId
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4017
4029
|
await runArgs.onSuccess?.(reply);
|
|
4018
4030
|
if (reply.diagnostics.outcome === "success" && replyContext.correlation?.conversationId && replyContext.correlation.turnId) {
|
|
4019
4031
|
try {
|
|
@@ -5775,14 +5787,18 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5775
5787
|
const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
|
|
5776
5788
|
const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
|
|
5777
5789
|
const sourceTeam = typeof event.message.source_team === "string" ? event.message.source_team : void 0;
|
|
5790
|
+
const botId = typeof event.message.bot_id === "string" ? event.message.bot_id : void 0;
|
|
5791
|
+
const channelType = typeof event.channel_type === "string" ? event.channel_type : void 0;
|
|
5778
5792
|
const raw = {
|
|
5779
5793
|
channel: channelId,
|
|
5794
|
+
...channelType ? { channel_type: channelType } : {},
|
|
5780
5795
|
ts: messageTs,
|
|
5781
5796
|
thread_ts: threadTs,
|
|
5782
5797
|
user: userId,
|
|
5783
5798
|
...teamId ? { team_id: teamId } : {},
|
|
5784
5799
|
...userTeam ? { user_team: userTeam } : {},
|
|
5785
|
-
...sourceTeam ? { source_team: sourceTeam } : {}
|
|
5800
|
+
...sourceTeam ? { source_team: sourceTeam } : {},
|
|
5801
|
+
...botId ? { bot_id: botId } : {}
|
|
5786
5802
|
};
|
|
5787
5803
|
const message = new Message2({
|
|
5788
5804
|
id: getEditedMentionMessageId(messageTs),
|
|
@@ -5798,8 +5814,11 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5798
5814
|
// Raw message_changed payloads do not include profile fields.
|
|
5799
5815
|
userName: "",
|
|
5800
5816
|
fullName: "",
|
|
5801
|
-
|
|
5802
|
-
|
|
5817
|
+
// Mirror the fresh-message parse so the shared ingress author gate
|
|
5818
|
+
// applies: synthesized flags must never launder a bot-authored or
|
|
5819
|
+
// self-authored edit into a routable user message.
|
|
5820
|
+
isBot: Boolean(botId),
|
|
5821
|
+
isMe: userId === botUserId
|
|
5803
5822
|
}
|
|
5804
5823
|
});
|
|
5805
5824
|
Object.defineProperty(message, "adapter", {
|
|
@@ -6055,14 +6074,16 @@ async function handleMessageChanged(args) {
|
|
|
6055
6074
|
}
|
|
6056
6075
|
const botUserId = args.adapter.botUserId;
|
|
6057
6076
|
if (!botUserId) {
|
|
6058
|
-
|
|
6077
|
+
throw new Error(
|
|
6078
|
+
"Slack bot identity is unresolved; cannot classify message_changed event"
|
|
6079
|
+
);
|
|
6059
6080
|
}
|
|
6060
6081
|
const result = extractMessageChangedMention(
|
|
6061
6082
|
args.body,
|
|
6062
6083
|
botUserId,
|
|
6063
6084
|
args.adapter
|
|
6064
6085
|
);
|
|
6065
|
-
if (!result) {
|
|
6086
|
+
if (!result || shouldIgnoreMessage(result.message)) {
|
|
6066
6087
|
return true;
|
|
6067
6088
|
}
|
|
6068
6089
|
await persistSlackMessage({
|
|
@@ -6374,12 +6395,12 @@ async function handleSlackWebhook(args) {
|
|
|
6374
6395
|
try {
|
|
6375
6396
|
await eventTask;
|
|
6376
6397
|
} catch (error) {
|
|
6377
|
-
if (
|
|
6378
|
-
logException(error, "
|
|
6379
|
-
|
|
6398
|
+
if (error instanceof SlackEventPersistenceError) {
|
|
6399
|
+
logException(error.cause, "slack_event_persist_failed");
|
|
6400
|
+
} else {
|
|
6401
|
+
logException(error, "slack_event_routing_failed");
|
|
6380
6402
|
}
|
|
6381
|
-
|
|
6382
|
-
return new Response("Slack event persistence failed", { status: 503 });
|
|
6403
|
+
return new Response("Slack event handling failed", { status: 503 });
|
|
6383
6404
|
}
|
|
6384
6405
|
} else {
|
|
6385
6406
|
enqueue(
|
|
@@ -6529,6 +6550,33 @@ async function requestLostLeaseRecovery(args) {
|
|
|
6529
6550
|
state: args.options.state
|
|
6530
6551
|
});
|
|
6531
6552
|
}
|
|
6553
|
+
async function recordFailedDeliveryAttempt(args) {
|
|
6554
|
+
const failure = await recordAttemptFailure2({
|
|
6555
|
+
conversationId: args.conversationId,
|
|
6556
|
+
inboundMessageIds: args.messageIds,
|
|
6557
|
+
leaseToken: args.leaseToken,
|
|
6558
|
+
conversationStore: args.options.conversationStore,
|
|
6559
|
+
nowMs: args.nowMs,
|
|
6560
|
+
state: args.options.state
|
|
6561
|
+
});
|
|
6562
|
+
for (const message of failure.deadLetteredMessages) {
|
|
6563
|
+
logWarn(
|
|
6564
|
+
"conversation_work_dead_lettered",
|
|
6565
|
+
{ conversationId: args.conversationId },
|
|
6566
|
+
{
|
|
6567
|
+
"app.conversation.source": message.source,
|
|
6568
|
+
"app.inbound.attempt_count": message.attemptCount ?? 0,
|
|
6569
|
+
"app.inbound.message_id": message.inboundMessageId,
|
|
6570
|
+
"app.inbound.pending_count": failure.pendingCount
|
|
6571
|
+
},
|
|
6572
|
+
"Conversation work message consumed after exceeding the delivery attempt limit"
|
|
6573
|
+
);
|
|
6574
|
+
}
|
|
6575
|
+
return failure;
|
|
6576
|
+
}
|
|
6577
|
+
function isTerminalFailure(failure) {
|
|
6578
|
+
return failure.status === "recorded" && failure.deadLetteredMessages.length > 0 && failure.pendingCount === 0;
|
|
6579
|
+
}
|
|
6532
6580
|
function startLeaseCheckIn(args) {
|
|
6533
6581
|
const timer = setInterval(() => {
|
|
6534
6582
|
const nowMs = now2(args.options);
|
|
@@ -6566,10 +6614,22 @@ function startLeaseCheckIn(args) {
|
|
|
6566
6614
|
}
|
|
6567
6615
|
async function processConversationWork(message, options) {
|
|
6568
6616
|
const conversationId = message.conversationId;
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6617
|
+
let initial;
|
|
6618
|
+
try {
|
|
6619
|
+
initial = await getConversationWorkState2({
|
|
6620
|
+
conversationId,
|
|
6621
|
+
state: options.state
|
|
6622
|
+
});
|
|
6623
|
+
} catch (error) {
|
|
6624
|
+
if (isInvalidConversationRecordError(error)) {
|
|
6625
|
+
throw new ConversationQueueMessageRejectedError(
|
|
6626
|
+
"invalid_record",
|
|
6627
|
+
`Conversation record failed validation for ${conversationId}`,
|
|
6628
|
+
{ conversationId }
|
|
6629
|
+
);
|
|
6630
|
+
}
|
|
6631
|
+
throw error;
|
|
6632
|
+
}
|
|
6573
6633
|
if (!initial || countPendingConversationMessages(initial) === 0 && initial.execution.status === "idle" && !initial.execution.lease) {
|
|
6574
6634
|
if (initial) {
|
|
6575
6635
|
await clearConsumedConversationWake2({
|
|
@@ -6628,6 +6688,14 @@ async function processConversationWork(message, options) {
|
|
|
6628
6688
|
}
|
|
6629
6689
|
const startedAtMs = now2(options);
|
|
6630
6690
|
const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
|
|
6691
|
+
const leasedWork = await getConversationWorkState2({
|
|
6692
|
+
conversationId,
|
|
6693
|
+
state: options.state
|
|
6694
|
+
});
|
|
6695
|
+
const attemptMessages = leasedWork?.messages ?? initial.messages;
|
|
6696
|
+
const attemptMessageIds = attemptMessages.map(
|
|
6697
|
+
(message2) => message2.inboundMessageId
|
|
6698
|
+
);
|
|
6631
6699
|
let leaseLost = false;
|
|
6632
6700
|
const markLeaseLost = () => {
|
|
6633
6701
|
leaseLost = true;
|
|
@@ -6647,10 +6715,43 @@ async function processConversationWork(message, options) {
|
|
|
6647
6715
|
},
|
|
6648
6716
|
"Conversation work lease acquired"
|
|
6649
6717
|
);
|
|
6718
|
+
const drainInbox = (handle) => drainConversationMailbox2({
|
|
6719
|
+
conversationId,
|
|
6720
|
+
leaseToken: lease.leaseToken,
|
|
6721
|
+
conversationStore: options.conversationStore,
|
|
6722
|
+
handle,
|
|
6723
|
+
nowMs: now2(options),
|
|
6724
|
+
state: options.state
|
|
6725
|
+
});
|
|
6726
|
+
const ack = async () => {
|
|
6727
|
+
const acknowledged = await ackMessages2({
|
|
6728
|
+
conversationId,
|
|
6729
|
+
inboundMessageIds: attemptMessageIds,
|
|
6730
|
+
leaseToken: lease.leaseToken,
|
|
6731
|
+
conversationStore: options.conversationStore,
|
|
6732
|
+
nowMs: now2(options),
|
|
6733
|
+
state: options.state
|
|
6734
|
+
});
|
|
6735
|
+
if (!acknowledged) {
|
|
6736
|
+
markLeaseLost();
|
|
6737
|
+
throw new Error(
|
|
6738
|
+
`Conversation work lease lost before inbox ack for ${conversationId}`
|
|
6739
|
+
);
|
|
6740
|
+
}
|
|
6741
|
+
};
|
|
6650
6742
|
const workerContext = {
|
|
6743
|
+
attempt: {
|
|
6744
|
+
ack,
|
|
6745
|
+
conversationId,
|
|
6746
|
+
destination,
|
|
6747
|
+
drain: drainInbox,
|
|
6748
|
+
isFinalAttempt: attemptMessages.some(
|
|
6749
|
+
(message2) => isFinalAttempt(message2)
|
|
6750
|
+
),
|
|
6751
|
+
messages: attemptMessages
|
|
6752
|
+
},
|
|
6651
6753
|
conversationId,
|
|
6652
6754
|
destination,
|
|
6653
|
-
leaseToken: lease.leaseToken,
|
|
6654
6755
|
shouldYield: () => leaseLost || now2(options) >= softYieldDeadlineMs,
|
|
6655
6756
|
checkIn: async () => {
|
|
6656
6757
|
const checkedIn = await checkInConversationWork2({
|
|
@@ -6664,15 +6765,7 @@ async function processConversationWork(message, options) {
|
|
|
6664
6765
|
markLeaseLost();
|
|
6665
6766
|
}
|
|
6666
6767
|
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
|
-
})
|
|
6768
|
+
}
|
|
6676
6769
|
};
|
|
6677
6770
|
try {
|
|
6678
6771
|
const result = await options.run(workerContext);
|
|
@@ -6739,6 +6832,51 @@ async function processConversationWork(message, options) {
|
|
|
6739
6832
|
);
|
|
6740
6833
|
return { status: "yielded" };
|
|
6741
6834
|
}
|
|
6835
|
+
if (result.status === "deferred") {
|
|
6836
|
+
const deferredNowMs = now2(options);
|
|
6837
|
+
const released = await releaseConversationWork2({
|
|
6838
|
+
conversationId,
|
|
6839
|
+
leaseToken: lease.leaseToken,
|
|
6840
|
+
conversationStore: options.conversationStore,
|
|
6841
|
+
nowMs: deferredNowMs,
|
|
6842
|
+
state: options.state
|
|
6843
|
+
});
|
|
6844
|
+
if (!released) {
|
|
6845
|
+
return { status: "lost_lease" };
|
|
6846
|
+
}
|
|
6847
|
+
const wake = await ensureConversationWake({
|
|
6848
|
+
conversationId,
|
|
6849
|
+
conversationStore: options.conversationStore,
|
|
6850
|
+
idempotencyKey: nudgeIdempotencyKey(
|
|
6851
|
+
"deferred",
|
|
6852
|
+
conversationId,
|
|
6853
|
+
deferredNowMs
|
|
6854
|
+
),
|
|
6855
|
+
nowMs: deferredNowMs,
|
|
6856
|
+
queue: options.queue,
|
|
6857
|
+
state: options.state
|
|
6858
|
+
});
|
|
6859
|
+
return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
|
|
6860
|
+
}
|
|
6861
|
+
if (attemptMessageIds.length > 0) {
|
|
6862
|
+
const failure = await recordFailedDeliveryAttempt({
|
|
6863
|
+
conversationId,
|
|
6864
|
+
leaseToken: lease.leaseToken,
|
|
6865
|
+
nowMs: now2(options),
|
|
6866
|
+
messageIds: attemptMessageIds,
|
|
6867
|
+
options
|
|
6868
|
+
});
|
|
6869
|
+
if (isTerminalFailure(failure)) {
|
|
6870
|
+
await deadLetterAttempt2({
|
|
6871
|
+
conversationId,
|
|
6872
|
+
leaseToken: lease.leaseToken,
|
|
6873
|
+
conversationStore: options.conversationStore,
|
|
6874
|
+
nowMs: now2(options),
|
|
6875
|
+
state: options.state
|
|
6876
|
+
});
|
|
6877
|
+
return { status: "failed" };
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6742
6880
|
const completion = await completeConversationWork2({
|
|
6743
6881
|
conversationId,
|
|
6744
6882
|
leaseToken: lease.leaseToken,
|
|
@@ -6776,53 +6914,62 @@ async function processConversationWork(message, options) {
|
|
|
6776
6914
|
return { status: "completed" };
|
|
6777
6915
|
} catch (error) {
|
|
6778
6916
|
const errorNowMs = now2(options);
|
|
6917
|
+
let recoveryRecorded = false;
|
|
6779
6918
|
try {
|
|
6780
|
-
const
|
|
6919
|
+
const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
|
|
6781
6920
|
conversationId,
|
|
6782
|
-
destination,
|
|
6783
6921
|
leaseToken: lease.leaseToken,
|
|
6784
|
-
conversationStore: options.conversationStore,
|
|
6785
6922
|
nowMs: errorNowMs,
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6923
|
+
messageIds: attemptMessageIds,
|
|
6924
|
+
options
|
|
6925
|
+
}) : void 0;
|
|
6926
|
+
if (failure && isTerminalFailure(failure)) {
|
|
6927
|
+
await deadLetterAttempt2({
|
|
6928
|
+
conversationId,
|
|
6929
|
+
leaseToken: lease.leaseToken,
|
|
6930
|
+
conversationStore: options.conversationStore,
|
|
6931
|
+
nowMs: errorNowMs,
|
|
6932
|
+
state: options.state
|
|
6933
|
+
});
|
|
6934
|
+
} else {
|
|
6935
|
+
const continuationMarked = await requestConversationContinuation2({
|
|
6790
6936
|
conversationId,
|
|
6937
|
+
destination,
|
|
6938
|
+
leaseToken: lease.leaseToken,
|
|
6791
6939
|
conversationStore: options.conversationStore,
|
|
6792
|
-
|
|
6793
|
-
|
|
6940
|
+
nowMs: errorNowMs,
|
|
6941
|
+
state: options.state
|
|
6942
|
+
});
|
|
6943
|
+
if (continuationMarked) {
|
|
6944
|
+
await ensureConversationWake({
|
|
6794
6945
|
conversationId,
|
|
6795
|
-
|
|
6796
|
-
|
|
6946
|
+
conversationStore: options.conversationStore,
|
|
6947
|
+
idempotencyKey: nudgeIdempotencyKey(
|
|
6948
|
+
"error",
|
|
6949
|
+
conversationId,
|
|
6950
|
+
errorNowMs
|
|
6951
|
+
),
|
|
6952
|
+
nowMs: errorNowMs,
|
|
6953
|
+
queue: options.queue,
|
|
6954
|
+
state: options.state
|
|
6955
|
+
});
|
|
6956
|
+
}
|
|
6957
|
+
await releaseConversationWork2({
|
|
6958
|
+
conversationId,
|
|
6959
|
+
leaseToken: lease.leaseToken,
|
|
6960
|
+
conversationStore: options.conversationStore,
|
|
6797
6961
|
nowMs: errorNowMs,
|
|
6798
|
-
queue: options.queue,
|
|
6799
6962
|
state: options.state
|
|
6800
6963
|
});
|
|
6801
6964
|
}
|
|
6802
|
-
|
|
6965
|
+
recoveryRecorded = true;
|
|
6966
|
+
} catch (recoveryError) {
|
|
6803
6967
|
logException(
|
|
6804
|
-
|
|
6968
|
+
recoveryError,
|
|
6805
6969
|
"conversation_work_requeue_failed",
|
|
6806
6970
|
{ conversationId },
|
|
6807
6971
|
{},
|
|
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"
|
|
6972
|
+
"Conversation work recovery failed after runner error"
|
|
6826
6973
|
);
|
|
6827
6974
|
}
|
|
6828
6975
|
if (!isProviderRetryError(error)) {
|
|
@@ -6836,7 +6983,10 @@ async function processConversationWork(message, options) {
|
|
|
6836
6983
|
"Conversation work failed"
|
|
6837
6984
|
);
|
|
6838
6985
|
}
|
|
6839
|
-
|
|
6986
|
+
if (!recoveryRecorded) {
|
|
6987
|
+
throw error;
|
|
6988
|
+
}
|
|
6989
|
+
return { status: "failed" };
|
|
6840
6990
|
} finally {
|
|
6841
6991
|
clearInterval(timer);
|
|
6842
6992
|
}
|
|
@@ -7360,7 +7510,7 @@ function combineTurnText(queuedMessages, latestText) {
|
|
|
7360
7510
|
// src/chat/runtime/slack-runtime.ts
|
|
7361
7511
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
7362
7512
|
function shouldRethrowTurnControlError(error) {
|
|
7363
|
-
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isProviderRetryError(error);
|
|
7513
|
+
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isTurnInputDeferredError(error) || isProviderRetryError(error);
|
|
7364
7514
|
}
|
|
7365
7515
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
7366
7516
|
if (!args.decision?.shouldUnsubscribe) {
|
|
@@ -7394,13 +7544,13 @@ function createAcceptedSteeringDrain(hooks, options) {
|
|
|
7394
7544
|
if (!hooks.drainSteeringMessages) {
|
|
7395
7545
|
return void 0;
|
|
7396
7546
|
}
|
|
7397
|
-
return async (
|
|
7547
|
+
return async (accept, context) => {
|
|
7398
7548
|
let interruptedMessages;
|
|
7399
7549
|
await hooks.drainSteeringMessages(async (messages) => {
|
|
7400
7550
|
const selection = await options.selectMessages(messages, context);
|
|
7401
7551
|
await options.onSkipped?.(selection.skipped);
|
|
7402
7552
|
const interrupted = selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.message);
|
|
7403
|
-
await
|
|
7553
|
+
await accept(getQueuedMessagesFromSlackMessages(interrupted, options));
|
|
7404
7554
|
interruptedMessages = interrupted;
|
|
7405
7555
|
await options.onAcceptedForProcessing?.(interrupted);
|
|
7406
7556
|
return [
|
|
@@ -7567,7 +7717,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7567
7717
|
completedAtMs
|
|
7568
7718
|
});
|
|
7569
7719
|
}
|
|
7570
|
-
await args.
|
|
7720
|
+
await args.ack?.();
|
|
7571
7721
|
};
|
|
7572
7722
|
const selectAcceptedSteeringMessages = async (args) => {
|
|
7573
7723
|
const selected = [];
|
|
@@ -7653,6 +7803,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7653
7803
|
let processingReaction;
|
|
7654
7804
|
const skippedSteeringMessages = [];
|
|
7655
7805
|
let completed = false;
|
|
7806
|
+
let acked = false;
|
|
7656
7807
|
const onTurnCompleted = async () => {
|
|
7657
7808
|
completed = true;
|
|
7658
7809
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7701,8 +7852,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7701
7852
|
)
|
|
7702
7853
|
);
|
|
7703
7854
|
};
|
|
7704
|
-
const
|
|
7705
|
-
await hooks.
|
|
7855
|
+
const ack = async () => {
|
|
7856
|
+
await hooks.ack?.();
|
|
7857
|
+
acked = true;
|
|
7706
7858
|
await startQueuedProcessingReactions();
|
|
7707
7859
|
};
|
|
7708
7860
|
const drainSteeringMessages = createAcceptedSteeringDrain(hooks, {
|
|
@@ -7732,7 +7884,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7732
7884
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7733
7885
|
destination: hooks.destination,
|
|
7734
7886
|
queuedMessages,
|
|
7735
|
-
|
|
7887
|
+
ack,
|
|
7736
7888
|
onToolInvocation: toolInvocationHook,
|
|
7737
7889
|
onTurnCompleted,
|
|
7738
7890
|
drainSteeringMessages,
|
|
@@ -7786,6 +7938,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7786
7938
|
{},
|
|
7787
7939
|
"onNewMention failed"
|
|
7788
7940
|
);
|
|
7941
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
7942
|
+
return;
|
|
7943
|
+
}
|
|
7789
7944
|
if (!eventId) {
|
|
7790
7945
|
throw new Error(
|
|
7791
7946
|
"Sentry did not return an event ID for mention_handler_failed"
|
|
@@ -7823,6 +7978,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7823
7978
|
let processingReaction;
|
|
7824
7979
|
const skippedSteeringMessages = [];
|
|
7825
7980
|
let completed = false;
|
|
7981
|
+
let acked = false;
|
|
7826
7982
|
const onTurnCompleted = async () => {
|
|
7827
7983
|
completed = true;
|
|
7828
7984
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7889,7 +8045,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7889
8045
|
message,
|
|
7890
8046
|
decision: { shouldReply: false, reason },
|
|
7891
8047
|
context: threadContext,
|
|
7892
|
-
|
|
8048
|
+
ack: hooks.ack,
|
|
7893
8049
|
text: combinedText
|
|
7894
8050
|
});
|
|
7895
8051
|
return;
|
|
@@ -7926,7 +8082,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7926
8082
|
message,
|
|
7927
8083
|
decision,
|
|
7928
8084
|
context: threadContext,
|
|
7929
|
-
|
|
8085
|
+
ack: hooks.ack,
|
|
7930
8086
|
preparedState,
|
|
7931
8087
|
text: combinedText
|
|
7932
8088
|
});
|
|
@@ -7938,7 +8094,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7938
8094
|
message,
|
|
7939
8095
|
decision,
|
|
7940
8096
|
context: threadContext,
|
|
7941
|
-
|
|
8097
|
+
ack: hooks.ack,
|
|
7942
8098
|
preparedState,
|
|
7943
8099
|
text: combinedText
|
|
7944
8100
|
});
|
|
@@ -7983,8 +8139,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7983
8139
|
)
|
|
7984
8140
|
);
|
|
7985
8141
|
};
|
|
7986
|
-
const
|
|
7987
|
-
await hooks.
|
|
8142
|
+
const ack = async () => {
|
|
8143
|
+
await hooks.ack?.();
|
|
8144
|
+
acked = true;
|
|
7988
8145
|
await startQueuedProcessingReactions();
|
|
7989
8146
|
};
|
|
7990
8147
|
const toolInvocationHook = createToolInvocationHook(
|
|
@@ -7997,7 +8154,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7997
8154
|
preparedState,
|
|
7998
8155
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7999
8156
|
queuedMessages,
|
|
8000
|
-
|
|
8157
|
+
ack,
|
|
8001
8158
|
onToolInvocation: toolInvocationHook,
|
|
8002
8159
|
onTurnCompleted,
|
|
8003
8160
|
drainSteeringMessages,
|
|
@@ -8051,6 +8208,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
8051
8208
|
{},
|
|
8052
8209
|
"onSubscribedMessage failed"
|
|
8053
8210
|
);
|
|
8211
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
8212
|
+
return;
|
|
8213
|
+
}
|
|
8054
8214
|
if (!eventId) {
|
|
8055
8215
|
throw new Error(
|
|
8056
8216
|
"Sentry did not return an event ID for subscribed_message_handler_failed"
|
|
@@ -9002,6 +9162,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
9002
9162
|
}
|
|
9003
9163
|
|
|
9004
9164
|
// src/chat/runtime/reply-executor.ts
|
|
9165
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
9005
9166
|
import { createSlackSource } from "@sentry/junior-plugin-api";
|
|
9006
9167
|
|
|
9007
9168
|
// src/chat/slack/message.ts
|
|
@@ -9134,6 +9295,9 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
|
|
|
9134
9295
|
}
|
|
9135
9296
|
|
|
9136
9297
|
// src/chat/runtime/reply-executor.ts
|
|
9298
|
+
async function persistThreadStateWithRetry(thread, patch) {
|
|
9299
|
+
await persistWithRetry(() => persistThreadState(thread, patch));
|
|
9300
|
+
}
|
|
9137
9301
|
function collectCanvasUrls(artifacts) {
|
|
9138
9302
|
return new Set(
|
|
9139
9303
|
[
|
|
@@ -9145,6 +9309,14 @@ function collectCanvasUrls(artifacts) {
|
|
|
9145
9309
|
function turnRequester(requester) {
|
|
9146
9310
|
return toStoredSlackRequester(requester);
|
|
9147
9311
|
}
|
|
9312
|
+
function parkedInputKey(message) {
|
|
9313
|
+
if (message.role !== "user") {
|
|
9314
|
+
return void 0;
|
|
9315
|
+
}
|
|
9316
|
+
const first = Array.isArray(message.content) ? message.content[0] : void 0;
|
|
9317
|
+
const text = first && typeof first === "object" && "text" in first ? String(first.text ?? "") : "";
|
|
9318
|
+
return `${message.timestamp}:${text}`;
|
|
9319
|
+
}
|
|
9148
9320
|
function isResourceEventMessage(message) {
|
|
9149
9321
|
const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
|
|
9150
9322
|
return raw?.event_type === "resource_event";
|
|
@@ -9226,6 +9398,7 @@ function createReplyToThread(deps) {
|
|
|
9226
9398
|
channelName,
|
|
9227
9399
|
channelType: slackChannelType
|
|
9228
9400
|
});
|
|
9401
|
+
const destinationVisibility = conversationVisibilityFromSlackChannelType(slackChannelType);
|
|
9229
9402
|
const threadTs = getThreadTs(threadId);
|
|
9230
9403
|
const assistantThreadContext = getAssistantThreadContext(message);
|
|
9231
9404
|
const messageTs = getMessageTs(message);
|
|
@@ -9238,7 +9411,8 @@ function createReplyToThread(deps) {
|
|
|
9238
9411
|
channelId: channelId ?? destination.channelId,
|
|
9239
9412
|
messageTs,
|
|
9240
9413
|
teamId,
|
|
9241
|
-
threadTs
|
|
9414
|
+
threadTs,
|
|
9415
|
+
type: destinationVisibility === "public" ? "pub" : "priv"
|
|
9242
9416
|
});
|
|
9243
9417
|
const runId = getRunId(thread, message);
|
|
9244
9418
|
const conversationId = threadId ?? runId;
|
|
@@ -9365,12 +9539,87 @@ function createReplyToThread(deps) {
|
|
|
9365
9539
|
}
|
|
9366
9540
|
};
|
|
9367
9541
|
let activeTurnId = preparedState.conversation.processing.activeTurnId;
|
|
9542
|
+
const resolveSteeringMessages = async (queuedMessages) => {
|
|
9543
|
+
return await Promise.all(
|
|
9544
|
+
queuedMessages.map(async (queued) => {
|
|
9545
|
+
const attachments = queued.message.attachments;
|
|
9546
|
+
return {
|
|
9547
|
+
text: queued.userText,
|
|
9548
|
+
timestampMs: queued.message.metadata.dateSent.getTime(),
|
|
9549
|
+
omittedImageAttachmentCount: !isVisionEnabled() && hasPotentialImageAttachment(attachments) ? countPotentialImageAttachments(attachments) : 0,
|
|
9550
|
+
userAttachments: await deps.resolveUserAttachments(
|
|
9551
|
+
attachments,
|
|
9552
|
+
{
|
|
9553
|
+
threadId,
|
|
9554
|
+
requesterId: isResourceEventMessage(queued.message) ? void 0 : queued.message.author.userId,
|
|
9555
|
+
channelId,
|
|
9556
|
+
runId,
|
|
9557
|
+
conversation: preparedState.conversation,
|
|
9558
|
+
messageTs: getSlackMessageTs(queued.message)
|
|
9559
|
+
}
|
|
9560
|
+
)
|
|
9561
|
+
};
|
|
9562
|
+
})
|
|
9563
|
+
);
|
|
9564
|
+
};
|
|
9565
|
+
const appendParkedTurnInput = async (parkedSessionId) => {
|
|
9566
|
+
if (!conversationId) {
|
|
9567
|
+
return true;
|
|
9568
|
+
}
|
|
9569
|
+
const parkedMessages = [
|
|
9570
|
+
...options.queuedMessages ?? [],
|
|
9571
|
+
{
|
|
9572
|
+
explicitMention: Boolean(
|
|
9573
|
+
options.explicitMention || message.isMention
|
|
9574
|
+
),
|
|
9575
|
+
message,
|
|
9576
|
+
rawText: currentText.rawText,
|
|
9577
|
+
userText: currentText.userText
|
|
9578
|
+
}
|
|
9579
|
+
].filter(
|
|
9580
|
+
// Redelivery of the parked turn's own message must not duplicate
|
|
9581
|
+
// the prompt that already started the session.
|
|
9582
|
+
(queued) => buildDeterministicTurnId(queued.message.id) !== parkedSessionId
|
|
9583
|
+
);
|
|
9584
|
+
if (parkedMessages.length === 0) {
|
|
9585
|
+
return true;
|
|
9586
|
+
}
|
|
9587
|
+
const stateAdapter = getStateAdapter();
|
|
9588
|
+
await stateAdapter.connect();
|
|
9589
|
+
const lock = await acquireActiveLock(stateAdapter, conversationId);
|
|
9590
|
+
if (!lock) {
|
|
9591
|
+
return false;
|
|
9592
|
+
}
|
|
9593
|
+
try {
|
|
9594
|
+
const piMessages = (await resolveSteeringMessages(parkedMessages)).map(buildSteeringPiMessage);
|
|
9595
|
+
const projection = await loadProjection({ conversationId });
|
|
9596
|
+
const appendedKeys = new Set(
|
|
9597
|
+
projection.map(parkedInputKey).filter((key) => key !== void 0)
|
|
9598
|
+
);
|
|
9599
|
+
const missing = piMessages.filter((piMessage) => {
|
|
9600
|
+
const key = parkedInputKey(piMessage);
|
|
9601
|
+
return key === void 0 || !appendedKeys.has(key);
|
|
9602
|
+
});
|
|
9603
|
+
if (missing.length === 0) {
|
|
9604
|
+
return true;
|
|
9605
|
+
}
|
|
9606
|
+
await commitMessages({
|
|
9607
|
+
conversationId,
|
|
9608
|
+
messages: [...projection, ...missing],
|
|
9609
|
+
requester: storedRequester,
|
|
9610
|
+
ttlMs: THREAD_STATE_TTL_MS4
|
|
9611
|
+
});
|
|
9612
|
+
return true;
|
|
9613
|
+
} finally {
|
|
9614
|
+
await stateAdapter.releaseLock(lock);
|
|
9615
|
+
}
|
|
9616
|
+
};
|
|
9368
9617
|
if (preparedState.userMessageAlreadyReplied) {
|
|
9369
9618
|
await persistThreadState(thread, {
|
|
9370
9619
|
conversation: preparedState.conversation
|
|
9371
9620
|
});
|
|
9372
9621
|
await options.onTurnStatePersisted?.();
|
|
9373
|
-
await options.
|
|
9622
|
+
await options.ack?.();
|
|
9374
9623
|
await options.onTurnCompleted?.();
|
|
9375
9624
|
return;
|
|
9376
9625
|
}
|
|
@@ -9380,6 +9629,9 @@ function createReplyToThread(deps) {
|
|
|
9380
9629
|
sessionId: activeTurnId
|
|
9381
9630
|
});
|
|
9382
9631
|
if (resumeRequest) {
|
|
9632
|
+
if (!await appendParkedTurnInput(resumeRequest.sessionId)) {
|
|
9633
|
+
throw new TurnInputDeferredError();
|
|
9634
|
+
}
|
|
9383
9635
|
try {
|
|
9384
9636
|
await deps.services.scheduleAgentContinue(resumeRequest);
|
|
9385
9637
|
} catch (error) {
|
|
@@ -9400,7 +9652,7 @@ function createReplyToThread(deps) {
|
|
|
9400
9652
|
conversation: preparedState.conversation
|
|
9401
9653
|
});
|
|
9402
9654
|
await options.onTurnStatePersisted?.();
|
|
9403
|
-
await options.
|
|
9655
|
+
await options.ack?.();
|
|
9404
9656
|
return;
|
|
9405
9657
|
}
|
|
9406
9658
|
const sessionRecord = await getAgentTurnSessionRecord(
|
|
@@ -9409,27 +9661,34 @@ function createReplyToThread(deps) {
|
|
|
9409
9661
|
);
|
|
9410
9662
|
if (sessionRecord?.state === "awaiting_resume") {
|
|
9411
9663
|
if (sessionRecord.resumeReason === "auth") {
|
|
9412
|
-
await
|
|
9413
|
-
|
|
9664
|
+
await abandonAgentTurnSessionRecord({
|
|
9665
|
+
conversationId,
|
|
9666
|
+
sessionId: activeTurnId,
|
|
9667
|
+
errorMessage: "Auth-parked session superseded by a new user message"
|
|
9414
9668
|
});
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9669
|
+
markTurnClosed({
|
|
9670
|
+
conversation: preparedState.conversation,
|
|
9671
|
+
nowMs: Date.now(),
|
|
9672
|
+
sessionId: activeTurnId,
|
|
9673
|
+
updateConversationStats
|
|
9674
|
+
});
|
|
9675
|
+
activeTurnId = void 0;
|
|
9676
|
+
} else {
|
|
9677
|
+
await failAgentTurnSessionRecord({
|
|
9678
|
+
conversationId,
|
|
9679
|
+
expectedVersion: sessionRecord.version,
|
|
9680
|
+
sessionId: activeTurnId,
|
|
9681
|
+
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
9682
|
+
});
|
|
9683
|
+
markTurnFailed({
|
|
9684
|
+
conversation: preparedState.conversation,
|
|
9685
|
+
nowMs: Date.now(),
|
|
9686
|
+
sessionId: activeTurnId,
|
|
9687
|
+
markConversationMessage,
|
|
9688
|
+
updateConversationStats
|
|
9689
|
+
});
|
|
9690
|
+
activeTurnId = void 0;
|
|
9418
9691
|
}
|
|
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
9692
|
}
|
|
9434
9693
|
}
|
|
9435
9694
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
@@ -9465,7 +9724,7 @@ function createReplyToThread(deps) {
|
|
|
9465
9724
|
conversation: preparedState.conversation
|
|
9466
9725
|
});
|
|
9467
9726
|
await options.onTurnStatePersisted?.();
|
|
9468
|
-
await options.
|
|
9727
|
+
await options.ack?.();
|
|
9469
9728
|
return;
|
|
9470
9729
|
}
|
|
9471
9730
|
startActiveTurn({
|
|
@@ -9485,6 +9744,7 @@ function createReplyToThread(deps) {
|
|
|
9485
9744
|
surface: "slack",
|
|
9486
9745
|
requester,
|
|
9487
9746
|
destination,
|
|
9747
|
+
destinationVisibility,
|
|
9488
9748
|
source,
|
|
9489
9749
|
traceId: getActiveTraceId()
|
|
9490
9750
|
}).catch((error) => {
|
|
@@ -9601,8 +9861,10 @@ function createReplyToThread(deps) {
|
|
|
9601
9861
|
};
|
|
9602
9862
|
let persistedAtLeastOnce = false;
|
|
9603
9863
|
let shouldPersistFailureState = true;
|
|
9864
|
+
let finalReplyDelivered = false;
|
|
9604
9865
|
let latestArtifacts = preparedState.artifacts;
|
|
9605
9866
|
let assistantTitleArtifacts = {};
|
|
9867
|
+
const hasVisibleSlackDelivery = (post) => post.text.trim().length > 0 || Boolean(post.files?.length);
|
|
9606
9868
|
try {
|
|
9607
9869
|
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
9608
9870
|
conversationId,
|
|
@@ -9694,39 +9956,16 @@ function createReplyToThread(deps) {
|
|
|
9694
9956
|
);
|
|
9695
9957
|
});
|
|
9696
9958
|
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;
|
|
9959
|
+
const drainSteeringMessages = options.drainSteeringMessages ? async (accept) => {
|
|
9960
|
+
let acceptedMessages;
|
|
9722
9961
|
const drained = await options.drainSteeringMessages(
|
|
9723
9962
|
async (queuedMessages) => {
|
|
9724
|
-
|
|
9725
|
-
await
|
|
9963
|
+
acceptedMessages = await resolveSteeringMessages(queuedMessages);
|
|
9964
|
+
await accept(acceptedMessages);
|
|
9726
9965
|
},
|
|
9727
9966
|
{ conversationContext: preparedState.conversationContext }
|
|
9728
9967
|
);
|
|
9729
|
-
return
|
|
9968
|
+
return acceptedMessages ?? await resolveSteeringMessages(drained);
|
|
9730
9969
|
} : void 0;
|
|
9731
9970
|
let reply = await deps.services.generateAssistantReply(
|
|
9732
9971
|
effectiveUserText,
|
|
@@ -9792,7 +10031,7 @@ function createReplyToThread(deps) {
|
|
|
9792
10031
|
},
|
|
9793
10032
|
onStatus: (nextStatus) => status.update(nextStatus),
|
|
9794
10033
|
onToolInvocation: options.onToolInvocation,
|
|
9795
|
-
onInputCommitted: options.
|
|
10034
|
+
onInputCommitted: options.ack,
|
|
9796
10035
|
drainSteeringMessages,
|
|
9797
10036
|
shouldYield: options.shouldYield
|
|
9798
10037
|
}
|
|
@@ -9825,6 +10064,14 @@ function createReplyToThread(deps) {
|
|
|
9825
10064
|
const shouldUseSlackFooter = Boolean(replyFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
9826
10065
|
if (plannedPosts.length > 0) {
|
|
9827
10066
|
let sent;
|
|
10067
|
+
const hasVisibleDelivery = plannedPosts.some(
|
|
10068
|
+
hasVisibleSlackDelivery
|
|
10069
|
+
);
|
|
10070
|
+
if (!hasVisibleDelivery) {
|
|
10071
|
+
throw new Error(
|
|
10072
|
+
"Slack final reply plan did not contain visible delivery"
|
|
10073
|
+
);
|
|
10074
|
+
}
|
|
9828
10075
|
if (shouldUseSlackFooter) {
|
|
9829
10076
|
const slackChannelId = channelId;
|
|
9830
10077
|
const slackThreadTs = threadTs;
|
|
@@ -9868,16 +10115,34 @@ function createReplyToThread(deps) {
|
|
|
9868
10115
|
}
|
|
9869
10116
|
} else {
|
|
9870
10117
|
for (const post of plannedPosts) {
|
|
10118
|
+
if (!hasVisibleSlackDelivery(post)) {
|
|
10119
|
+
continue;
|
|
10120
|
+
}
|
|
9871
10121
|
sent = await postThreadReply(
|
|
9872
10122
|
buildSlackOutputMessage(post.text, post.files),
|
|
9873
10123
|
post.stage
|
|
9874
10124
|
);
|
|
9875
10125
|
}
|
|
9876
10126
|
}
|
|
10127
|
+
finalReplyDelivered = true;
|
|
10128
|
+
shouldPersistFailureState = false;
|
|
9877
10129
|
const firstPlannedMessageHasFiles = (plannedPosts[0]?.files?.length ?? 0) > 0;
|
|
9878
10130
|
if (sent && reactionPerformed && plannedPosts.length === 1 && !firstPlannedMessageHasFiles && isRedundantReactionAckText(reply.text)) {
|
|
9879
|
-
|
|
10131
|
+
try {
|
|
10132
|
+
await sent.delete();
|
|
10133
|
+
} catch (error) {
|
|
10134
|
+
logException(
|
|
10135
|
+
error,
|
|
10136
|
+
"slack_redundant_ack_delete_failed",
|
|
10137
|
+
turnTraceContext,
|
|
10138
|
+
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10139
|
+
"Failed to delete redundant reaction-ack reply"
|
|
10140
|
+
);
|
|
10141
|
+
}
|
|
9880
10142
|
}
|
|
10143
|
+
} else {
|
|
10144
|
+
finalReplyDelivered = true;
|
|
10145
|
+
shouldPersistFailureState = false;
|
|
9881
10146
|
}
|
|
9882
10147
|
const completedState = buildDeliveredTurnStatePatch({
|
|
9883
10148
|
artifactStatePatch: {
|
|
@@ -9893,27 +10158,63 @@ function createReplyToThread(deps) {
|
|
|
9893
10158
|
if (completedState.artifacts) {
|
|
9894
10159
|
latestArtifacts = completedState.artifacts;
|
|
9895
10160
|
}
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
10161
|
+
try {
|
|
10162
|
+
if (conversationId && reply.piMessages?.length) {
|
|
10163
|
+
await completeDeliveredTurn({
|
|
10164
|
+
channelName,
|
|
10165
|
+
conversationId,
|
|
10166
|
+
durationMs: reply.diagnostics.durationMs,
|
|
10167
|
+
usage: reply.diagnostics.usage,
|
|
10168
|
+
destination,
|
|
10169
|
+
destinationVisibility,
|
|
10170
|
+
source,
|
|
10171
|
+
sessionId: turnId,
|
|
10172
|
+
sliceId: 1,
|
|
10173
|
+
messages: reply.piMessages,
|
|
10174
|
+
logContext: {
|
|
10175
|
+
threadId,
|
|
10176
|
+
requesterId: slackRequesterId,
|
|
10177
|
+
channelId,
|
|
10178
|
+
runId,
|
|
10179
|
+
assistantUserName: botConfig.userName,
|
|
10180
|
+
modelId: reply.diagnostics.modelId
|
|
10181
|
+
},
|
|
10182
|
+
requester,
|
|
10183
|
+
surface: "slack"
|
|
10184
|
+
});
|
|
10185
|
+
} else if (conversationId) {
|
|
10186
|
+
await recordAgentTurnSessionSummary({
|
|
10187
|
+
channelName,
|
|
10188
|
+
conversationId,
|
|
10189
|
+
cumulativeDurationMs: reply.diagnostics.durationMs,
|
|
10190
|
+
cumulativeUsage: reply.diagnostics.usage,
|
|
10191
|
+
sessionId: turnId,
|
|
10192
|
+
sliceId: 1,
|
|
10193
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
10194
|
+
state: "completed",
|
|
10195
|
+
requester,
|
|
10196
|
+
destination,
|
|
10197
|
+
destinationVisibility,
|
|
10198
|
+
source,
|
|
10199
|
+
traceId: getActiveTraceId()
|
|
10200
|
+
});
|
|
10201
|
+
}
|
|
10202
|
+
await persistThreadStateWithRetry(thread, {
|
|
10203
|
+
...completedState
|
|
9916
10204
|
});
|
|
10205
|
+
if (completedState.artifacts && (assistantTitleArtifacts.assistantTitle !== void 0 || assistantTitleArtifacts.assistantTitleSourceMessageId !== void 0) && (completedState.artifacts.assistantTitle !== assistantTitleArtifacts.assistantTitle || completedState.artifacts.assistantTitleSourceMessageId !== assistantTitleArtifacts.assistantTitleSourceMessageId)) {
|
|
10206
|
+
await persistThreadStateWithRetry(thread, {
|
|
10207
|
+
artifacts: latestArtifacts
|
|
10208
|
+
});
|
|
10209
|
+
}
|
|
10210
|
+
} catch (commitError) {
|
|
10211
|
+
logException(
|
|
10212
|
+
commitError,
|
|
10213
|
+
"slack_reply_post_delivery_commit_failed",
|
|
10214
|
+
turnTraceContext,
|
|
10215
|
+
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10216
|
+
"Post-delivery turn state persistence failed after Slack accepted the reply"
|
|
10217
|
+
);
|
|
9917
10218
|
}
|
|
9918
10219
|
preparedState.conversation = completedState.conversation;
|
|
9919
10220
|
persistedAtLeastOnce = true;
|
|
@@ -9947,6 +10248,17 @@ function createReplyToThread(deps) {
|
|
|
9947
10248
|
}
|
|
9948
10249
|
}
|
|
9949
10250
|
} catch (error) {
|
|
10251
|
+
if (finalReplyDelivered) {
|
|
10252
|
+
shouldPersistFailureState = false;
|
|
10253
|
+
logException(
|
|
10254
|
+
error,
|
|
10255
|
+
"slack_reply_post_delivery_commit_failed",
|
|
10256
|
+
turnTraceContext,
|
|
10257
|
+
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
10258
|
+
"Post-delivery turn work failed after Slack accepted the reply"
|
|
10259
|
+
);
|
|
10260
|
+
return;
|
|
10261
|
+
}
|
|
9950
10262
|
if (isCooperativeTurnYieldError(error)) {
|
|
9951
10263
|
shouldPersistFailureState = false;
|
|
9952
10264
|
throw error;
|
|
@@ -10121,6 +10433,7 @@ function createReplyToThread(deps) {
|
|
|
10121
10433
|
state: "failed",
|
|
10122
10434
|
requester,
|
|
10123
10435
|
destination,
|
|
10436
|
+
destinationVisibility,
|
|
10124
10437
|
source,
|
|
10125
10438
|
traceId: getActiveTraceId()
|
|
10126
10439
|
});
|
|
@@ -10558,7 +10871,7 @@ function createJuniorSlackAdapter(config) {
|
|
|
10558
10871
|
|
|
10559
10872
|
// src/chat/runtime/agent-continue-runner.ts
|
|
10560
10873
|
var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
10561
|
-
function
|
|
10874
|
+
function sleep2(ms) {
|
|
10562
10875
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10563
10876
|
}
|
|
10564
10877
|
async function persistCompletedReplyState2(args) {
|
|
@@ -10645,6 +10958,28 @@ async function failContinuationStartup(args) {
|
|
|
10645
10958
|
);
|
|
10646
10959
|
}
|
|
10647
10960
|
}
|
|
10961
|
+
async function resolveContinuationRequester(args) {
|
|
10962
|
+
const stored = args.sessionRecordRequester;
|
|
10963
|
+
if (stored?.platform === "slack" && stored.teamId === args.teamId && stored.userId === args.userId) {
|
|
10964
|
+
return createSlackResumeRequester({
|
|
10965
|
+
requester: stored,
|
|
10966
|
+
teamId: args.teamId,
|
|
10967
|
+
userId: args.userId
|
|
10968
|
+
});
|
|
10969
|
+
}
|
|
10970
|
+
const work = await getConversationWorkState2({
|
|
10971
|
+
conversationId: args.conversationId
|
|
10972
|
+
});
|
|
10973
|
+
const workRequester = work?.requester;
|
|
10974
|
+
if (workRequester && workRequester.teamId === args.teamId && workRequester.slackUserId === args.userId) {
|
|
10975
|
+
return createSlackRequester(args.teamId, args.userId, {
|
|
10976
|
+
email: workRequester.email,
|
|
10977
|
+
fullName: workRequester.fullName,
|
|
10978
|
+
userName: workRequester.slackUserName
|
|
10979
|
+
});
|
|
10980
|
+
}
|
|
10981
|
+
return void 0;
|
|
10982
|
+
}
|
|
10648
10983
|
function isContinuationResume(summary) {
|
|
10649
10984
|
return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield");
|
|
10650
10985
|
}
|
|
@@ -10708,11 +11043,20 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10708
11043
|
payload.destination,
|
|
10709
11044
|
"Slack continuation"
|
|
10710
11045
|
);
|
|
10711
|
-
const requester =
|
|
10712
|
-
|
|
11046
|
+
const requester = await resolveContinuationRequester({
|
|
11047
|
+
conversationId: payload.conversationId,
|
|
11048
|
+
sessionRecordRequester: activeSessionRecord.requester,
|
|
10713
11049
|
teamId: destination.teamId,
|
|
10714
11050
|
userId: userMessage2.author.userId
|
|
10715
11051
|
});
|
|
11052
|
+
if (!requester) {
|
|
11053
|
+
await failStrandedSessionWithFallback({
|
|
11054
|
+
conversationId: payload.conversationId,
|
|
11055
|
+
errorMessage: "Stored Slack requester missing for continuation",
|
|
11056
|
+
sessionRecord: activeSessionRecord
|
|
11057
|
+
});
|
|
11058
|
+
return false;
|
|
11059
|
+
}
|
|
10716
11060
|
if (!activeSessionRecord.source) {
|
|
10717
11061
|
await failAgentTurnSessionRecord({
|
|
10718
11062
|
conversationId: payload.conversationId,
|
|
@@ -10822,8 +11166,119 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10822
11166
|
}
|
|
10823
11167
|
});
|
|
10824
11168
|
}
|
|
11169
|
+
async function failStrandedSessionWithFallback(args) {
|
|
11170
|
+
await failAgentTurnSessionRecord({
|
|
11171
|
+
conversationId: args.conversationId,
|
|
11172
|
+
expectedVersion: args.sessionRecord.version,
|
|
11173
|
+
sessionId: args.sessionRecord.sessionId,
|
|
11174
|
+
errorMessage: args.errorMessage
|
|
11175
|
+
});
|
|
11176
|
+
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11177
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11178
|
+
markTurnFailed({
|
|
11179
|
+
conversation,
|
|
11180
|
+
nowMs: Date.now(),
|
|
11181
|
+
sessionId: args.sessionRecord.sessionId,
|
|
11182
|
+
userMessageId: getTurnUserMessage(
|
|
11183
|
+
conversation,
|
|
11184
|
+
args.sessionRecord.sessionId
|
|
11185
|
+
)?.id,
|
|
11186
|
+
markConversationMessage,
|
|
11187
|
+
updateConversationStats
|
|
11188
|
+
});
|
|
11189
|
+
await persistThreadStateById(args.conversationId, { conversation });
|
|
11190
|
+
const thread = parseSlackThreadId(args.conversationId);
|
|
11191
|
+
if (!thread) {
|
|
11192
|
+
return;
|
|
11193
|
+
}
|
|
11194
|
+
const eventName = "agent_turn_stranded_session_failed";
|
|
11195
|
+
const eventId = logException(
|
|
11196
|
+
new Error(args.errorMessage),
|
|
11197
|
+
eventName,
|
|
11198
|
+
{ conversationId: args.conversationId },
|
|
11199
|
+
{
|
|
11200
|
+
"app.ai.conversation_id": args.conversationId,
|
|
11201
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
11202
|
+
},
|
|
11203
|
+
"Stranded running agent session terminally failed"
|
|
11204
|
+
);
|
|
11205
|
+
await postSlackMessage({
|
|
11206
|
+
channelId: thread.channelId,
|
|
11207
|
+
threadTs: thread.threadTs,
|
|
11208
|
+
text: buildTurnFailureResponse(
|
|
11209
|
+
requireTurnFailureEventId(eventId, eventName)
|
|
11210
|
+
)
|
|
11211
|
+
});
|
|
11212
|
+
}
|
|
11213
|
+
async function recoverStrandedRunningSession(args) {
|
|
11214
|
+
const stateAdapter = getStateAdapter();
|
|
11215
|
+
await stateAdapter.connect();
|
|
11216
|
+
const probe = await acquireActiveLock(stateAdapter, args.conversationId);
|
|
11217
|
+
if (!probe) {
|
|
11218
|
+
return false;
|
|
11219
|
+
}
|
|
11220
|
+
await stateAdapter.releaseLock(probe);
|
|
11221
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
11222
|
+
args.conversationId,
|
|
11223
|
+
args.summary.sessionId
|
|
11224
|
+
);
|
|
11225
|
+
if (!sessionRecord || sessionRecord.state !== "running") {
|
|
11226
|
+
return false;
|
|
11227
|
+
}
|
|
11228
|
+
const parked = await persistYieldSessionRecord({
|
|
11229
|
+
channelName: sessionRecord.channelName,
|
|
11230
|
+
conversationId: args.conversationId,
|
|
11231
|
+
sessionId: sessionRecord.sessionId,
|
|
11232
|
+
currentSliceId: sessionRecord.sliceId,
|
|
11233
|
+
destination: sessionRecord.destination,
|
|
11234
|
+
source: sessionRecord.source,
|
|
11235
|
+
messages: sessionRecord.piMessages,
|
|
11236
|
+
errorMessage: "Recovered running session after hard worker death",
|
|
11237
|
+
logContext: { modelId: botConfig.modelId },
|
|
11238
|
+
requester: sessionRecord.requester,
|
|
11239
|
+
surface: sessionRecord.surface
|
|
11240
|
+
});
|
|
11241
|
+
if (!parked) {
|
|
11242
|
+
await failStrandedSessionWithFallback({
|
|
11243
|
+
conversationId: args.conversationId,
|
|
11244
|
+
errorMessage: "Stranded running session had no resumable boundary after worker death",
|
|
11245
|
+
sessionRecord
|
|
11246
|
+
});
|
|
11247
|
+
return false;
|
|
11248
|
+
}
|
|
11249
|
+
const request = await getAwaitingAgentContinueRequest({
|
|
11250
|
+
conversationId: args.conversationId,
|
|
11251
|
+
sessionId: sessionRecord.sessionId
|
|
11252
|
+
});
|
|
11253
|
+
if (!request) {
|
|
11254
|
+
await failStrandedSessionWithFallback({
|
|
11255
|
+
conversationId: args.conversationId,
|
|
11256
|
+
errorMessage: "Stranded running session could not materialize continuation metadata",
|
|
11257
|
+
sessionRecord: parked
|
|
11258
|
+
});
|
|
11259
|
+
return false;
|
|
11260
|
+
}
|
|
11261
|
+
if (await continueSlackAgentRunWithLockRetry(request, args.options)) {
|
|
11262
|
+
return true;
|
|
11263
|
+
}
|
|
11264
|
+
await failUnresumableContinuation({
|
|
11265
|
+
conversationId: args.conversationId,
|
|
11266
|
+
expectedVersion: request.expectedVersion,
|
|
11267
|
+
summary: args.summary,
|
|
11268
|
+
errorMessage: "Awaiting agent continuation was stale before it could run"
|
|
11269
|
+
});
|
|
11270
|
+
return false;
|
|
11271
|
+
}
|
|
10825
11272
|
async function resumeAwaitingSlackContinuation(conversationId, options = {}) {
|
|
10826
11273
|
const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
|
|
11274
|
+
const newest = summaries[0];
|
|
11275
|
+
if (newest?.state === "running") {
|
|
11276
|
+
return await recoverStrandedRunningSession({
|
|
11277
|
+
conversationId,
|
|
11278
|
+
options,
|
|
11279
|
+
summary: newest
|
|
11280
|
+
});
|
|
11281
|
+
}
|
|
10827
11282
|
for (const summary of summaries) {
|
|
10828
11283
|
if (!isContinuationResume(summary)) {
|
|
10829
11284
|
continue;
|
|
@@ -10889,7 +11344,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options = {}) {
|
|
|
10889
11344
|
},
|
|
10890
11345
|
"Agent continuation lock was busy; retrying"
|
|
10891
11346
|
);
|
|
10892
|
-
await
|
|
11347
|
+
await sleep2(delayMs);
|
|
10893
11348
|
}
|
|
10894
11349
|
}
|
|
10895
11350
|
return true;
|