@sentry/junior 0.84.1 → 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-TFPY6V3T.js → agent-hooks-NWOUB3UR.js} +7 -7
- package/dist/app.js +916 -363
- 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/current-instruction.d.ts +6 -0
- package/dist/chat/ingress/message-changed.d.ts +2 -0
- package/dist/chat/respond-helpers.d.ts +1 -2
- 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-AL6ZFV7U.js → chunk-2NFV5FMB.js} +4 -4
- package/dist/{chunk-JK7376UT.js → chunk-37B2R2QJ.js} +17 -17
- package/dist/{chunk-QWKB6NJV.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-SBYMRDH7.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-CWMMGUWM.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-FPN7NYTE.js → chunk-ENPSU7L7.js} +165 -27
- package/dist/{chunk-T2YRJZ5A.js → chunk-FPHA6GCQ.js} +905 -711
- package/dist/{chunk-MJ4E2PK7.js → chunk-GB5DFM4D.js} +1 -1
- package/dist/{chunk-BAPRSWNW.js → chunk-GGD6WK6V.js} +124 -25
- package/dist/{chunk-SVFA43LT.js → chunk-JRXCSSSU.js} +19 -4
- package/dist/{chunk-DR75T7J3.js → chunk-L7OHKDOX.js} +9 -5
- package/dist/{chunk-LSG6PQF4.js → chunk-RIB3M6YA.js} +2 -2
- package/dist/{chunk-ALL7GG6U.js → chunk-ZU2ALUVQ.js} +45 -6
- 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-ELRCNB4A.js → db-SZVUU7RB.js} +4 -4
- package/dist/handlers/github-webhook/issue-comment.d.ts +3 -0
- package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +3 -0
- package/dist/instrumentation.js +1 -1
- package/dist/nitro.js +4 -4
- package/dist/reporting/conversations.d.ts +2 -1
- package/dist/reporting.js +110 -34
- package/dist/{runner-2XU6QQND.js → runner-HEBRPNN2.js} +27 -13
- package/package.json +5 -5
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,19 +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
|
-
escapeXml,
|
|
120
133
|
failAgentTurnSessionRecord,
|
|
121
134
|
getAgentTurnSessionRecord,
|
|
122
|
-
getInterruptionMarker,
|
|
123
135
|
listAgentTurnSessionSummariesForConversation,
|
|
124
136
|
loadProjection,
|
|
125
137
|
normalizeSlackStatusText,
|
|
@@ -127,7 +139,7 @@ import {
|
|
|
127
139
|
recordAuthorizationCompleted,
|
|
128
140
|
splitSlackReplyText,
|
|
129
141
|
truncateStatusText
|
|
130
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-37B2R2QJ.js";
|
|
131
143
|
import {
|
|
132
144
|
validatePluginEgressCredentialHooks,
|
|
133
145
|
validatePluginRegistrations
|
|
@@ -147,20 +159,20 @@ import {
|
|
|
147
159
|
setPlugins,
|
|
148
160
|
validatePlugins,
|
|
149
161
|
verifySlackDirectCredentialSubject
|
|
150
|
-
} from "./chunk-
|
|
162
|
+
} from "./chunk-2NFV5FMB.js";
|
|
151
163
|
import {
|
|
152
164
|
createPluginLogger,
|
|
153
165
|
createPluginState
|
|
154
|
-
} from "./chunk-
|
|
166
|
+
} from "./chunk-BRSQQRG6.js";
|
|
155
167
|
import {
|
|
156
168
|
getConversationStore,
|
|
157
169
|
getDb
|
|
158
|
-
} from "./chunk-
|
|
170
|
+
} from "./chunk-6I6HBOQM.js";
|
|
159
171
|
import "./chunk-G3E7SCME.js";
|
|
160
172
|
import {
|
|
161
173
|
acquireActiveLock,
|
|
162
174
|
getStateAdapter
|
|
163
|
-
} from "./chunk-
|
|
175
|
+
} from "./chunk-GB5DFM4D.js";
|
|
164
176
|
import {
|
|
165
177
|
SlackActionError,
|
|
166
178
|
createSlackDestination,
|
|
@@ -168,18 +180,21 @@ import {
|
|
|
168
180
|
downloadPrivateSlackFile,
|
|
169
181
|
getHeaderString,
|
|
170
182
|
getSlackClient,
|
|
183
|
+
getWorkspaceTeamId,
|
|
171
184
|
isDmChannel,
|
|
172
185
|
normalizeSlackConversationId,
|
|
173
186
|
parseDestination,
|
|
174
187
|
requireSlackDestination,
|
|
188
|
+
runWithSlackInstallationToken,
|
|
189
|
+
runWithWorkspaceTeamId,
|
|
175
190
|
sameDestination
|
|
176
|
-
} from "./chunk-
|
|
191
|
+
} from "./chunk-GGD6WK6V.js";
|
|
177
192
|
import {
|
|
178
193
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
179
|
-
GEN_AI_PROVIDER_NAME,
|
|
180
194
|
botConfig,
|
|
181
195
|
completeObject,
|
|
182
196
|
completeText,
|
|
197
|
+
escapeXml,
|
|
183
198
|
getChatConfig,
|
|
184
199
|
getRuntimeMetadata,
|
|
185
200
|
getSlackBotToken,
|
|
@@ -191,13 +206,14 @@ import {
|
|
|
191
206
|
normalizeSlackEmojiName,
|
|
192
207
|
setSlackReactionConfig,
|
|
193
208
|
stripRuntimeTurnContext,
|
|
194
|
-
trimTrailingAssistantMessages
|
|
195
|
-
|
|
209
|
+
trimTrailingAssistantMessages,
|
|
210
|
+
unwrapCurrentInstruction
|
|
211
|
+
} from "./chunk-ZU2ALUVQ.js";
|
|
196
212
|
import {
|
|
197
213
|
parseSlackThreadId,
|
|
198
214
|
resolveSlackChannelIdFromMessage,
|
|
199
215
|
resolveSlackChannelIdFromThreadId
|
|
200
|
-
} from "./chunk-
|
|
216
|
+
} from "./chunk-L7OHKDOX.js";
|
|
201
217
|
import {
|
|
202
218
|
discoverSkills
|
|
203
219
|
} from "./chunk-64ACM6AP.js";
|
|
@@ -494,96 +510,25 @@ async function postSlackApiReplyPosts(args) {
|
|
|
494
510
|
return lastPostedMessageTs;
|
|
495
511
|
}
|
|
496
512
|
|
|
497
|
-
// src/chat/services/
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
return eventId;
|
|
503
|
-
}
|
|
504
|
-
function getExecutionFailureReason(reply) {
|
|
505
|
-
const errorMessage = reply.diagnostics.errorMessage?.trim();
|
|
506
|
-
if (errorMessage) {
|
|
507
|
-
return errorMessage;
|
|
508
|
-
}
|
|
509
|
-
if (reply.diagnostics.toolErrorCount > 0) {
|
|
510
|
-
return `${reply.diagnostics.toolErrorCount} tool result error(s)`;
|
|
511
|
-
}
|
|
512
|
-
if (reply.diagnostics.assistantMessageCount > 0) {
|
|
513
|
-
return "assistant returned no text";
|
|
514
|
-
}
|
|
515
|
-
return "empty assistant turn";
|
|
516
|
-
}
|
|
517
|
-
function getFailureCapture(reply) {
|
|
518
|
-
if (reply.diagnostics.outcome === "provider_error") {
|
|
519
|
-
return {
|
|
520
|
-
eventName: "agent_turn_provider_error",
|
|
521
|
-
error: reply.diagnostics.providerError ?? new Error(
|
|
522
|
-
reply.diagnostics.errorMessage ?? "Provider error without explicit message"
|
|
523
|
-
),
|
|
524
|
-
attributes: {},
|
|
525
|
-
body: "Agent turn failed with provider error"
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
const failureReason = getExecutionFailureReason(reply);
|
|
529
|
-
return {
|
|
530
|
-
eventName: "agent_turn_execution_failure",
|
|
531
|
-
error: new Error(`Agent turn execution failure: ${failureReason}`),
|
|
532
|
-
attributes: {
|
|
533
|
-
"app.ai.execution_failure_reason": failureReason
|
|
534
|
-
},
|
|
535
|
-
body: "Agent turn completed with execution failure"
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
function getAgentTurnDiagnosticsAttributes(reply) {
|
|
539
|
-
return {
|
|
540
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
541
|
-
"gen_ai.operation.name": "invoke_agent",
|
|
542
|
-
"app.ai.outcome": reply.diagnostics.outcome,
|
|
543
|
-
"app.ai.assistant_messages": reply.diagnostics.assistantMessageCount,
|
|
544
|
-
"app.ai.tool_results": reply.diagnostics.toolResultCount,
|
|
545
|
-
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
|
|
546
|
-
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
547
|
-
"app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
|
|
548
|
-
...reply.diagnostics.thinkingLevel ? {
|
|
549
|
-
"app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
|
|
550
|
-
} : {},
|
|
551
|
-
...reply.diagnostics.stopReason ? {
|
|
552
|
-
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
553
|
-
} : {},
|
|
554
|
-
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
555
|
-
};
|
|
513
|
+
// src/chat/services/persist-retry.ts
|
|
514
|
+
var PERSIST_ATTEMPTS = 3;
|
|
515
|
+
function sleep(ms) {
|
|
516
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
556
517
|
}
|
|
557
|
-
function
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
...getAgentTurnDiagnosticsAttributes(args.reply),
|
|
569
|
-
...args.attributes,
|
|
570
|
-
...capture.attributes
|
|
571
|
-
},
|
|
572
|
-
capture.body
|
|
573
|
-
),
|
|
574
|
-
capture.eventName
|
|
575
|
-
);
|
|
576
|
-
const providerPartialText = args.reply.diagnostics.outcome === "provider_error" ? args.reply.text.trim() : "";
|
|
577
|
-
return {
|
|
578
|
-
...args.reply,
|
|
579
|
-
text: providerPartialText ? `${providerPartialText}${getInterruptionMarker()}` : buildTurnFailureResponse(eventId),
|
|
580
|
-
deliveryMode: "thread",
|
|
581
|
-
deliveryPlan: {
|
|
582
|
-
mode: "thread",
|
|
583
|
-
postThreadText: true,
|
|
584
|
-
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
|
+
}
|
|
585
529
|
}
|
|
586
|
-
}
|
|
530
|
+
}
|
|
531
|
+
throw lastError;
|
|
587
532
|
}
|
|
588
533
|
|
|
589
534
|
// src/chat/agent-dispatch/signing.ts
|
|
@@ -1034,6 +979,16 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1034
979
|
}
|
|
1035
980
|
let dispatch = claimedDispatch;
|
|
1036
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
|
+
};
|
|
1037
992
|
const destinationLockId = getDispatchDestinationLockId(dispatch.destination);
|
|
1038
993
|
const stateAdapter = getStateAdapter();
|
|
1039
994
|
await stateAdapter.connect();
|
|
@@ -1118,7 +1073,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1118
1073
|
correlation: {
|
|
1119
1074
|
conversationId,
|
|
1120
1075
|
threadId: conversationId,
|
|
1121
|
-
turnId
|
|
1076
|
+
turnId,
|
|
1122
1077
|
runId: dispatch.id,
|
|
1123
1078
|
channelId: dispatch.destination.channelId,
|
|
1124
1079
|
teamId: dispatch.destination.teamId
|
|
@@ -1158,13 +1113,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1158
1113
|
reply,
|
|
1159
1114
|
logException,
|
|
1160
1115
|
context: {
|
|
1161
|
-
|
|
1162
|
-
slackThreadId: conversationId,
|
|
1163
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1164
|
-
runId: dispatch.id,
|
|
1165
|
-
actorType: dispatch.actor.type,
|
|
1166
|
-
actorId: dispatch.actor.id,
|
|
1167
|
-
assistantUserName: botConfig.userName,
|
|
1116
|
+
...logContext,
|
|
1168
1117
|
modelId: reply.diagnostics.modelId
|
|
1169
1118
|
}
|
|
1170
1119
|
});
|
|
@@ -1198,13 +1147,45 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1198
1147
|
});
|
|
1199
1148
|
updateConversationStats(conversation);
|
|
1200
1149
|
const nextArtifacts = reply.artifactStatePatch ? mergeArtifactsState(artifacts, reply.artifactStatePatch) : artifacts;
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
+
}
|
|
1208
1189
|
dispatch = await markDispatch({
|
|
1209
1190
|
dispatch,
|
|
1210
1191
|
status: failure ? "failed" : "completed",
|
|
@@ -1215,21 +1196,13 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1215
1196
|
try {
|
|
1216
1197
|
await scheduleCompletedTasks({
|
|
1217
1198
|
conversationId,
|
|
1218
|
-
sessionId:
|
|
1199
|
+
sessionId: turnId
|
|
1219
1200
|
});
|
|
1220
1201
|
} catch (error) {
|
|
1221
1202
|
logException(
|
|
1222
1203
|
error,
|
|
1223
1204
|
"plugin_session_completed_task_schedule_failed",
|
|
1224
|
-
|
|
1225
|
-
conversationId,
|
|
1226
|
-
slackThreadId: conversationId,
|
|
1227
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1228
|
-
runId: dispatch.id,
|
|
1229
|
-
actorType: dispatch.actor.type,
|
|
1230
|
-
actorId: dispatch.actor.id,
|
|
1231
|
-
assistantUserName: botConfig.userName
|
|
1232
|
-
},
|
|
1205
|
+
logContext,
|
|
1233
1206
|
{},
|
|
1234
1207
|
"Plugin session.completed task scheduling failed"
|
|
1235
1208
|
);
|
|
@@ -1278,13 +1251,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
1278
1251
|
error,
|
|
1279
1252
|
"agent_dispatch_run_failed",
|
|
1280
1253
|
{
|
|
1281
|
-
|
|
1282
|
-
slackThreadId: conversationId,
|
|
1283
|
-
slackChannelId: dispatch.destination.channelId,
|
|
1284
|
-
runId: dispatch.id,
|
|
1285
|
-
actorType: dispatch.actor.type,
|
|
1286
|
-
actorId: dispatch.actor.id,
|
|
1287
|
-
assistantUserName: botConfig.userName,
|
|
1254
|
+
...logContext,
|
|
1288
1255
|
modelId: botConfig.modelId
|
|
1289
1256
|
},
|
|
1290
1257
|
{},
|
|
@@ -1505,8 +1472,8 @@ async function drainConversationMailbox2(args) {
|
|
|
1505
1472
|
}
|
|
1506
1473
|
return result;
|
|
1507
1474
|
}
|
|
1508
|
-
async function
|
|
1509
|
-
const result = await
|
|
1475
|
+
async function ackMessages2(args) {
|
|
1476
|
+
const result = await ackMessages(args);
|
|
1510
1477
|
await recordExecutionMetadata(args);
|
|
1511
1478
|
return result;
|
|
1512
1479
|
}
|
|
@@ -1525,6 +1492,18 @@ async function completeConversationWork2(args) {
|
|
|
1525
1492
|
await recordExecutionMetadata(args);
|
|
1526
1493
|
return result;
|
|
1527
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
|
+
}
|
|
1528
1507
|
async function clearExpiredConversationLease2(args) {
|
|
1529
1508
|
const result = await clearExpiredConversationLease(args);
|
|
1530
1509
|
await recordExecutionMetadata(args);
|
|
@@ -1592,6 +1571,10 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
1592
1571
|
await args.adapter.initialize({
|
|
1593
1572
|
getState: () => state
|
|
1594
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
|
+
}
|
|
1595
1578
|
initializedAdapters.add(args.adapter);
|
|
1596
1579
|
}
|
|
1597
1580
|
function verifySlackSignature(args) {
|
|
@@ -1639,7 +1622,7 @@ async function runWithSlackInstallation(args) {
|
|
|
1639
1622
|
enterpriseId: args.installation.enterpriseId,
|
|
1640
1623
|
isEnterpriseInstall: args.installation.isEnterpriseInstall
|
|
1641
1624
|
},
|
|
1642
|
-
args.task
|
|
1625
|
+
() => runWithSlackInstallationToken(tokenContext.token, args.task)
|
|
1643
1626
|
);
|
|
1644
1627
|
}
|
|
1645
1628
|
|
|
@@ -2013,14 +1996,22 @@ function createSlackConversationWorker(options) {
|
|
|
2013
1996
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
2014
1997
|
const state = getConnectedState2(options.state);
|
|
2015
1998
|
await state.connect();
|
|
2016
|
-
const records = getPendingRecords(
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
state
|
|
2020
|
-
})
|
|
2021
|
-
);
|
|
1999
|
+
const records = getPendingRecords({
|
|
2000
|
+
execution: { pendingMessages: [...context.attempt.messages] }
|
|
2001
|
+
});
|
|
2022
2002
|
if (records.length === 0) {
|
|
2023
|
-
|
|
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
|
+
});
|
|
2024
2015
|
return { status: "completed" };
|
|
2025
2016
|
}
|
|
2026
2017
|
const latestRecord = records[records.length - 1];
|
|
@@ -2070,33 +2061,22 @@ function createSlackConversationWorker(options) {
|
|
|
2070
2061
|
skipped,
|
|
2071
2062
|
totalSinceLastHandler: messages.length
|
|
2072
2063
|
};
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
const markInitialMessagesInjected = async () => {
|
|
2078
|
-
if (initialMessagesPersisted) {
|
|
2079
|
-
return true;
|
|
2064
|
+
let initialMessagesAcked = false;
|
|
2065
|
+
const ack = async () => {
|
|
2066
|
+
if (initialMessagesAcked) {
|
|
2067
|
+
return;
|
|
2080
2068
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
conversationStore: options.conversationStore,
|
|
2086
|
-
state
|
|
2087
|
-
});
|
|
2088
|
-
initialMessagesPersisted = marked;
|
|
2089
|
-
return marked;
|
|
2090
|
-
};
|
|
2091
|
-
const onInputCommitted = async () => {
|
|
2092
|
-
if (!await markInitialMessagesInjected()) {
|
|
2069
|
+
try {
|
|
2070
|
+
await context.attempt.ack();
|
|
2071
|
+
initialMessagesAcked = true;
|
|
2072
|
+
} catch {
|
|
2093
2073
|
throw new TurnInputCommitLostError(
|
|
2094
|
-
`Conversation work lease lost before Slack
|
|
2074
|
+
`Conversation work lease lost before Slack inbox ack for ${context.conversationId}`
|
|
2095
2075
|
);
|
|
2096
2076
|
}
|
|
2097
2077
|
};
|
|
2098
|
-
const drainSteeringMessages = async (
|
|
2099
|
-
await context.
|
|
2078
|
+
const drainSteeringMessages = async (accept) => {
|
|
2079
|
+
await context.attempt.drain(async (pendingRecords) => {
|
|
2100
2080
|
const messages2 = pendingRecords.map((record) => {
|
|
2101
2081
|
const metadata = record.input.metadata;
|
|
2102
2082
|
if (!isSlackMetadata(metadata)) {
|
|
@@ -2111,7 +2091,7 @@ function createSlackConversationWorker(options) {
|
|
|
2111
2091
|
message
|
|
2112
2092
|
};
|
|
2113
2093
|
});
|
|
2114
|
-
return await
|
|
2094
|
+
return await accept(messages2);
|
|
2115
2095
|
});
|
|
2116
2096
|
};
|
|
2117
2097
|
try {
|
|
@@ -2120,7 +2100,8 @@ function createSlackConversationWorker(options) {
|
|
|
2120
2100
|
destination: context.destination,
|
|
2121
2101
|
messageContext,
|
|
2122
2102
|
drainSteeringMessages,
|
|
2123
|
-
|
|
2103
|
+
ack,
|
|
2104
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2124
2105
|
shouldYield: context.shouldYield
|
|
2125
2106
|
});
|
|
2126
2107
|
return;
|
|
@@ -2129,10 +2110,14 @@ function createSlackConversationWorker(options) {
|
|
|
2129
2110
|
destination: context.destination,
|
|
2130
2111
|
messageContext,
|
|
2131
2112
|
drainSteeringMessages,
|
|
2132
|
-
|
|
2113
|
+
ack,
|
|
2114
|
+
isFinalAttempt: context.attempt.isFinalAttempt,
|
|
2133
2115
|
shouldYield: context.shouldYield
|
|
2134
2116
|
});
|
|
2135
2117
|
} catch (error) {
|
|
2118
|
+
if (isTurnInputDeferredError(error)) {
|
|
2119
|
+
return { status: "deferred" };
|
|
2120
|
+
}
|
|
2136
2121
|
if (isCooperativeTurnYieldError(error)) {
|
|
2137
2122
|
return { status: "yielded" };
|
|
2138
2123
|
}
|
|
@@ -2143,7 +2128,7 @@ function createSlackConversationWorker(options) {
|
|
|
2143
2128
|
}
|
|
2144
2129
|
}
|
|
2145
2130
|
});
|
|
2146
|
-
if (turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2131
|
+
if (turnResult?.status === "deferred" || turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
2147
2132
|
return turnResult;
|
|
2148
2133
|
}
|
|
2149
2134
|
return { status: "completed" };
|
|
@@ -2349,16 +2334,58 @@ function normalizeGitHubCheckSuiteEvents(deliveryId, body) {
|
|
|
2349
2334
|
});
|
|
2350
2335
|
}
|
|
2351
2336
|
|
|
2352
|
-
// src/handlers/github-webhook/
|
|
2337
|
+
// src/handlers/github-webhook/issue-comment.ts
|
|
2353
2338
|
import { z as z4 } from "zod";
|
|
2354
|
-
var
|
|
2339
|
+
var issueCommentWebhookSchema = z4.object({
|
|
2355
2340
|
action: z4.string(),
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2341
|
+
comment: z4.object({
|
|
2342
|
+
body: z4.string(),
|
|
2343
|
+
user: z4.object({
|
|
2344
|
+
login: z4.string().optional()
|
|
2345
|
+
}).strip().optional()
|
|
2346
|
+
}).strip(),
|
|
2347
|
+
issue: z4.object({
|
|
2348
|
+
number: z4.number(),
|
|
2349
|
+
pull_request: z4.object({
|
|
2350
|
+
url: z4.string().min(1)
|
|
2351
|
+
}).strip().optional()
|
|
2352
|
+
}).strip(),
|
|
2360
2353
|
repository: z4.object({
|
|
2361
2354
|
full_name: z4.string().min(1)
|
|
2355
|
+
}).strip()
|
|
2356
|
+
}).strip();
|
|
2357
|
+
function normalizeGitHubIssueCommentEvent(deliveryId, body) {
|
|
2358
|
+
const parsed = issueCommentWebhookSchema.safeParse(body);
|
|
2359
|
+
if (!parsed.success || parsed.data.action !== "created" || !parsed.data.issue.pull_request) {
|
|
2360
|
+
return void 0;
|
|
2361
|
+
}
|
|
2362
|
+
const eventType = "comment.created";
|
|
2363
|
+
const resource = gitHubPullRequestResource({
|
|
2364
|
+
pullRequestNumber: parsed.data.issue.number,
|
|
2365
|
+
repositoryFullName: parsed.data.repository.full_name
|
|
2366
|
+
});
|
|
2367
|
+
const author = parsed.data.comment.user?.login;
|
|
2368
|
+
return {
|
|
2369
|
+
eventKey: gitHubEventKey(deliveryId, eventType),
|
|
2370
|
+
eventType,
|
|
2371
|
+
occurredAtMs: Date.now(),
|
|
2372
|
+
provider: "github",
|
|
2373
|
+
resourceRef: resource.resourceRef,
|
|
2374
|
+
trustedSummary: `${resource.label} received a comment${author ? ` from ${author}` : ""}.`,
|
|
2375
|
+
untrustedText: parsed.data.comment.body
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/handlers/github-webhook/pull-request.ts
|
|
2380
|
+
import { z as z5 } from "zod";
|
|
2381
|
+
var pullRequestWebhookSchema = z5.object({
|
|
2382
|
+
action: z5.string(),
|
|
2383
|
+
pull_request: z5.object({
|
|
2384
|
+
merged: z5.boolean().optional(),
|
|
2385
|
+
number: z5.number()
|
|
2386
|
+
}),
|
|
2387
|
+
repository: z5.object({
|
|
2388
|
+
full_name: z5.string().min(1)
|
|
2362
2389
|
})
|
|
2363
2390
|
});
|
|
2364
2391
|
function normalizeGitHubPullRequestEvent(deliveryId, body) {
|
|
@@ -2382,21 +2409,60 @@ function normalizeGitHubPullRequestEvent(deliveryId, body) {
|
|
|
2382
2409
|
};
|
|
2383
2410
|
}
|
|
2384
2411
|
|
|
2412
|
+
// src/handlers/github-webhook/pull-request-review-comment.ts
|
|
2413
|
+
import { z as z6 } from "zod";
|
|
2414
|
+
var pullRequestReviewCommentWebhookSchema = z6.object({
|
|
2415
|
+
action: z6.string(),
|
|
2416
|
+
comment: z6.object({
|
|
2417
|
+
body: z6.string(),
|
|
2418
|
+
user: z6.object({
|
|
2419
|
+
login: z6.string().optional()
|
|
2420
|
+
}).strip().optional()
|
|
2421
|
+
}).strip(),
|
|
2422
|
+
pull_request: z6.object({
|
|
2423
|
+
number: z6.number()
|
|
2424
|
+
}).strip(),
|
|
2425
|
+
repository: z6.object({
|
|
2426
|
+
full_name: z6.string().min(1)
|
|
2427
|
+
}).strip()
|
|
2428
|
+
}).strip();
|
|
2429
|
+
function normalizeGitHubPullRequestReviewCommentEvent(deliveryId, body) {
|
|
2430
|
+
const parsed = pullRequestReviewCommentWebhookSchema.safeParse(body);
|
|
2431
|
+
if (!parsed.success || parsed.data.action !== "created") {
|
|
2432
|
+
return void 0;
|
|
2433
|
+
}
|
|
2434
|
+
const eventType = "review_comment.created";
|
|
2435
|
+
const resource = gitHubPullRequestResource({
|
|
2436
|
+
pullRequestNumber: parsed.data.pull_request.number,
|
|
2437
|
+
repositoryFullName: parsed.data.repository.full_name
|
|
2438
|
+
});
|
|
2439
|
+
const author = parsed.data.comment.user?.login;
|
|
2440
|
+
return {
|
|
2441
|
+
eventKey: gitHubEventKey(deliveryId, eventType),
|
|
2442
|
+
eventType,
|
|
2443
|
+
occurredAtMs: Date.now(),
|
|
2444
|
+
provider: "github",
|
|
2445
|
+
resourceRef: resource.resourceRef,
|
|
2446
|
+
trustedSummary: `${resource.label} received an inline review comment${author ? ` from ${author}` : ""}.`,
|
|
2447
|
+
untrustedText: parsed.data.comment.body
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2385
2451
|
// src/handlers/github-webhook/pull-request-review.ts
|
|
2386
|
-
import { z as
|
|
2387
|
-
var pullRequestReviewWebhookSchema =
|
|
2388
|
-
action:
|
|
2389
|
-
pull_request:
|
|
2390
|
-
number:
|
|
2452
|
+
import { z as z7 } from "zod";
|
|
2453
|
+
var pullRequestReviewWebhookSchema = z7.object({
|
|
2454
|
+
action: z7.string(),
|
|
2455
|
+
pull_request: z7.object({
|
|
2456
|
+
number: z7.number()
|
|
2391
2457
|
}),
|
|
2392
|
-
repository:
|
|
2393
|
-
full_name:
|
|
2458
|
+
repository: z7.object({
|
|
2459
|
+
full_name: z7.string().min(1)
|
|
2394
2460
|
}),
|
|
2395
|
-
review:
|
|
2396
|
-
body:
|
|
2397
|
-
state:
|
|
2398
|
-
user:
|
|
2399
|
-
login:
|
|
2461
|
+
review: z7.object({
|
|
2462
|
+
body: z7.string().optional().nullable(),
|
|
2463
|
+
state: z7.string(),
|
|
2464
|
+
user: z7.object({
|
|
2465
|
+
login: z7.string().optional()
|
|
2400
2466
|
}).optional()
|
|
2401
2467
|
})
|
|
2402
2468
|
});
|
|
@@ -2406,7 +2472,7 @@ function normalizeGitHubPullRequestReviewEvent(deliveryId, body) {
|
|
|
2406
2472
|
return void 0;
|
|
2407
2473
|
}
|
|
2408
2474
|
const reviewState = parsed.data.review.state.toUpperCase();
|
|
2409
|
-
const eventType = reviewState === "APPROVED" ? "review.approved" : reviewState === "CHANGES_REQUESTED" ? "review.changes_requested" : void 0;
|
|
2475
|
+
const eventType = reviewState === "APPROVED" ? "review.approved" : reviewState === "CHANGES_REQUESTED" ? "review.changes_requested" : reviewState === "COMMENTED" ? "review.commented" : void 0;
|
|
2410
2476
|
if (!eventType) {
|
|
2411
2477
|
return void 0;
|
|
2412
2478
|
}
|
|
@@ -2421,7 +2487,7 @@ function normalizeGitHubPullRequestReviewEvent(deliveryId, body) {
|
|
|
2421
2487
|
occurredAtMs: Date.now(),
|
|
2422
2488
|
provider: "github",
|
|
2423
2489
|
resourceRef: resource.resourceRef,
|
|
2424
|
-
trustedSummary: eventType === "review.approved" ? `${resource.label} was approved${reviewer ? ` by ${reviewer}` : ""}.` : `${resource.label} received requested changes${reviewer ? ` from ${reviewer}` : ""}.`,
|
|
2490
|
+
trustedSummary: eventType === "review.approved" ? `${resource.label} was approved${reviewer ? ` by ${reviewer}` : ""}.` : eventType === "review.changes_requested" ? `${resource.label} received requested changes${reviewer ? ` from ${reviewer}` : ""}.` : `${resource.label} received a review comment${reviewer ? ` from ${reviewer}` : ""}.`,
|
|
2425
2491
|
untrustedText: parsed.data.review.body ?? void 0
|
|
2426
2492
|
};
|
|
2427
2493
|
}
|
|
@@ -2457,6 +2523,20 @@ function normalizeGitHubResourceEvents(args) {
|
|
|
2457
2523
|
);
|
|
2458
2524
|
return event ? [event] : [];
|
|
2459
2525
|
}
|
|
2526
|
+
case "issue_comment": {
|
|
2527
|
+
const event = normalizeGitHubIssueCommentEvent(
|
|
2528
|
+
args.deliveryId,
|
|
2529
|
+
args.body
|
|
2530
|
+
);
|
|
2531
|
+
return event ? [event] : [];
|
|
2532
|
+
}
|
|
2533
|
+
case "pull_request_review_comment": {
|
|
2534
|
+
const event = normalizeGitHubPullRequestReviewCommentEvent(
|
|
2535
|
+
args.deliveryId,
|
|
2536
|
+
args.body
|
|
2537
|
+
);
|
|
2538
|
+
return event ? [event] : [];
|
|
2539
|
+
}
|
|
2460
2540
|
case "check_suite":
|
|
2461
2541
|
return normalizeGitHubCheckSuiteEvents(args.deliveryId, args.body);
|
|
2462
2542
|
default:
|
|
@@ -2520,11 +2600,29 @@ async function recoverConversationWork(args) {
|
|
|
2520
2600
|
state: args.state
|
|
2521
2601
|
});
|
|
2522
2602
|
for (const conversationId of ids) {
|
|
2603
|
+
let work;
|
|
2523
2604
|
try {
|
|
2524
|
-
|
|
2605
|
+
work = await getConversationWorkState2({
|
|
2525
2606
|
conversationId,
|
|
2526
2607
|
state: args.state
|
|
2527
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 {
|
|
2528
2626
|
if (!work) {
|
|
2529
2627
|
await removeActiveConversation2({
|
|
2530
2628
|
conversationId,
|
|
@@ -3433,17 +3531,6 @@ function isSlackTitlePermissionError(error) {
|
|
|
3433
3531
|
return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
|
|
3434
3532
|
}
|
|
3435
3533
|
|
|
3436
|
-
// src/chat/slack/workspace-context.ts
|
|
3437
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3438
|
-
var workspaceTeamIdStorage = new AsyncLocalStorage();
|
|
3439
|
-
function runWithWorkspaceTeamId(teamId, fn) {
|
|
3440
|
-
if (!teamId) return fn();
|
|
3441
|
-
return workspaceTeamIdStorage.run(teamId, fn);
|
|
3442
|
-
}
|
|
3443
|
-
function getWorkspaceTeamId() {
|
|
3444
|
-
return workspaceTeamIdStorage.getStore();
|
|
3445
|
-
}
|
|
3446
|
-
|
|
3447
3534
|
// src/chat/runtime/thread-context.ts
|
|
3448
3535
|
function escapeRegExp(value) {
|
|
3449
3536
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3638,8 +3725,8 @@ function buildAuthPauseResponse(slackUserId, providerDisplayName) {
|
|
|
3638
3725
|
}
|
|
3639
3726
|
|
|
3640
3727
|
// src/chat/runtime/request-deadline.ts
|
|
3641
|
-
import { AsyncLocalStorage
|
|
3642
|
-
var requestDeadlineStorage = new
|
|
3728
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3729
|
+
var requestDeadlineStorage = new AsyncLocalStorage();
|
|
3643
3730
|
function createTurnRequestDeadline(startedAtMs) {
|
|
3644
3731
|
const requestBudgetMs = Math.max(
|
|
3645
3732
|
1,
|
|
@@ -3918,6 +4005,27 @@ async function resumeSlackTurn(args) {
|
|
|
3918
4005
|
footer
|
|
3919
4006
|
});
|
|
3920
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
|
+
}
|
|
3921
4029
|
await runArgs.onSuccess?.(reply);
|
|
3922
4030
|
if (reply.diagnostics.outcome === "success" && replyContext.correlation?.conversationId && replyContext.correlation.turnId) {
|
|
3923
4031
|
try {
|
|
@@ -5679,14 +5787,18 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5679
5787
|
const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
|
|
5680
5788
|
const userTeam = typeof event.message.user_team === "string" ? event.message.user_team : void 0;
|
|
5681
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;
|
|
5682
5792
|
const raw = {
|
|
5683
5793
|
channel: channelId,
|
|
5794
|
+
...channelType ? { channel_type: channelType } : {},
|
|
5684
5795
|
ts: messageTs,
|
|
5685
5796
|
thread_ts: threadTs,
|
|
5686
5797
|
user: userId,
|
|
5687
5798
|
...teamId ? { team_id: teamId } : {},
|
|
5688
5799
|
...userTeam ? { user_team: userTeam } : {},
|
|
5689
|
-
...sourceTeam ? { source_team: sourceTeam } : {}
|
|
5800
|
+
...sourceTeam ? { source_team: sourceTeam } : {},
|
|
5801
|
+
...botId ? { bot_id: botId } : {}
|
|
5690
5802
|
};
|
|
5691
5803
|
const message = new Message2({
|
|
5692
5804
|
id: getEditedMentionMessageId(messageTs),
|
|
@@ -5702,8 +5814,11 @@ function extractMessageChangedMention(body, botUserId, adapter) {
|
|
|
5702
5814
|
// Raw message_changed payloads do not include profile fields.
|
|
5703
5815
|
userName: "",
|
|
5704
5816
|
fullName: "",
|
|
5705
|
-
|
|
5706
|
-
|
|
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
|
|
5707
5822
|
}
|
|
5708
5823
|
});
|
|
5709
5824
|
Object.defineProperty(message, "adapter", {
|
|
@@ -5959,14 +6074,16 @@ async function handleMessageChanged(args) {
|
|
|
5959
6074
|
}
|
|
5960
6075
|
const botUserId = args.adapter.botUserId;
|
|
5961
6076
|
if (!botUserId) {
|
|
5962
|
-
|
|
6077
|
+
throw new Error(
|
|
6078
|
+
"Slack bot identity is unresolved; cannot classify message_changed event"
|
|
6079
|
+
);
|
|
5963
6080
|
}
|
|
5964
6081
|
const result = extractMessageChangedMention(
|
|
5965
6082
|
args.body,
|
|
5966
6083
|
botUserId,
|
|
5967
6084
|
args.adapter
|
|
5968
6085
|
);
|
|
5969
|
-
if (!result) {
|
|
6086
|
+
if (!result || shouldIgnoreMessage(result.message)) {
|
|
5970
6087
|
return true;
|
|
5971
6088
|
}
|
|
5972
6089
|
await persistSlackMessage({
|
|
@@ -6278,12 +6395,12 @@ async function handleSlackWebhook(args) {
|
|
|
6278
6395
|
try {
|
|
6279
6396
|
await eventTask;
|
|
6280
6397
|
} catch (error) {
|
|
6281
|
-
if (
|
|
6282
|
-
logException(error, "
|
|
6283
|
-
|
|
6398
|
+
if (error instanceof SlackEventPersistenceError) {
|
|
6399
|
+
logException(error.cause, "slack_event_persist_failed");
|
|
6400
|
+
} else {
|
|
6401
|
+
logException(error, "slack_event_routing_failed");
|
|
6284
6402
|
}
|
|
6285
|
-
|
|
6286
|
-
return new Response("Slack event persistence failed", { status: 503 });
|
|
6403
|
+
return new Response("Slack event handling failed", { status: 503 });
|
|
6287
6404
|
}
|
|
6288
6405
|
} else {
|
|
6289
6406
|
enqueue(
|
|
@@ -6433,6 +6550,33 @@ async function requestLostLeaseRecovery(args) {
|
|
|
6433
6550
|
state: args.options.state
|
|
6434
6551
|
});
|
|
6435
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
|
+
}
|
|
6436
6580
|
function startLeaseCheckIn(args) {
|
|
6437
6581
|
const timer = setInterval(() => {
|
|
6438
6582
|
const nowMs = now2(args.options);
|
|
@@ -6470,10 +6614,22 @@ function startLeaseCheckIn(args) {
|
|
|
6470
6614
|
}
|
|
6471
6615
|
async function processConversationWork(message, options) {
|
|
6472
6616
|
const conversationId = message.conversationId;
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
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
|
+
}
|
|
6477
6633
|
if (!initial || countPendingConversationMessages(initial) === 0 && initial.execution.status === "idle" && !initial.execution.lease) {
|
|
6478
6634
|
if (initial) {
|
|
6479
6635
|
await clearConsumedConversationWake2({
|
|
@@ -6532,6 +6688,14 @@ async function processConversationWork(message, options) {
|
|
|
6532
6688
|
}
|
|
6533
6689
|
const startedAtMs = now2(options);
|
|
6534
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
|
+
);
|
|
6535
6699
|
let leaseLost = false;
|
|
6536
6700
|
const markLeaseLost = () => {
|
|
6537
6701
|
leaseLost = true;
|
|
@@ -6551,10 +6715,43 @@ async function processConversationWork(message, options) {
|
|
|
6551
6715
|
},
|
|
6552
6716
|
"Conversation work lease acquired"
|
|
6553
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
|
+
};
|
|
6554
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
|
+
},
|
|
6555
6753
|
conversationId,
|
|
6556
6754
|
destination,
|
|
6557
|
-
leaseToken: lease.leaseToken,
|
|
6558
6755
|
shouldYield: () => leaseLost || now2(options) >= softYieldDeadlineMs,
|
|
6559
6756
|
checkIn: async () => {
|
|
6560
6757
|
const checkedIn = await checkInConversationWork2({
|
|
@@ -6568,15 +6765,7 @@ async function processConversationWork(message, options) {
|
|
|
6568
6765
|
markLeaseLost();
|
|
6569
6766
|
}
|
|
6570
6767
|
return checkedIn;
|
|
6571
|
-
}
|
|
6572
|
-
drainMailbox: (inject) => drainConversationMailbox2({
|
|
6573
|
-
conversationId,
|
|
6574
|
-
leaseToken: lease.leaseToken,
|
|
6575
|
-
conversationStore: options.conversationStore,
|
|
6576
|
-
inject,
|
|
6577
|
-
nowMs: now2(options),
|
|
6578
|
-
state: options.state
|
|
6579
|
-
})
|
|
6768
|
+
}
|
|
6580
6769
|
};
|
|
6581
6770
|
try {
|
|
6582
6771
|
const result = await options.run(workerContext);
|
|
@@ -6643,6 +6832,51 @@ async function processConversationWork(message, options) {
|
|
|
6643
6832
|
);
|
|
6644
6833
|
return { status: "yielded" };
|
|
6645
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
|
+
}
|
|
6646
6880
|
const completion = await completeConversationWork2({
|
|
6647
6881
|
conversationId,
|
|
6648
6882
|
leaseToken: lease.leaseToken,
|
|
@@ -6680,53 +6914,62 @@ async function processConversationWork(message, options) {
|
|
|
6680
6914
|
return { status: "completed" };
|
|
6681
6915
|
} catch (error) {
|
|
6682
6916
|
const errorNowMs = now2(options);
|
|
6917
|
+
let recoveryRecorded = false;
|
|
6683
6918
|
try {
|
|
6684
|
-
const
|
|
6919
|
+
const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
|
|
6685
6920
|
conversationId,
|
|
6686
|
-
destination,
|
|
6687
6921
|
leaseToken: lease.leaseToken,
|
|
6688
|
-
conversationStore: options.conversationStore,
|
|
6689
6922
|
nowMs: errorNowMs,
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6923
|
+
messageIds: attemptMessageIds,
|
|
6924
|
+
options
|
|
6925
|
+
}) : void 0;
|
|
6926
|
+
if (failure && isTerminalFailure(failure)) {
|
|
6927
|
+
await deadLetterAttempt2({
|
|
6694
6928
|
conversationId,
|
|
6929
|
+
leaseToken: lease.leaseToken,
|
|
6695
6930
|
conversationStore: options.conversationStore,
|
|
6696
|
-
|
|
6697
|
-
|
|
6931
|
+
nowMs: errorNowMs,
|
|
6932
|
+
state: options.state
|
|
6933
|
+
});
|
|
6934
|
+
} else {
|
|
6935
|
+
const continuationMarked = await requestConversationContinuation2({
|
|
6936
|
+
conversationId,
|
|
6937
|
+
destination,
|
|
6938
|
+
leaseToken: lease.leaseToken,
|
|
6939
|
+
conversationStore: options.conversationStore,
|
|
6940
|
+
nowMs: errorNowMs,
|
|
6941
|
+
state: options.state
|
|
6942
|
+
});
|
|
6943
|
+
if (continuationMarked) {
|
|
6944
|
+
await ensureConversationWake({
|
|
6698
6945
|
conversationId,
|
|
6699
|
-
|
|
6700
|
-
|
|
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,
|
|
6701
6961
|
nowMs: errorNowMs,
|
|
6702
|
-
queue: options.queue,
|
|
6703
6962
|
state: options.state
|
|
6704
6963
|
});
|
|
6705
6964
|
}
|
|
6706
|
-
|
|
6965
|
+
recoveryRecorded = true;
|
|
6966
|
+
} catch (recoveryError) {
|
|
6707
6967
|
logException(
|
|
6708
|
-
|
|
6968
|
+
recoveryError,
|
|
6709
6969
|
"conversation_work_requeue_failed",
|
|
6710
6970
|
{ conversationId },
|
|
6711
6971
|
{},
|
|
6712
|
-
"Conversation work
|
|
6713
|
-
);
|
|
6714
|
-
}
|
|
6715
|
-
try {
|
|
6716
|
-
await releaseConversationWork2({
|
|
6717
|
-
conversationId,
|
|
6718
|
-
leaseToken: lease.leaseToken,
|
|
6719
|
-
conversationStore: options.conversationStore,
|
|
6720
|
-
nowMs: errorNowMs,
|
|
6721
|
-
state: options.state
|
|
6722
|
-
});
|
|
6723
|
-
} catch (releaseError) {
|
|
6724
|
-
logException(
|
|
6725
|
-
releaseError,
|
|
6726
|
-
"conversation_work_release_failed",
|
|
6727
|
-
{ conversationId },
|
|
6728
|
-
{},
|
|
6729
|
-
"Conversation work release failed after runner error"
|
|
6972
|
+
"Conversation work recovery failed after runner error"
|
|
6730
6973
|
);
|
|
6731
6974
|
}
|
|
6732
6975
|
if (!isProviderRetryError(error)) {
|
|
@@ -6740,7 +6983,10 @@ async function processConversationWork(message, options) {
|
|
|
6740
6983
|
"Conversation work failed"
|
|
6741
6984
|
);
|
|
6742
6985
|
}
|
|
6743
|
-
|
|
6986
|
+
if (!recoveryRecorded) {
|
|
6987
|
+
throw error;
|
|
6988
|
+
}
|
|
6989
|
+
return { status: "failed" };
|
|
6744
6990
|
} finally {
|
|
6745
6991
|
clearInterval(timer);
|
|
6746
6992
|
}
|
|
@@ -6913,14 +7159,14 @@ function registerVercelPluginTaskDevConsumer() {
|
|
|
6913
7159
|
}
|
|
6914
7160
|
|
|
6915
7161
|
// src/chat/services/subscribed-decision.ts
|
|
6916
|
-
import { z as
|
|
6917
|
-
var replyDecisionSchema =
|
|
6918
|
-
should_reply:
|
|
6919
|
-
should_unsubscribe:
|
|
7162
|
+
import { z as z8 } from "zod";
|
|
7163
|
+
var replyDecisionSchema = z8.object({
|
|
7164
|
+
should_reply: z8.boolean().describe("Whether Junior should respond to this thread message."),
|
|
7165
|
+
should_unsubscribe: z8.boolean().describe(
|
|
6920
7166
|
"Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
|
|
6921
7167
|
),
|
|
6922
|
-
confidence:
|
|
6923
|
-
reason:
|
|
7168
|
+
confidence: z8.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
|
|
7169
|
+
reason: z8.string().optional().describe("Short reason for the decision.")
|
|
6924
7170
|
}).strict();
|
|
6925
7171
|
var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
|
|
6926
7172
|
var ROUTER_CLASSIFIER_MAX_TOKENS = 240;
|
|
@@ -7264,7 +7510,7 @@ function combineTurnText(queuedMessages, latestText) {
|
|
|
7264
7510
|
// src/chat/runtime/slack-runtime.ts
|
|
7265
7511
|
var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
|
|
7266
7512
|
function shouldRethrowTurnControlError(error) {
|
|
7267
|
-
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isProviderRetryError(error);
|
|
7513
|
+
return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isTurnInputDeferredError(error) || isProviderRetryError(error);
|
|
7268
7514
|
}
|
|
7269
7515
|
async function maybeHandleThreadOptOutDecision(args) {
|
|
7270
7516
|
if (!args.decision?.shouldUnsubscribe) {
|
|
@@ -7298,13 +7544,13 @@ function createAcceptedSteeringDrain(hooks, options) {
|
|
|
7298
7544
|
if (!hooks.drainSteeringMessages) {
|
|
7299
7545
|
return void 0;
|
|
7300
7546
|
}
|
|
7301
|
-
return async (
|
|
7547
|
+
return async (accept, context) => {
|
|
7302
7548
|
let interruptedMessages;
|
|
7303
7549
|
await hooks.drainSteeringMessages(async (messages) => {
|
|
7304
7550
|
const selection = await options.selectMessages(messages, context);
|
|
7305
7551
|
await options.onSkipped?.(selection.skipped);
|
|
7306
7552
|
const interrupted = selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.message);
|
|
7307
|
-
await
|
|
7553
|
+
await accept(getQueuedMessagesFromSlackMessages(interrupted, options));
|
|
7308
7554
|
interruptedMessages = interrupted;
|
|
7309
7555
|
await options.onAcceptedForProcessing?.(interrupted);
|
|
7310
7556
|
return [
|
|
@@ -7471,7 +7717,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7471
7717
|
completedAtMs
|
|
7472
7718
|
});
|
|
7473
7719
|
}
|
|
7474
|
-
await args.
|
|
7720
|
+
await args.ack?.();
|
|
7475
7721
|
};
|
|
7476
7722
|
const selectAcceptedSteeringMessages = async (args) => {
|
|
7477
7723
|
const selected = [];
|
|
@@ -7557,6 +7803,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7557
7803
|
let processingReaction;
|
|
7558
7804
|
const skippedSteeringMessages = [];
|
|
7559
7805
|
let completed = false;
|
|
7806
|
+
let acked = false;
|
|
7560
7807
|
const onTurnCompleted = async () => {
|
|
7561
7808
|
completed = true;
|
|
7562
7809
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7605,8 +7852,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7605
7852
|
)
|
|
7606
7853
|
);
|
|
7607
7854
|
};
|
|
7608
|
-
const
|
|
7609
|
-
await hooks.
|
|
7855
|
+
const ack = async () => {
|
|
7856
|
+
await hooks.ack?.();
|
|
7857
|
+
acked = true;
|
|
7610
7858
|
await startQueuedProcessingReactions();
|
|
7611
7859
|
};
|
|
7612
7860
|
const drainSteeringMessages = createAcceptedSteeringDrain(hooks, {
|
|
@@ -7636,7 +7884,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7636
7884
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7637
7885
|
destination: hooks.destination,
|
|
7638
7886
|
queuedMessages,
|
|
7639
|
-
|
|
7887
|
+
ack,
|
|
7640
7888
|
onToolInvocation: toolInvocationHook,
|
|
7641
7889
|
onTurnCompleted,
|
|
7642
7890
|
drainSteeringMessages,
|
|
@@ -7690,6 +7938,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7690
7938
|
{},
|
|
7691
7939
|
"onNewMention failed"
|
|
7692
7940
|
);
|
|
7941
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
7942
|
+
return;
|
|
7943
|
+
}
|
|
7693
7944
|
if (!eventId) {
|
|
7694
7945
|
throw new Error(
|
|
7695
7946
|
"Sentry did not return an event ID for mention_handler_failed"
|
|
@@ -7727,6 +7978,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7727
7978
|
let processingReaction;
|
|
7728
7979
|
const skippedSteeringMessages = [];
|
|
7729
7980
|
let completed = false;
|
|
7981
|
+
let acked = false;
|
|
7730
7982
|
const onTurnCompleted = async () => {
|
|
7731
7983
|
completed = true;
|
|
7732
7984
|
await flushSkippedSteeringMessagesBestEffort({
|
|
@@ -7793,7 +8045,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7793
8045
|
message,
|
|
7794
8046
|
decision: { shouldReply: false, reason },
|
|
7795
8047
|
context: threadContext,
|
|
7796
|
-
|
|
8048
|
+
ack: hooks.ack,
|
|
7797
8049
|
text: combinedText
|
|
7798
8050
|
});
|
|
7799
8051
|
return;
|
|
@@ -7830,7 +8082,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7830
8082
|
message,
|
|
7831
8083
|
decision,
|
|
7832
8084
|
context: threadContext,
|
|
7833
|
-
|
|
8085
|
+
ack: hooks.ack,
|
|
7834
8086
|
preparedState,
|
|
7835
8087
|
text: combinedText
|
|
7836
8088
|
});
|
|
@@ -7842,7 +8094,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7842
8094
|
message,
|
|
7843
8095
|
decision,
|
|
7844
8096
|
context: threadContext,
|
|
7845
|
-
|
|
8097
|
+
ack: hooks.ack,
|
|
7846
8098
|
preparedState,
|
|
7847
8099
|
text: combinedText
|
|
7848
8100
|
});
|
|
@@ -7887,8 +8139,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7887
8139
|
)
|
|
7888
8140
|
);
|
|
7889
8141
|
};
|
|
7890
|
-
const
|
|
7891
|
-
await hooks.
|
|
8142
|
+
const ack = async () => {
|
|
8143
|
+
await hooks.ack?.();
|
|
8144
|
+
acked = true;
|
|
7892
8145
|
await startQueuedProcessingReactions();
|
|
7893
8146
|
};
|
|
7894
8147
|
const toolInvocationHook = createToolInvocationHook(
|
|
@@ -7901,7 +8154,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
7901
8154
|
preparedState,
|
|
7902
8155
|
beforeFirstResponsePost: hooks.beforeFirstResponsePost,
|
|
7903
8156
|
queuedMessages,
|
|
7904
|
-
|
|
8157
|
+
ack,
|
|
7905
8158
|
onToolInvocation: toolInvocationHook,
|
|
7906
8159
|
onTurnCompleted,
|
|
7907
8160
|
drainSteeringMessages,
|
|
@@ -7955,6 +8208,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
7955
8208
|
{},
|
|
7956
8209
|
"onSubscribedMessage failed"
|
|
7957
8210
|
);
|
|
8211
|
+
if (!acked && hooks.isFinalAttempt === false) {
|
|
8212
|
+
return;
|
|
8213
|
+
}
|
|
7958
8214
|
if (!eventId) {
|
|
7959
8215
|
throw new Error(
|
|
7960
8216
|
"Sentry did not return an event ID for subscribed_message_handler_failed"
|
|
@@ -8059,10 +8315,12 @@ function textPart(value) {
|
|
|
8059
8315
|
}
|
|
8060
8316
|
function messageText(message) {
|
|
8061
8317
|
const content = message.content;
|
|
8318
|
+
const unwrapTask = message.role === "user";
|
|
8319
|
+
const displayText = (text) => unwrapTask ? unwrapCurrentInstruction(text) ?? text : text;
|
|
8062
8320
|
if (!Array.isArray(content)) {
|
|
8063
|
-
return typeof content === "string" ? content : "";
|
|
8321
|
+
return typeof content === "string" ? displayText(content) : "";
|
|
8064
8322
|
}
|
|
8065
|
-
return content.map(textPart).filter(Boolean).join("\n").trim();
|
|
8323
|
+
return content.map(textPart).filter((text) => Boolean(text)).map(displayText).join("\n").trim();
|
|
8066
8324
|
}
|
|
8067
8325
|
function sanitizeText(text) {
|
|
8068
8326
|
return text.replace(
|
|
@@ -8904,6 +9162,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
8904
9162
|
}
|
|
8905
9163
|
|
|
8906
9164
|
// src/chat/runtime/reply-executor.ts
|
|
9165
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS4 } from "chat";
|
|
8907
9166
|
import { createSlackSource } from "@sentry/junior-plugin-api";
|
|
8908
9167
|
|
|
8909
9168
|
// src/chat/slack/message.ts
|
|
@@ -9036,6 +9295,9 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
|
|
|
9036
9295
|
}
|
|
9037
9296
|
|
|
9038
9297
|
// src/chat/runtime/reply-executor.ts
|
|
9298
|
+
async function persistThreadStateWithRetry(thread, patch) {
|
|
9299
|
+
await persistWithRetry(() => persistThreadState(thread, patch));
|
|
9300
|
+
}
|
|
9039
9301
|
function collectCanvasUrls(artifacts) {
|
|
9040
9302
|
return new Set(
|
|
9041
9303
|
[
|
|
@@ -9047,6 +9309,14 @@ function collectCanvasUrls(artifacts) {
|
|
|
9047
9309
|
function turnRequester(requester) {
|
|
9048
9310
|
return toStoredSlackRequester(requester);
|
|
9049
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
|
+
}
|
|
9050
9320
|
function isResourceEventMessage(message) {
|
|
9051
9321
|
const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
|
|
9052
9322
|
return raw?.event_type === "resource_event";
|
|
@@ -9128,6 +9398,7 @@ function createReplyToThread(deps) {
|
|
|
9128
9398
|
channelName,
|
|
9129
9399
|
channelType: slackChannelType
|
|
9130
9400
|
});
|
|
9401
|
+
const destinationVisibility = conversationVisibilityFromSlackChannelType(slackChannelType);
|
|
9131
9402
|
const threadTs = getThreadTs(threadId);
|
|
9132
9403
|
const assistantThreadContext = getAssistantThreadContext(message);
|
|
9133
9404
|
const messageTs = getMessageTs(message);
|
|
@@ -9140,7 +9411,8 @@ function createReplyToThread(deps) {
|
|
|
9140
9411
|
channelId: channelId ?? destination.channelId,
|
|
9141
9412
|
messageTs,
|
|
9142
9413
|
teamId,
|
|
9143
|
-
threadTs
|
|
9414
|
+
threadTs,
|
|
9415
|
+
type: destinationVisibility === "public" ? "pub" : "priv"
|
|
9144
9416
|
});
|
|
9145
9417
|
const runId = getRunId(thread, message);
|
|
9146
9418
|
const conversationId = threadId ?? runId;
|
|
@@ -9267,12 +9539,87 @@ function createReplyToThread(deps) {
|
|
|
9267
9539
|
}
|
|
9268
9540
|
};
|
|
9269
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
|
+
};
|
|
9270
9617
|
if (preparedState.userMessageAlreadyReplied) {
|
|
9271
9618
|
await persistThreadState(thread, {
|
|
9272
9619
|
conversation: preparedState.conversation
|
|
9273
9620
|
});
|
|
9274
9621
|
await options.onTurnStatePersisted?.();
|
|
9275
|
-
await options.
|
|
9622
|
+
await options.ack?.();
|
|
9276
9623
|
await options.onTurnCompleted?.();
|
|
9277
9624
|
return;
|
|
9278
9625
|
}
|
|
@@ -9282,6 +9629,9 @@ function createReplyToThread(deps) {
|
|
|
9282
9629
|
sessionId: activeTurnId
|
|
9283
9630
|
});
|
|
9284
9631
|
if (resumeRequest) {
|
|
9632
|
+
if (!await appendParkedTurnInput(resumeRequest.sessionId)) {
|
|
9633
|
+
throw new TurnInputDeferredError();
|
|
9634
|
+
}
|
|
9285
9635
|
try {
|
|
9286
9636
|
await deps.services.scheduleAgentContinue(resumeRequest);
|
|
9287
9637
|
} catch (error) {
|
|
@@ -9302,7 +9652,7 @@ function createReplyToThread(deps) {
|
|
|
9302
9652
|
conversation: preparedState.conversation
|
|
9303
9653
|
});
|
|
9304
9654
|
await options.onTurnStatePersisted?.();
|
|
9305
|
-
await options.
|
|
9655
|
+
await options.ack?.();
|
|
9306
9656
|
return;
|
|
9307
9657
|
}
|
|
9308
9658
|
const sessionRecord = await getAgentTurnSessionRecord(
|
|
@@ -9311,27 +9661,34 @@ function createReplyToThread(deps) {
|
|
|
9311
9661
|
);
|
|
9312
9662
|
if (sessionRecord?.state === "awaiting_resume") {
|
|
9313
9663
|
if (sessionRecord.resumeReason === "auth") {
|
|
9314
|
-
await
|
|
9315
|
-
|
|
9664
|
+
await abandonAgentTurnSessionRecord({
|
|
9665
|
+
conversationId,
|
|
9666
|
+
sessionId: activeTurnId,
|
|
9667
|
+
errorMessage: "Auth-parked session superseded by a new user message"
|
|
9316
9668
|
});
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
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;
|
|
9320
9691
|
}
|
|
9321
|
-
await failAgentTurnSessionRecord({
|
|
9322
|
-
conversationId,
|
|
9323
|
-
expectedVersion: sessionRecord.version,
|
|
9324
|
-
sessionId: activeTurnId,
|
|
9325
|
-
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
9326
|
-
});
|
|
9327
|
-
markTurnFailed({
|
|
9328
|
-
conversation: preparedState.conversation,
|
|
9329
|
-
nowMs: Date.now(),
|
|
9330
|
-
sessionId: activeTurnId,
|
|
9331
|
-
markConversationMessage,
|
|
9332
|
-
updateConversationStats
|
|
9333
|
-
});
|
|
9334
|
-
activeTurnId = void 0;
|
|
9335
9692
|
}
|
|
9336
9693
|
}
|
|
9337
9694
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
@@ -9367,7 +9724,7 @@ function createReplyToThread(deps) {
|
|
|
9367
9724
|
conversation: preparedState.conversation
|
|
9368
9725
|
});
|
|
9369
9726
|
await options.onTurnStatePersisted?.();
|
|
9370
|
-
await options.
|
|
9727
|
+
await options.ack?.();
|
|
9371
9728
|
return;
|
|
9372
9729
|
}
|
|
9373
9730
|
startActiveTurn({
|
|
@@ -9387,6 +9744,7 @@ function createReplyToThread(deps) {
|
|
|
9387
9744
|
surface: "slack",
|
|
9388
9745
|
requester,
|
|
9389
9746
|
destination,
|
|
9747
|
+
destinationVisibility,
|
|
9390
9748
|
source,
|
|
9391
9749
|
traceId: getActiveTraceId()
|
|
9392
9750
|
}).catch((error) => {
|
|
@@ -9503,8 +9861,10 @@ function createReplyToThread(deps) {
|
|
|
9503
9861
|
};
|
|
9504
9862
|
let persistedAtLeastOnce = false;
|
|
9505
9863
|
let shouldPersistFailureState = true;
|
|
9864
|
+
let finalReplyDelivered = false;
|
|
9506
9865
|
let latestArtifacts = preparedState.artifacts;
|
|
9507
9866
|
let assistantTitleArtifacts = {};
|
|
9867
|
+
const hasVisibleSlackDelivery = (post) => post.text.trim().length > 0 || Boolean(post.files?.length);
|
|
9508
9868
|
try {
|
|
9509
9869
|
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
9510
9870
|
conversationId,
|
|
@@ -9596,39 +9956,16 @@ function createReplyToThread(deps) {
|
|
|
9596
9956
|
);
|
|
9597
9957
|
});
|
|
9598
9958
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
9599
|
-
const
|
|
9600
|
-
|
|
9601
|
-
queuedMessages.map(async (queued) => {
|
|
9602
|
-
const attachments = queued.message.attachments;
|
|
9603
|
-
return {
|
|
9604
|
-
text: queued.userText,
|
|
9605
|
-
timestampMs: queued.message.metadata.dateSent.getTime(),
|
|
9606
|
-
omittedImageAttachmentCount: !isVisionEnabled() && hasPotentialImageAttachment(attachments) ? countPotentialImageAttachments(attachments) : 0,
|
|
9607
|
-
userAttachments: await deps.resolveUserAttachments(
|
|
9608
|
-
attachments,
|
|
9609
|
-
{
|
|
9610
|
-
threadId,
|
|
9611
|
-
requesterId: isResourceEventMessage(queued.message) ? void 0 : queued.message.author.userId,
|
|
9612
|
-
channelId,
|
|
9613
|
-
runId,
|
|
9614
|
-
conversation: preparedState.conversation,
|
|
9615
|
-
messageTs: getSlackMessageTs(queued.message)
|
|
9616
|
-
}
|
|
9617
|
-
)
|
|
9618
|
-
};
|
|
9619
|
-
})
|
|
9620
|
-
);
|
|
9621
|
-
};
|
|
9622
|
-
const drainSteeringMessages = options.drainSteeringMessages ? async (inject) => {
|
|
9623
|
-
let injectedMessages;
|
|
9959
|
+
const drainSteeringMessages = options.drainSteeringMessages ? async (accept) => {
|
|
9960
|
+
let acceptedMessages;
|
|
9624
9961
|
const drained = await options.drainSteeringMessages(
|
|
9625
9962
|
async (queuedMessages) => {
|
|
9626
|
-
|
|
9627
|
-
await
|
|
9963
|
+
acceptedMessages = await resolveSteeringMessages(queuedMessages);
|
|
9964
|
+
await accept(acceptedMessages);
|
|
9628
9965
|
},
|
|
9629
9966
|
{ conversationContext: preparedState.conversationContext }
|
|
9630
9967
|
);
|
|
9631
|
-
return
|
|
9968
|
+
return acceptedMessages ?? await resolveSteeringMessages(drained);
|
|
9632
9969
|
} : void 0;
|
|
9633
9970
|
let reply = await deps.services.generateAssistantReply(
|
|
9634
9971
|
effectiveUserText,
|
|
@@ -9694,7 +10031,7 @@ function createReplyToThread(deps) {
|
|
|
9694
10031
|
},
|
|
9695
10032
|
onStatus: (nextStatus) => status.update(nextStatus),
|
|
9696
10033
|
onToolInvocation: options.onToolInvocation,
|
|
9697
|
-
onInputCommitted: options.
|
|
10034
|
+
onInputCommitted: options.ack,
|
|
9698
10035
|
drainSteeringMessages,
|
|
9699
10036
|
shouldYield: options.shouldYield
|
|
9700
10037
|
}
|
|
@@ -9727,6 +10064,14 @@ function createReplyToThread(deps) {
|
|
|
9727
10064
|
const shouldUseSlackFooter = Boolean(replyFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
9728
10065
|
if (plannedPosts.length > 0) {
|
|
9729
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
|
+
}
|
|
9730
10075
|
if (shouldUseSlackFooter) {
|
|
9731
10076
|
const slackChannelId = channelId;
|
|
9732
10077
|
const slackThreadTs = threadTs;
|
|
@@ -9770,16 +10115,34 @@ function createReplyToThread(deps) {
|
|
|
9770
10115
|
}
|
|
9771
10116
|
} else {
|
|
9772
10117
|
for (const post of plannedPosts) {
|
|
10118
|
+
if (!hasVisibleSlackDelivery(post)) {
|
|
10119
|
+
continue;
|
|
10120
|
+
}
|
|
9773
10121
|
sent = await postThreadReply(
|
|
9774
10122
|
buildSlackOutputMessage(post.text, post.files),
|
|
9775
10123
|
post.stage
|
|
9776
10124
|
);
|
|
9777
10125
|
}
|
|
9778
10126
|
}
|
|
10127
|
+
finalReplyDelivered = true;
|
|
10128
|
+
shouldPersistFailureState = false;
|
|
9779
10129
|
const firstPlannedMessageHasFiles = (plannedPosts[0]?.files?.length ?? 0) > 0;
|
|
9780
10130
|
if (sent && reactionPerformed && plannedPosts.length === 1 && !firstPlannedMessageHasFiles && isRedundantReactionAckText(reply.text)) {
|
|
9781
|
-
|
|
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
|
+
}
|
|
9782
10142
|
}
|
|
10143
|
+
} else {
|
|
10144
|
+
finalReplyDelivered = true;
|
|
10145
|
+
shouldPersistFailureState = false;
|
|
9783
10146
|
}
|
|
9784
10147
|
const completedState = buildDeliveredTurnStatePatch({
|
|
9785
10148
|
artifactStatePatch: {
|
|
@@ -9795,27 +10158,63 @@ function createReplyToThread(deps) {
|
|
|
9795
10158
|
if (completedState.artifacts) {
|
|
9796
10159
|
latestArtifacts = completedState.artifacts;
|
|
9797
10160
|
}
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
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
|
|
9818
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
|
+
);
|
|
9819
10218
|
}
|
|
9820
10219
|
preparedState.conversation = completedState.conversation;
|
|
9821
10220
|
persistedAtLeastOnce = true;
|
|
@@ -9849,6 +10248,17 @@ function createReplyToThread(deps) {
|
|
|
9849
10248
|
}
|
|
9850
10249
|
}
|
|
9851
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
|
+
}
|
|
9852
10262
|
if (isCooperativeTurnYieldError(error)) {
|
|
9853
10263
|
shouldPersistFailureState = false;
|
|
9854
10264
|
throw error;
|
|
@@ -10023,6 +10433,7 @@ function createReplyToThread(deps) {
|
|
|
10023
10433
|
state: "failed",
|
|
10024
10434
|
requester,
|
|
10025
10435
|
destination,
|
|
10436
|
+
destinationVisibility,
|
|
10026
10437
|
source,
|
|
10027
10438
|
traceId: getActiveTraceId()
|
|
10028
10439
|
});
|
|
@@ -10460,7 +10871,7 @@ function createJuniorSlackAdapter(config) {
|
|
|
10460
10871
|
|
|
10461
10872
|
// src/chat/runtime/agent-continue-runner.ts
|
|
10462
10873
|
var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
10463
|
-
function
|
|
10874
|
+
function sleep2(ms) {
|
|
10464
10875
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10465
10876
|
}
|
|
10466
10877
|
async function persistCompletedReplyState2(args) {
|
|
@@ -10547,6 +10958,28 @@ async function failContinuationStartup(args) {
|
|
|
10547
10958
|
);
|
|
10548
10959
|
}
|
|
10549
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
|
+
}
|
|
10550
10983
|
function isContinuationResume(summary) {
|
|
10551
10984
|
return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield");
|
|
10552
10985
|
}
|
|
@@ -10610,11 +11043,20 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10610
11043
|
payload.destination,
|
|
10611
11044
|
"Slack continuation"
|
|
10612
11045
|
);
|
|
10613
|
-
const requester =
|
|
10614
|
-
|
|
11046
|
+
const requester = await resolveContinuationRequester({
|
|
11047
|
+
conversationId: payload.conversationId,
|
|
11048
|
+
sessionRecordRequester: activeSessionRecord.requester,
|
|
10615
11049
|
teamId: destination.teamId,
|
|
10616
11050
|
userId: userMessage2.author.userId
|
|
10617
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
|
+
}
|
|
10618
11060
|
if (!activeSessionRecord.source) {
|
|
10619
11061
|
await failAgentTurnSessionRecord({
|
|
10620
11062
|
conversationId: payload.conversationId,
|
|
@@ -10724,8 +11166,119 @@ async function continueSlackAgentRun(payload, options = {}) {
|
|
|
10724
11166
|
}
|
|
10725
11167
|
});
|
|
10726
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
|
+
}
|
|
10727
11272
|
async function resumeAwaitingSlackContinuation(conversationId, options = {}) {
|
|
10728
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
|
+
}
|
|
10729
11282
|
for (const summary of summaries) {
|
|
10730
11283
|
if (!isContinuationResume(summary)) {
|
|
10731
11284
|
continue;
|
|
@@ -10791,7 +11344,7 @@ async function continueSlackAgentRunWithLockRetry(payload, options = {}) {
|
|
|
10791
11344
|
},
|
|
10792
11345
|
"Agent continuation lock was busy; retrying"
|
|
10793
11346
|
);
|
|
10794
|
-
await
|
|
11347
|
+
await sleep2(delayMs);
|
|
10795
11348
|
}
|
|
10796
11349
|
}
|
|
10797
11350
|
return true;
|