@sentry/junior 0.71.3 → 0.72.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/bin/junior.mjs +10 -0
- package/dist/api-reference.d.ts +2 -0
- package/dist/app.d.ts +5 -5
- package/dist/app.js +1039 -1971
- package/dist/chat/agent-dispatch/heartbeat.d.ts +0 -6
- package/dist/chat/mcp/errors.d.ts +3 -0
- package/dist/chat/mcp/tool-manager.d.ts +5 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -2
- package/dist/chat/requester.d.ts +60 -0
- package/dist/chat/respond.d.ts +2 -6
- package/dist/chat/runtime/agent-continue-runner.d.ts +25 -0
- package/dist/chat/runtime/reply-executor.d.ts +4 -4
- package/dist/chat/runtime/turn.d.ts +2 -2
- package/dist/chat/services/agent-continue.d.ts +27 -0
- package/dist/chat/services/message-actor-identity.d.ts +12 -4
- package/dist/chat/services/turn-session-record.d.ts +10 -7
- package/dist/chat/slack/user.d.ts +4 -4
- package/dist/chat/state/adapter.d.ts +2 -0
- package/dist/chat/state/conversation-details.d.ts +4 -3
- package/dist/chat/state/session-log.d.ts +43 -0
- package/dist/chat/state/turn-session.d.ts +7 -10
- package/dist/chat/task-execution/slack-work.d.ts +5 -5
- package/dist/chat/task-execution/store.d.ts +83 -48
- package/dist/chat/task-execution/worker.d.ts +3 -3
- package/dist/chat/tools/definition.d.ts +3 -0
- package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -1
- package/dist/chat/tools/types.d.ts +2 -5
- package/dist/{chunk-R62YWUNO.js → chunk-3FYPXHPL.js} +10 -28
- package/dist/chunk-4JXCSGSA.js +212 -0
- package/dist/{chunk-GT67ZWZQ.js → chunk-55XEZFGD.js} +5 -3
- package/dist/{chunk-BBXYXOJW.js → chunk-6GEYPE6T.js} +18 -523
- package/dist/chunk-G3E7SCME.js +28 -0
- package/dist/{chunk-UXG6TU2U.js → chunk-GB3AL54K.js} +8 -93
- package/dist/chunk-HNMUVGSR.js +1119 -0
- package/dist/{chunk-XE2VFQQN.js → chunk-ICKIDP7G.js} +1 -1
- package/dist/chunk-KVZL5NZS.js +519 -0
- package/dist/chunk-PP7AGSBU.js +185 -0
- package/dist/{chunk-B5HKWWQB.js → chunk-VLIO6RQR.js} +8 -6
- package/dist/{chunk-HOGQL2H6.js → chunk-VSNA5KAB.js} +177 -101
- package/dist/{chunk-76YMBKW7.js → chunk-XC33FJZN.js} +4 -12
- package/dist/{chunk-JS4HURDT.js → chunk-ZJQPA67D.js} +25 -25
- package/dist/cli/check.js +10 -8
- package/dist/cli/run.js +9 -1
- package/dist/cli/snapshot-warmup.js +10 -7
- package/dist/cli/upgrade.js +599 -0
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +5 -4
- package/dist/plugins.d.ts +1 -1
- package/dist/reporting/conversations.d.ts +116 -0
- package/dist/reporting.d.ts +24 -129
- package/dist/reporting.js +310 -158
- package/package.json +3 -3
- package/dist/chat/runtime/timeout-resume-runner.d.ts +0 -19
- package/dist/chat/services/requester-identity.d.ts +0 -19
- package/dist/chat/services/timeout-resume.d.ts +0 -23
- package/dist/handlers/turn-resume.d.ts +0 -4
package/dist/app.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineJuniorPlugins,
|
|
3
|
+
getVercelConversationWorkQueue,
|
|
4
|
+
pluginCatalogConfigFromPluginSet,
|
|
5
|
+
pluginHookRegistrationsFromPluginSet,
|
|
6
|
+
resolveConversationWorkQueueTopic,
|
|
7
|
+
verifyConversationQueueMessage
|
|
8
|
+
} from "./chunk-ICKIDP7G.js";
|
|
1
9
|
import {
|
|
2
10
|
GET,
|
|
3
11
|
JUNIOR_PERSONALITY,
|
|
@@ -20,7 +28,6 @@ import {
|
|
|
20
28
|
getAgentTurnSessionRecord,
|
|
21
29
|
getInterruptionMarker,
|
|
22
30
|
initConversationContext,
|
|
23
|
-
listAgentTurnSessionSummaries,
|
|
24
31
|
listAgentTurnSessionSummariesForConversation,
|
|
25
32
|
loadConnectedMcpProviders,
|
|
26
33
|
loadProjection,
|
|
@@ -39,13 +46,53 @@ import {
|
|
|
39
46
|
upsertAgentTurnSessionRecord,
|
|
40
47
|
validateAgentPlugins,
|
|
41
48
|
verifySlackDirectCredentialSubject
|
|
42
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-VSNA5KAB.js";
|
|
43
50
|
import {
|
|
44
51
|
discoverSkills,
|
|
45
52
|
findSkillByName,
|
|
46
53
|
loadSkillsByName,
|
|
47
54
|
parseSkillInvocation
|
|
48
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-55XEZFGD.js";
|
|
56
|
+
import {
|
|
57
|
+
buildConversationStatePatch,
|
|
58
|
+
coerceThreadConversationState
|
|
59
|
+
} from "./chunk-4JXCSGSA.js";
|
|
60
|
+
import {
|
|
61
|
+
CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
|
|
62
|
+
CONVERSATION_WORK_STALE_ENQUEUE_MS,
|
|
63
|
+
JUNIOR_THREAD_STATE_TTL_MS,
|
|
64
|
+
appendAndEnqueueInboundMessage,
|
|
65
|
+
checkInConversationWork,
|
|
66
|
+
clearExpiredConversationLease,
|
|
67
|
+
completeConversationWork,
|
|
68
|
+
countPendingConversationMessages,
|
|
69
|
+
drainConversationMailbox,
|
|
70
|
+
getConversationWorkState,
|
|
71
|
+
hasRunnableConversationWork,
|
|
72
|
+
listActiveConversationIds,
|
|
73
|
+
markConversationMessagesInjected,
|
|
74
|
+
markConversationWorkEnqueued,
|
|
75
|
+
releaseConversationWork,
|
|
76
|
+
removeActiveConversation,
|
|
77
|
+
requestConversationContinuation,
|
|
78
|
+
requestConversationWork,
|
|
79
|
+
startConversationWork
|
|
80
|
+
} from "./chunk-HNMUVGSR.js";
|
|
81
|
+
import {
|
|
82
|
+
SlackActionError,
|
|
83
|
+
createSlackDestination,
|
|
84
|
+
destinationKey,
|
|
85
|
+
downloadPrivateSlackFile,
|
|
86
|
+
getFilePermalink,
|
|
87
|
+
getHeaderString,
|
|
88
|
+
getSlackClient,
|
|
89
|
+
isConversationScopedChannel,
|
|
90
|
+
isDmChannel,
|
|
91
|
+
normalizeSlackConversationId,
|
|
92
|
+
parseDestination,
|
|
93
|
+
sameDestination,
|
|
94
|
+
withSlackRetries
|
|
95
|
+
} from "./chunk-XC33FJZN.js";
|
|
49
96
|
import {
|
|
50
97
|
buildNonInteractiveShellScript,
|
|
51
98
|
createSandboxInstance,
|
|
@@ -54,19 +101,16 @@ import {
|
|
|
54
101
|
isSnapshotMissingError,
|
|
55
102
|
resolveRuntimeDependencySnapshot,
|
|
56
103
|
runNonInteractiveCommand
|
|
57
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-VLIO6RQR.js";
|
|
58
105
|
import {
|
|
59
|
-
ACTIVE_LOCK_TTL_MS,
|
|
60
106
|
SANDBOX_DATA_ROOT,
|
|
61
107
|
SANDBOX_SKILLS_ROOT,
|
|
62
108
|
SANDBOX_WORKSPACE_ROOT,
|
|
63
|
-
getStateAdapter,
|
|
64
109
|
sandboxSkillDir,
|
|
65
110
|
sandboxSkillFile
|
|
66
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-G3E7SCME.js";
|
|
67
112
|
import {
|
|
68
113
|
CredentialUnavailableError,
|
|
69
|
-
buildActorIdentity,
|
|
70
114
|
buildOAuthTokenRequest,
|
|
71
115
|
createPluginBroker,
|
|
72
116
|
credentialContextSchema,
|
|
@@ -77,39 +121,21 @@ import {
|
|
|
77
121
|
getPluginOAuthConfig,
|
|
78
122
|
getPluginProviders,
|
|
79
123
|
hasRequiredOAuthScope,
|
|
80
|
-
isActorUserId,
|
|
81
124
|
isPluginConfigKey,
|
|
82
125
|
isPluginProvider,
|
|
83
|
-
parseActorUserId,
|
|
84
126
|
parseOAuthTokenResponse,
|
|
85
127
|
resolveAuthTokenPlaceholder,
|
|
86
128
|
resolvePluginCommandEnv,
|
|
87
|
-
setPluginCatalogConfig
|
|
88
|
-
|
|
89
|
-
} from "./chunk-UXG6TU2U.js";
|
|
129
|
+
setPluginCatalogConfig
|
|
130
|
+
} from "./chunk-GB3AL54K.js";
|
|
90
131
|
import {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
pluginHookRegistrationsFromPluginSet,
|
|
95
|
-
resolveConversationWorkQueueTopic,
|
|
96
|
-
verifyConversationQueueMessage
|
|
97
|
-
} from "./chunk-XE2VFQQN.js";
|
|
132
|
+
homeDir,
|
|
133
|
+
listReferenceFiles
|
|
134
|
+
} from "./chunk-KVZL5NZS.js";
|
|
98
135
|
import {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
downloadPrivateSlackFile,
|
|
103
|
-
getFilePermalink,
|
|
104
|
-
getHeaderString,
|
|
105
|
-
getSlackClient,
|
|
106
|
-
isConversationScopedChannel,
|
|
107
|
-
isDmChannel,
|
|
108
|
-
normalizeSlackConversationId,
|
|
109
|
-
parseDestination,
|
|
110
|
-
sameDestination,
|
|
111
|
-
withSlackRetries
|
|
112
|
-
} from "./chunk-76YMBKW7.js";
|
|
136
|
+
ACTIVE_LOCK_TTL_MS,
|
|
137
|
+
getStateAdapter
|
|
138
|
+
} from "./chunk-3FYPXHPL.js";
|
|
113
139
|
import {
|
|
114
140
|
FUNCTION_TIMEOUT_BUFFER_SECONDS,
|
|
115
141
|
GEN_AI_PROVIDER_NAME,
|
|
@@ -161,7 +187,15 @@ import {
|
|
|
161
187
|
toObservablePromptPart,
|
|
162
188
|
trimTrailingAssistantMessages,
|
|
163
189
|
upsertActiveSkill
|
|
164
|
-
} from "./chunk-
|
|
190
|
+
} from "./chunk-ZJQPA67D.js";
|
|
191
|
+
import {
|
|
192
|
+
createRequester,
|
|
193
|
+
createRequesterFromStoredSlackRequester,
|
|
194
|
+
createSlackRequester,
|
|
195
|
+
isActorUserId,
|
|
196
|
+
parseActorUserId,
|
|
197
|
+
toStoredSlackRequester
|
|
198
|
+
} from "./chunk-PP7AGSBU.js";
|
|
165
199
|
import {
|
|
166
200
|
buildTurnFailureResponse,
|
|
167
201
|
createChatSdkLogger,
|
|
@@ -170,9 +204,7 @@ import {
|
|
|
170
204
|
extractGenAiUsageSummary,
|
|
171
205
|
getActiveTraceId,
|
|
172
206
|
getLogContextAttributes,
|
|
173
|
-
homeDir,
|
|
174
207
|
isRecord,
|
|
175
|
-
listReferenceFiles,
|
|
176
208
|
logError,
|
|
177
209
|
logException,
|
|
178
210
|
logInfo,
|
|
@@ -183,11 +215,10 @@ import {
|
|
|
183
215
|
setSpanAttributes,
|
|
184
216
|
setSpanStatus,
|
|
185
217
|
setTags,
|
|
186
|
-
toOptionalNumber,
|
|
187
218
|
toOptionalString,
|
|
188
219
|
withContext,
|
|
189
220
|
withSpan
|
|
190
|
-
} from "./chunk-
|
|
221
|
+
} from "./chunk-6GEYPE6T.js";
|
|
191
222
|
import {
|
|
192
223
|
sentry_exports
|
|
193
224
|
} from "./chunk-Z3YD6NHK.js";
|
|
@@ -1051,6 +1082,12 @@ function getMcpAwareErrorType(error, fallback) {
|
|
|
1051
1082
|
function getMcpAwareErrorMessage(error) {
|
|
1052
1083
|
return error instanceof Error ? error.message : String(error);
|
|
1053
1084
|
}
|
|
1085
|
+
function getMcpAwareTelemetryMessage(error, privacy) {
|
|
1086
|
+
if (privacy === "private" && error instanceof McpToolError) {
|
|
1087
|
+
return "MCP tool call failed";
|
|
1088
|
+
}
|
|
1089
|
+
return getMcpAwareErrorMessage(error);
|
|
1090
|
+
}
|
|
1054
1091
|
|
|
1055
1092
|
// src/chat/mcp/tool-manager.ts
|
|
1056
1093
|
function normalizeMcpToolName(provider, toolName) {
|
|
@@ -1270,63 +1307,104 @@ var McpToolManager = class {
|
|
|
1270
1307
|
...tool2.title?.trim() ? { title: tool2.title.trim() } : {},
|
|
1271
1308
|
...outputSchema ? { outputSchema } : {},
|
|
1272
1309
|
...annotations ? { annotations } : {},
|
|
1273
|
-
execute: async (args) => {
|
|
1310
|
+
execute: async (args, options) => {
|
|
1274
1311
|
const resolvedArgs = typeof args === "object" && args !== null ? args : {};
|
|
1312
|
+
const conversationPrivacy = options?.conversationPrivacy ?? "private";
|
|
1313
|
+
const managedToolName = normalizeMcpToolName(
|
|
1314
|
+
plugin.manifest.name,
|
|
1315
|
+
tool2.name
|
|
1316
|
+
);
|
|
1275
1317
|
const baseAttributes = {
|
|
1276
|
-
"mcp.method.name": "tools/call"
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const result = await client.callTool(tool2.name, resolvedArgs);
|
|
1281
|
-
if ("isError" in result && result.isError) {
|
|
1282
|
-
throw new McpToolError(extractMcpErrorMessage(result));
|
|
1283
|
-
}
|
|
1284
|
-
return {
|
|
1285
|
-
content: toAgentToolContent(result),
|
|
1286
|
-
details: {
|
|
1287
|
-
provider: plugin.manifest.name,
|
|
1288
|
-
tool: tool2.name,
|
|
1289
|
-
rawResult: result
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
if (error instanceof McpAuthorizationRequiredError && await this.handleAuthorizationRequired(
|
|
1318
|
+
"mcp.method.name": "tools/call",
|
|
1319
|
+
"gen_ai.operation.name": "execute_tool",
|
|
1320
|
+
"gen_ai.tool.name": managedToolName,
|
|
1321
|
+
"gen_ai.tool.description": describeMcpTool(
|
|
1294
1322
|
plugin.manifest.name,
|
|
1295
|
-
|
|
1296
|
-
)
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1323
|
+
tool2
|
|
1324
|
+
),
|
|
1325
|
+
"gen_ai.tool.type": "extension",
|
|
1326
|
+
"app.plugin.name": plugin.manifest.name,
|
|
1327
|
+
...options?.toolCallId ? { "gen_ai.tool.call.id": options.toolCallId } : {}
|
|
1328
|
+
};
|
|
1329
|
+
const argumentAttribute = serializeMcpPayload(
|
|
1330
|
+
resolvedArgs,
|
|
1331
|
+
conversationPrivacy
|
|
1332
|
+
);
|
|
1333
|
+
return await withSpan(
|
|
1334
|
+
`execute_tool ${managedToolName}`,
|
|
1335
|
+
"gen_ai.execute_tool",
|
|
1336
|
+
{},
|
|
1337
|
+
async () => {
|
|
1338
|
+
try {
|
|
1339
|
+
const result = await client.callTool(tool2.name, resolvedArgs);
|
|
1340
|
+
if ("isError" in result && result.isError) {
|
|
1341
|
+
throw new McpToolError(extractMcpErrorMessage(result));
|
|
1342
|
+
}
|
|
1343
|
+
const resultAttribute = serializeMcpPayload(
|
|
1344
|
+
result,
|
|
1345
|
+
conversationPrivacy
|
|
1346
|
+
);
|
|
1347
|
+
if (resultAttribute) {
|
|
1348
|
+
setSpanAttributes({
|
|
1349
|
+
"gen_ai.tool.call.result": resultAttribute
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
return {
|
|
1353
|
+
content: toAgentToolContent(result),
|
|
1354
|
+
details: {
|
|
1355
|
+
provider: plugin.manifest.name,
|
|
1356
|
+
tool: tool2.name,
|
|
1357
|
+
rawResult: result
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
if (error instanceof McpAuthorizationRequiredError && await this.handleAuthorizationRequired(
|
|
1362
|
+
plugin.manifest.name,
|
|
1363
|
+
error
|
|
1364
|
+
)) {
|
|
1365
|
+
const parkedResult = {
|
|
1307
1366
|
toolResult: {
|
|
1308
1367
|
authorizationPending: true
|
|
1309
1368
|
}
|
|
1310
|
-
}
|
|
1369
|
+
};
|
|
1370
|
+
return {
|
|
1371
|
+
// Pi turns thrown tool errors into toolResult isError frames.
|
|
1372
|
+
// Once auth pause has been requested, return a placeholder result
|
|
1373
|
+
// and let the aborted turn park cleanly instead of surfacing a
|
|
1374
|
+
// spurious tool failure to the model.
|
|
1375
|
+
content: [{ type: "text", text: "Authorization pending." }],
|
|
1376
|
+
details: {
|
|
1377
|
+
provider: plugin.manifest.name,
|
|
1378
|
+
tool: tool2.name,
|
|
1379
|
+
rawResult: parkedResult
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1311
1382
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1383
|
+
const errorAttributes = {
|
|
1384
|
+
...baseAttributes,
|
|
1385
|
+
"error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
|
|
1386
|
+
"exception.message": getMcpAwareTelemetryMessage(
|
|
1387
|
+
error,
|
|
1388
|
+
conversationPrivacy
|
|
1389
|
+
)
|
|
1390
|
+
};
|
|
1391
|
+
setSpanAttributes(errorAttributes);
|
|
1392
|
+
if (error instanceof McpToolError) {
|
|
1393
|
+
logWarn(
|
|
1394
|
+
"mcp_tool_call_failed",
|
|
1395
|
+
{},
|
|
1396
|
+
errorAttributes,
|
|
1397
|
+
"MCP tool call failed"
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
throw error;
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1315
1404
|
...baseAttributes,
|
|
1316
|
-
"
|
|
1317
|
-
"exception.message": getMcpAwareErrorMessage(error)
|
|
1318
|
-
};
|
|
1319
|
-
setSpanAttributes(errorAttributes);
|
|
1320
|
-
if (error instanceof McpToolError) {
|
|
1321
|
-
logWarn(
|
|
1322
|
-
"mcp_tool_call_failed",
|
|
1323
|
-
{},
|
|
1324
|
-
errorAttributes,
|
|
1325
|
-
"MCP tool call failed"
|
|
1326
|
-
);
|
|
1405
|
+
...argumentAttribute ? { "gen_ai.tool.call.arguments": argumentAttribute } : {}
|
|
1327
1406
|
}
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1407
|
+
);
|
|
1330
1408
|
}
|
|
1331
1409
|
};
|
|
1332
1410
|
}
|
|
@@ -1371,6 +1449,11 @@ var McpToolManager = class {
|
|
|
1371
1449
|
function toOptionalRecord(value) {
|
|
1372
1450
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1373
1451
|
}
|
|
1452
|
+
function serializeMcpPayload(payload, privacy) {
|
|
1453
|
+
return serializeGenAiAttribute(
|
|
1454
|
+
privacy === "private" ? toGenAiPayloadMetadata(payload) : payload
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1374
1457
|
|
|
1375
1458
|
// src/chat/mcp/tool-name.ts
|
|
1376
1459
|
function parseMcpProviderFromToolName(toolName) {
|
|
@@ -2713,7 +2796,7 @@ function createCallMcpToolTool(mcpToolManager) {
|
|
|
2713
2796
|
},
|
|
2714
2797
|
{ additionalProperties: false }
|
|
2715
2798
|
),
|
|
2716
|
-
execute: async (input) => {
|
|
2799
|
+
execute: async (input, options) => {
|
|
2717
2800
|
const { tool_name } = input;
|
|
2718
2801
|
const provider = parseMcpProviderFromToolName(tool_name);
|
|
2719
2802
|
if (provider) {
|
|
@@ -2738,7 +2821,11 @@ function createCallMcpToolTool(mcpToolManager) {
|
|
|
2738
2821
|
throw new McpToolError(missingToolMessage(tool_name, provider));
|
|
2739
2822
|
}
|
|
2740
2823
|
return await mcpTool.execute(
|
|
2741
|
-
resolveMcpArguments(input)
|
|
2824
|
+
resolveMcpArguments(input),
|
|
2825
|
+
{
|
|
2826
|
+
conversationPrivacy: options?.conversationPrivacy ?? "private",
|
|
2827
|
+
...options?.toolCallId ? { toolCallId: options.toolCallId } : {}
|
|
2828
|
+
}
|
|
2742
2829
|
);
|
|
2743
2830
|
}
|
|
2744
2831
|
});
|
|
@@ -5226,13 +5313,13 @@ function createSystemTimeTool() {
|
|
|
5226
5313
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5227
5314
|
inputSchema: Type20.Object({}),
|
|
5228
5315
|
execute: async () => {
|
|
5229
|
-
const
|
|
5316
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
5230
5317
|
return {
|
|
5231
5318
|
ok: true,
|
|
5232
|
-
unix_ms:
|
|
5233
|
-
iso_utc:
|
|
5234
|
-
iso_local: new Date(
|
|
5235
|
-
timezone_offset_minutes:
|
|
5319
|
+
unix_ms: now2.getTime(),
|
|
5320
|
+
iso_utc: now2.toISOString(),
|
|
5321
|
+
iso_local: new Date(now2.getTime() - now2.getTimezoneOffset() * 6e4).toISOString().replace("Z", ""),
|
|
5322
|
+
timezone_offset_minutes: now2.getTimezoneOffset()
|
|
5236
5323
|
};
|
|
5237
5324
|
}
|
|
5238
5325
|
});
|
|
@@ -5244,9 +5331,6 @@ import {
|
|
|
5244
5331
|
} from "@earendil-works/pi-agent-core";
|
|
5245
5332
|
import { Type as Type21 } from "@sinclair/typebox";
|
|
5246
5333
|
|
|
5247
|
-
// src/chat/state/ttl.ts
|
|
5248
|
-
var JUNIOR_THREAD_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5249
|
-
|
|
5250
5334
|
// src/chat/tools/advisor/session-store.ts
|
|
5251
5335
|
var ADVISOR_SESSION_TTL_MS = JUNIOR_THREAD_STATE_TTL_MS;
|
|
5252
5336
|
function cloneMessages(messages) {
|
|
@@ -6667,11 +6751,11 @@ function getSandboxEgressSecret() {
|
|
|
6667
6751
|
}
|
|
6668
6752
|
function createSandboxEgressCredentialToken(input) {
|
|
6669
6753
|
const ttlMs = Math.max(1, input.ttlMs ?? DEFAULT_SESSION_TTL_MS);
|
|
6670
|
-
const
|
|
6754
|
+
const now2 = Date.now();
|
|
6671
6755
|
const context = {
|
|
6672
6756
|
credentials: input.credentials,
|
|
6673
6757
|
egressId: input.egressId,
|
|
6674
|
-
expiresAtMs:
|
|
6758
|
+
expiresAtMs: now2 + ttlMs,
|
|
6675
6759
|
contextId: randomUUID()
|
|
6676
6760
|
};
|
|
6677
6761
|
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
@@ -7953,14 +8037,14 @@ function createSandboxExecutor(options) {
|
|
|
7953
8037
|
if (!credentialEgress) {
|
|
7954
8038
|
throw new Error("Sandbox credential egress is not configured");
|
|
7955
8039
|
}
|
|
7956
|
-
const
|
|
8040
|
+
const now2 = Date.now();
|
|
7957
8041
|
const token = createSandboxEgressCredentialToken({
|
|
7958
8042
|
credentials: credentialEgress,
|
|
7959
8043
|
egressId,
|
|
7960
8044
|
ttlMs: sandboxEgressTokenTtlMs
|
|
7961
8045
|
});
|
|
7962
8046
|
sandboxEgressCredentialTokens.set(egressId, {
|
|
7963
|
-
expiresAtMs:
|
|
8047
|
+
expiresAtMs: now2 + sandboxEgressTokenTtlMs,
|
|
7964
8048
|
token
|
|
7965
8049
|
});
|
|
7966
8050
|
return token;
|
|
@@ -8763,12 +8847,12 @@ async function deleteMcpAuthSession(authSessionId) {
|
|
|
8763
8847
|
}
|
|
8764
8848
|
async function deleteMcpAuthSessionsForUserProvider(userId, provider) {
|
|
8765
8849
|
const stateAdapter = await getConnectedStateAdapter();
|
|
8766
|
-
const
|
|
8767
|
-
const authSessionIds = parseSessionIndex(await stateAdapter.get(
|
|
8850
|
+
const indexKey = sessionIndexKey(userId, provider);
|
|
8851
|
+
const authSessionIds = parseSessionIndex(await stateAdapter.get(indexKey));
|
|
8768
8852
|
for (const authSessionId of authSessionIds) {
|
|
8769
8853
|
await stateAdapter.delete(sessionKey(authSessionId));
|
|
8770
8854
|
}
|
|
8771
|
-
await stateAdapter.delete(
|
|
8855
|
+
await stateAdapter.delete(indexKey);
|
|
8772
8856
|
}
|
|
8773
8857
|
async function getLatestMcpAuthSessionForUserProvider(userId, provider) {
|
|
8774
8858
|
const stateAdapter = await getConnectedStateAdapter();
|
|
@@ -9328,9 +9412,9 @@ function getToolErrorAttributes(error) {
|
|
|
9328
9412
|
...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
|
|
9329
9413
|
};
|
|
9330
9414
|
}
|
|
9331
|
-
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
|
|
9415
|
+
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext, conversationPrivacy) {
|
|
9332
9416
|
const errorType = getToolErrorType(error);
|
|
9333
|
-
const errorMessage =
|
|
9417
|
+
const errorMessage = getMcpAwareTelemetryMessage(error, conversationPrivacy);
|
|
9334
9418
|
setSpanAttributes({
|
|
9335
9419
|
"error.type": errorType,
|
|
9336
9420
|
...error instanceof PluginCredentialFailureError ? { "app.credential.provider": error.provider } : {}
|
|
@@ -9453,7 +9537,9 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9453
9537
|
...signal ? { signal } : {}
|
|
9454
9538
|
}) : await toolDef.execute(toolInput, {
|
|
9455
9539
|
experimental_context: sandbox,
|
|
9456
|
-
...signal ? { signal } : {}
|
|
9540
|
+
...signal ? { signal } : {},
|
|
9541
|
+
conversationPrivacy: effectiveConversationPrivacy,
|
|
9542
|
+
...normalizedToolCallId ? { toolCallId: normalizedToolCallId } : {}
|
|
9457
9543
|
});
|
|
9458
9544
|
const normalized = normalizeToolResult(result, isSandbox);
|
|
9459
9545
|
if (bashCommand && pluginAuthOrchestration) {
|
|
@@ -9484,16 +9570,17 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9484
9570
|
toolName,
|
|
9485
9571
|
normalizedToolCallId,
|
|
9486
9572
|
shouldTrace,
|
|
9487
|
-
spanContext
|
|
9573
|
+
spanContext,
|
|
9574
|
+
effectiveConversationPrivacy
|
|
9488
9575
|
);
|
|
9489
9576
|
}
|
|
9490
9577
|
},
|
|
9491
9578
|
{
|
|
9492
9579
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
9493
9580
|
"gen_ai.operation.name": "execute_tool",
|
|
9494
|
-
"app.conversation.privacy": effectiveConversationPrivacy,
|
|
9495
9581
|
"gen_ai.tool.name": toolName,
|
|
9496
9582
|
"gen_ai.tool.description": toolDef.description,
|
|
9583
|
+
"gen_ai.tool.type": "extension",
|
|
9497
9584
|
...toolArgumentsMetadata,
|
|
9498
9585
|
...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
|
|
9499
9586
|
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
@@ -9699,208 +9786,6 @@ function createChannelConfigurationService(storage) {
|
|
|
9699
9786
|
};
|
|
9700
9787
|
}
|
|
9701
9788
|
|
|
9702
|
-
// src/chat/state/conversation.ts
|
|
9703
|
-
function coerceRole(value) {
|
|
9704
|
-
return value === "assistant" || value === "system" || value === "user" ? value : "user";
|
|
9705
|
-
}
|
|
9706
|
-
function coerceAuthor(value) {
|
|
9707
|
-
if (!isRecord(value)) return void 0;
|
|
9708
|
-
const author = {
|
|
9709
|
-
fullName: toOptionalString(value.fullName),
|
|
9710
|
-
userId: toOptionalString(value.userId),
|
|
9711
|
-
userName: toOptionalString(value.userName)
|
|
9712
|
-
};
|
|
9713
|
-
if (typeof value.isBot === "boolean") {
|
|
9714
|
-
author.isBot = value.isBot;
|
|
9715
|
-
}
|
|
9716
|
-
if (!author.fullName && !author.userId && !author.userName && author.isBot === void 0) {
|
|
9717
|
-
return void 0;
|
|
9718
|
-
}
|
|
9719
|
-
return author;
|
|
9720
|
-
}
|
|
9721
|
-
function coerceMessageMeta(value) {
|
|
9722
|
-
if (!isRecord(value)) return void 0;
|
|
9723
|
-
const meta = {};
|
|
9724
|
-
const attachmentCount = toOptionalNumber(value.attachmentCount);
|
|
9725
|
-
if (typeof attachmentCount === "number" && attachmentCount > 0) {
|
|
9726
|
-
meta.attachmentCount = attachmentCount;
|
|
9727
|
-
}
|
|
9728
|
-
if (typeof value.explicitMention === "boolean") {
|
|
9729
|
-
meta.explicitMention = value.explicitMention;
|
|
9730
|
-
}
|
|
9731
|
-
const imageAttachmentCount = toOptionalNumber(value.imageAttachmentCount);
|
|
9732
|
-
if (typeof imageAttachmentCount === "number" && imageAttachmentCount > 0) {
|
|
9733
|
-
meta.imageAttachmentCount = imageAttachmentCount;
|
|
9734
|
-
}
|
|
9735
|
-
if (typeof value.replied === "boolean") {
|
|
9736
|
-
meta.replied = value.replied;
|
|
9737
|
-
}
|
|
9738
|
-
if (typeof value.skippedReason === "string" && value.skippedReason.trim().length > 0) {
|
|
9739
|
-
meta.skippedReason = value.skippedReason;
|
|
9740
|
-
}
|
|
9741
|
-
if (typeof value.slackTs === "string" && value.slackTs.trim().length > 0) {
|
|
9742
|
-
meta.slackTs = value.slackTs;
|
|
9743
|
-
}
|
|
9744
|
-
if (Array.isArray(value.imageFileIds)) {
|
|
9745
|
-
const imageFileIds = value.imageFileIds.filter(
|
|
9746
|
-
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
9747
|
-
);
|
|
9748
|
-
if (imageFileIds.length > 0) {
|
|
9749
|
-
meta.imageFileIds = imageFileIds;
|
|
9750
|
-
}
|
|
9751
|
-
}
|
|
9752
|
-
if (typeof value.imagesHydrated === "boolean") {
|
|
9753
|
-
meta.imagesHydrated = value.imagesHydrated;
|
|
9754
|
-
}
|
|
9755
|
-
if (meta.attachmentCount === void 0 && meta.explicitMention === void 0 && meta.imageAttachmentCount === void 0 && meta.replied === void 0 && meta.skippedReason === void 0 && meta.slackTs === void 0 && meta.imageFileIds === void 0 && meta.imagesHydrated === void 0) {
|
|
9756
|
-
return void 0;
|
|
9757
|
-
}
|
|
9758
|
-
return meta;
|
|
9759
|
-
}
|
|
9760
|
-
function defaultConversationState() {
|
|
9761
|
-
const nowMs = Date.now();
|
|
9762
|
-
return {
|
|
9763
|
-
schemaVersion: 1,
|
|
9764
|
-
messages: [],
|
|
9765
|
-
piMessages: [],
|
|
9766
|
-
compactions: [],
|
|
9767
|
-
backfill: {},
|
|
9768
|
-
processing: {},
|
|
9769
|
-
stats: {
|
|
9770
|
-
estimatedContextTokens: 0,
|
|
9771
|
-
totalMessageCount: 0,
|
|
9772
|
-
compactedMessageCount: 0,
|
|
9773
|
-
updatedAtMs: nowMs
|
|
9774
|
-
},
|
|
9775
|
-
vision: {
|
|
9776
|
-
byFileId: {}
|
|
9777
|
-
}
|
|
9778
|
-
};
|
|
9779
|
-
}
|
|
9780
|
-
function coercePendingAuthState(value) {
|
|
9781
|
-
if (!isRecord(value)) {
|
|
9782
|
-
return void 0;
|
|
9783
|
-
}
|
|
9784
|
-
const kind = value.kind;
|
|
9785
|
-
const provider = toOptionalString(value.provider);
|
|
9786
|
-
const requesterId = toOptionalString(value.requesterId);
|
|
9787
|
-
const scope = toOptionalString(value.scope);
|
|
9788
|
-
const sessionId = toOptionalString(value.sessionId);
|
|
9789
|
-
const linkSentAtMs = toOptionalNumber(value.linkSentAtMs);
|
|
9790
|
-
if (kind !== "mcp" && kind !== "plugin" || !provider || !requesterId || !sessionId || typeof linkSentAtMs !== "number") {
|
|
9791
|
-
return void 0;
|
|
9792
|
-
}
|
|
9793
|
-
return {
|
|
9794
|
-
kind,
|
|
9795
|
-
provider,
|
|
9796
|
-
requesterId,
|
|
9797
|
-
...scope ? { scope } : {},
|
|
9798
|
-
sessionId,
|
|
9799
|
-
linkSentAtMs
|
|
9800
|
-
};
|
|
9801
|
-
}
|
|
9802
|
-
function coerceThreadConversationState(value) {
|
|
9803
|
-
if (!isRecord(value)) {
|
|
9804
|
-
return defaultConversationState();
|
|
9805
|
-
}
|
|
9806
|
-
const root = value;
|
|
9807
|
-
const rawConversation = isRecord(root.conversation) ? root.conversation : {};
|
|
9808
|
-
const base = defaultConversationState();
|
|
9809
|
-
const rawMessages = Array.isArray(rawConversation.messages) ? rawConversation.messages : [];
|
|
9810
|
-
const messages = [];
|
|
9811
|
-
for (const item of rawMessages) {
|
|
9812
|
-
if (!isRecord(item)) continue;
|
|
9813
|
-
const id = toOptionalString(item.id);
|
|
9814
|
-
const text = toOptionalString(item.text);
|
|
9815
|
-
const createdAtMs = toOptionalNumber(item.createdAtMs);
|
|
9816
|
-
if (!id || !text || !createdAtMs) continue;
|
|
9817
|
-
messages.push({
|
|
9818
|
-
id,
|
|
9819
|
-
role: coerceRole(item.role),
|
|
9820
|
-
text,
|
|
9821
|
-
createdAtMs,
|
|
9822
|
-
author: coerceAuthor(item.author),
|
|
9823
|
-
meta: coerceMessageMeta(item.meta)
|
|
9824
|
-
});
|
|
9825
|
-
}
|
|
9826
|
-
const rawCompactions = Array.isArray(rawConversation.compactions) ? rawConversation.compactions : [];
|
|
9827
|
-
const compactions = [];
|
|
9828
|
-
for (const item of rawCompactions) {
|
|
9829
|
-
if (!isRecord(item)) continue;
|
|
9830
|
-
const id = toOptionalString(item.id);
|
|
9831
|
-
const summary = toOptionalString(item.summary);
|
|
9832
|
-
const createdAtMs = toOptionalNumber(item.createdAtMs);
|
|
9833
|
-
if (!id || !summary || !createdAtMs) continue;
|
|
9834
|
-
const coveredMessageIds = Array.isArray(item.coveredMessageIds) ? item.coveredMessageIds.filter(
|
|
9835
|
-
(entry) => typeof entry === "string" && entry.length > 0
|
|
9836
|
-
) : [];
|
|
9837
|
-
compactions.push({
|
|
9838
|
-
id,
|
|
9839
|
-
summary,
|
|
9840
|
-
createdAtMs,
|
|
9841
|
-
coveredMessageIds
|
|
9842
|
-
});
|
|
9843
|
-
}
|
|
9844
|
-
const rawBackfill = isRecord(rawConversation.backfill) ? rawConversation.backfill : {};
|
|
9845
|
-
const backfill = {
|
|
9846
|
-
completedAtMs: toOptionalNumber(rawBackfill.completedAtMs),
|
|
9847
|
-
source: rawBackfill.source === "recent_messages" || rawBackfill.source === "thread_fetch" ? rawBackfill.source : void 0
|
|
9848
|
-
};
|
|
9849
|
-
const rawProcessing = isRecord(rawConversation.processing) ? rawConversation.processing : {};
|
|
9850
|
-
const processing = {
|
|
9851
|
-
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
9852
|
-
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
9853
|
-
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
9854
|
-
};
|
|
9855
|
-
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
9856
|
-
const stats = {
|
|
9857
|
-
estimatedContextTokens: toOptionalNumber(rawStats.estimatedContextTokens) ?? base.stats.estimatedContextTokens,
|
|
9858
|
-
totalMessageCount: toOptionalNumber(rawStats.totalMessageCount) ?? messages.length,
|
|
9859
|
-
compactedMessageCount: toOptionalNumber(rawStats.compactedMessageCount) ?? 0,
|
|
9860
|
-
updatedAtMs: toOptionalNumber(rawStats.updatedAtMs) ?? base.stats.updatedAtMs
|
|
9861
|
-
};
|
|
9862
|
-
const rawVision = isRecord(rawConversation.vision) ? rawConversation.vision : {};
|
|
9863
|
-
const rawVisionByFileId = isRecord(rawVision.byFileId) ? rawVision.byFileId : {};
|
|
9864
|
-
const byFileId = {};
|
|
9865
|
-
for (const [fileId, value2] of Object.entries(rawVisionByFileId)) {
|
|
9866
|
-
if (typeof fileId !== "string" || fileId.trim().length === 0) continue;
|
|
9867
|
-
if (!isRecord(value2)) continue;
|
|
9868
|
-
const summary = toOptionalString(value2.summary);
|
|
9869
|
-
const analyzedAtMs = toOptionalNumber(value2.analyzedAtMs);
|
|
9870
|
-
if (!summary || !analyzedAtMs) continue;
|
|
9871
|
-
byFileId[fileId] = {
|
|
9872
|
-
summary,
|
|
9873
|
-
analyzedAtMs
|
|
9874
|
-
};
|
|
9875
|
-
}
|
|
9876
|
-
return {
|
|
9877
|
-
schemaVersion: 1,
|
|
9878
|
-
messages,
|
|
9879
|
-
piMessages: Array.isArray(rawConversation.piMessages) ? rawConversation.piMessages : [],
|
|
9880
|
-
compactions,
|
|
9881
|
-
backfill,
|
|
9882
|
-
processing,
|
|
9883
|
-
stats,
|
|
9884
|
-
vision: {
|
|
9885
|
-
backfillCompletedAtMs: toOptionalNumber(rawVision.backfillCompletedAtMs),
|
|
9886
|
-
byFileId
|
|
9887
|
-
}
|
|
9888
|
-
};
|
|
9889
|
-
}
|
|
9890
|
-
function buildConversationStatePatch(conversation) {
|
|
9891
|
-
return {
|
|
9892
|
-
conversation: {
|
|
9893
|
-
...conversation,
|
|
9894
|
-
schemaVersion: 1,
|
|
9895
|
-
stats: {
|
|
9896
|
-
...conversation.stats,
|
|
9897
|
-
totalMessageCount: conversation.messages.length,
|
|
9898
|
-
updatedAtMs: Date.now()
|
|
9899
|
-
}
|
|
9900
|
-
}
|
|
9901
|
-
};
|
|
9902
|
-
}
|
|
9903
|
-
|
|
9904
9789
|
// src/chat/state/artifacts.ts
|
|
9905
9790
|
function coerceThreadArtifactsState(value) {
|
|
9906
9791
|
if (!value || typeof value !== "object") {
|
|
@@ -10664,7 +10549,7 @@ function addAgentTurnUsage(...usages) {
|
|
|
10664
10549
|
}
|
|
10665
10550
|
|
|
10666
10551
|
// src/chat/services/turn-session-record.ts
|
|
10667
|
-
var
|
|
10552
|
+
var AGENT_CONTINUE_MAX_SLICES = 48;
|
|
10668
10553
|
function logSessionRecordError(error, eventName, args, attributes, message) {
|
|
10669
10554
|
logException(
|
|
10670
10555
|
error,
|
|
@@ -10740,7 +10625,10 @@ async function persistRunningSessionRecord(args) {
|
|
|
10740
10625
|
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
10741
10626
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
10742
10627
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
10743
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
10628
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
10629
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
10630
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
10631
|
+
} : {}
|
|
10744
10632
|
});
|
|
10745
10633
|
return true;
|
|
10746
10634
|
} catch (recordError) {
|
|
@@ -10781,7 +10669,10 @@ async function persistCompletedSessionRecord(args) {
|
|
|
10781
10669
|
...args.surface ?? latestSessionRecord?.surface ? { surface: args.surface ?? latestSessionRecord?.surface } : {},
|
|
10782
10670
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
10783
10671
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
10784
|
-
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
10672
|
+
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
|
|
10673
|
+
...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
10674
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex
|
|
10675
|
+
} : {}
|
|
10785
10676
|
});
|
|
10786
10677
|
} catch (recordError) {
|
|
10787
10678
|
logSessionRecordError(
|
|
@@ -10869,7 +10760,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
10869
10760
|
latestSessionRecord?.cumulativeUsage,
|
|
10870
10761
|
args.currentUsage
|
|
10871
10762
|
);
|
|
10872
|
-
if (nextSliceId >
|
|
10763
|
+
if (nextSliceId > AGENT_CONTINUE_MAX_SLICES) {
|
|
10873
10764
|
return await upsertAgentTurnSessionRecord({
|
|
10874
10765
|
...args.channelName ?? latestSessionRecord?.channelName ? {
|
|
10875
10766
|
channelName: args.channelName ?? latestSessionRecord?.channelName
|
|
@@ -10888,7 +10779,7 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
10888
10779
|
...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
|
|
10889
10780
|
resumeReason: "timeout",
|
|
10890
10781
|
resumedFromSliceId: latestSessionRecord?.resumedFromSliceId,
|
|
10891
|
-
errorMessage: `
|
|
10782
|
+
errorMessage: `Agent continuation exceeded slice limit (${AGENT_CONTINUE_MAX_SLICES})`,
|
|
10892
10783
|
...args.requester ?? latestSessionRecord?.requester ? { requester: args.requester ?? latestSessionRecord?.requester } : {},
|
|
10893
10784
|
...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {}
|
|
10894
10785
|
});
|
|
@@ -10914,13 +10805,13 @@ async function persistTimeoutSessionRecord(args) {
|
|
|
10914
10805
|
} catch (recordError) {
|
|
10915
10806
|
logSessionRecordError(
|
|
10916
10807
|
recordError,
|
|
10917
|
-
"
|
|
10808
|
+
"agent_continue_session_record_failed",
|
|
10918
10809
|
args,
|
|
10919
10810
|
{
|
|
10920
10811
|
"app.ai.resume_from_slice_id": args.currentSliceId,
|
|
10921
10812
|
"app.ai.resume_next_slice_id": nextSliceId
|
|
10922
10813
|
},
|
|
10923
|
-
"Failed to persist
|
|
10814
|
+
"Failed to persist session record before scheduling agent continuation"
|
|
10924
10815
|
);
|
|
10925
10816
|
return void 0;
|
|
10926
10817
|
}
|
|
@@ -11114,13 +11005,13 @@ var StateBackedMcpOAuthClientProvider = class {
|
|
|
11114
11005
|
if (!this.sessionContext) {
|
|
11115
11006
|
throw new Error(`Unknown MCP auth session: ${this.authSessionId}`);
|
|
11116
11007
|
}
|
|
11117
|
-
const
|
|
11008
|
+
const now2 = Date.now();
|
|
11118
11009
|
const nextSession = {
|
|
11119
11010
|
authSessionId: this.authSessionId,
|
|
11120
11011
|
...this.sessionContext,
|
|
11121
11012
|
...patch,
|
|
11122
|
-
createdAtMs:
|
|
11123
|
-
updatedAtMs:
|
|
11013
|
+
createdAtMs: now2,
|
|
11014
|
+
updatedAtMs: now2
|
|
11124
11015
|
};
|
|
11125
11016
|
await putMcpAuthSession(nextSession);
|
|
11126
11017
|
return nextSession;
|
|
@@ -11158,7 +11049,7 @@ async function createMcpOAuthClientProvider(input) {
|
|
|
11158
11049
|
input.provider
|
|
11159
11050
|
);
|
|
11160
11051
|
const reusableSession = existingSession && existingSession.conversationId === input.conversationId && existingSession.sessionId === input.sessionId ? existingSession : void 0;
|
|
11161
|
-
const
|
|
11052
|
+
const now2 = Date.now();
|
|
11162
11053
|
const authSessionId = reusableSession?.authSessionId ?? randomUUID3();
|
|
11163
11054
|
await putMcpAuthSession({
|
|
11164
11055
|
authSessionId,
|
|
@@ -11175,8 +11066,8 @@ async function createMcpOAuthClientProvider(input) {
|
|
|
11175
11066
|
...input.artifactState ? { artifactState: input.artifactState } : {},
|
|
11176
11067
|
...reusableSession?.authorizationUrl ? { authorizationUrl: reusableSession.authorizationUrl } : {},
|
|
11177
11068
|
...reusableSession?.codeVerifier ? { codeVerifier: reusableSession.codeVerifier } : {},
|
|
11178
|
-
createdAtMs: reusableSession?.createdAtMs ??
|
|
11179
|
-
updatedAtMs:
|
|
11069
|
+
createdAtMs: reusableSession?.createdAtMs ?? now2,
|
|
11070
|
+
updatedAtMs: now2
|
|
11180
11071
|
});
|
|
11181
11072
|
return new StateBackedMcpOAuthClientProvider(
|
|
11182
11073
|
authSessionId,
|
|
@@ -11415,18 +11306,15 @@ function extractSliceUsage(messages, beforeMessageCount) {
|
|
|
11415
11306
|
);
|
|
11416
11307
|
return hasAgentTurnUsage(usage) ? usage : void 0;
|
|
11417
11308
|
}
|
|
11418
|
-
function requesterFromContext(
|
|
11419
|
-
const identity = actorRequesterFromContext(
|
|
11420
|
-
|
|
11421
|
-
...identity?.email ? { email: identity.email } : {},
|
|
11422
|
-
...identity?.fullName ? { fullName: identity.fullName } : {},
|
|
11423
|
-
...identity?.userId ? { slackUserId: identity.userId } : {},
|
|
11424
|
-
...identity?.userName ? { slackUserName: identity.userName } : {}
|
|
11425
|
-
};
|
|
11426
|
-
return Object.keys(agentRequester).length > 0 ? agentRequester : void 0;
|
|
11309
|
+
function requesterFromContext(context) {
|
|
11310
|
+
const identity = actorRequesterFromContext(context);
|
|
11311
|
+
return identity ? toStoredSlackRequester(identity) : void 0;
|
|
11427
11312
|
}
|
|
11428
|
-
function actorRequesterFromContext(
|
|
11429
|
-
return
|
|
11313
|
+
function actorRequesterFromContext(context) {
|
|
11314
|
+
return createRequester(context.requester, {
|
|
11315
|
+
teamId: context.destination?.teamId ?? context.correlation?.teamId ?? context.requester?.teamId,
|
|
11316
|
+
userId: context.correlation?.requesterId
|
|
11317
|
+
});
|
|
11430
11318
|
}
|
|
11431
11319
|
function surfaceFromContext(context) {
|
|
11432
11320
|
if (context.surface) {
|
|
@@ -11544,6 +11432,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
11544
11432
|
let timeoutResumeSliceId = 1;
|
|
11545
11433
|
let timeoutResumeMessages = [];
|
|
11546
11434
|
let beforeMessageCount = 0;
|
|
11435
|
+
let turnStartMessageIndex;
|
|
11547
11436
|
let lastKnownSandboxId = context.sandbox?.sandboxId;
|
|
11548
11437
|
let lastKnownSandboxDependencyProfileHash = context.sandbox?.sandboxDependencyProfileHash;
|
|
11549
11438
|
let loadedSkillNamesForResume = [];
|
|
@@ -11556,14 +11445,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
11556
11445
|
let inputCommitted = false;
|
|
11557
11446
|
let turnUsage;
|
|
11558
11447
|
let thinkingSelection;
|
|
11559
|
-
const requester = requesterFromContext(
|
|
11560
|
-
|
|
11561
|
-
context.correlation?.requesterId
|
|
11562
|
-
);
|
|
11563
|
-
const actorRequester = actorRequesterFromContext(
|
|
11564
|
-
context.requester,
|
|
11565
|
-
context.correlation?.requesterId
|
|
11566
|
-
);
|
|
11448
|
+
const requester = requesterFromContext(context);
|
|
11449
|
+
const actorRequester = actorRequesterFromContext(context);
|
|
11567
11450
|
const surface = surfaceFromContext(context);
|
|
11568
11451
|
const credentialActor = context.credentialContext?.actor;
|
|
11569
11452
|
const credentialActorLogContext = credentialActor ? {
|
|
@@ -12094,7 +11977,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12094
11977
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12095
11978
|
logContext: sessionRecordLogContext,
|
|
12096
11979
|
requester,
|
|
12097
|
-
...surface ? { surface } : {}
|
|
11980
|
+
...surface ? { surface } : {},
|
|
11981
|
+
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
|
|
12098
11982
|
});
|
|
12099
11983
|
if (!persisted) {
|
|
12100
11984
|
return false;
|
|
@@ -12229,10 +12113,14 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12229
12113
|
existingSessionRecord.piMessages,
|
|
12230
12114
|
turnContextPrompt
|
|
12231
12115
|
) : existingSessionRecord.piMessages;
|
|
12116
|
+
turnStartMessageIndex = existingSessionRecord.turnStartMessageIndex;
|
|
12232
12117
|
} else if (context.piMessages && context.piMessages.length > 0) {
|
|
12233
12118
|
agent.state.messages = [...context.piMessages];
|
|
12234
12119
|
}
|
|
12235
12120
|
beforeMessageCount = agent.state.messages.length;
|
|
12121
|
+
if (!resumedFromSessionRecord) {
|
|
12122
|
+
turnStartMessageIndex = beforeMessageCount;
|
|
12123
|
+
}
|
|
12236
12124
|
await withSpan(
|
|
12237
12125
|
`invoke_agent ${botConfig.modelId}`,
|
|
12238
12126
|
"gen_ai.invoke_agent",
|
|
@@ -12407,7 +12295,8 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12407
12295
|
loadedSkillNames: loadedSkillNamesForResume,
|
|
12408
12296
|
logContext: sessionRecordLogContext,
|
|
12409
12297
|
requester,
|
|
12410
|
-
...surface ? { surface } : {}
|
|
12298
|
+
...surface ? { surface } : {},
|
|
12299
|
+
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {}
|
|
12411
12300
|
});
|
|
12412
12301
|
}
|
|
12413
12302
|
return buildTurnResult({
|
|
@@ -12478,7 +12367,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12478
12367
|
}
|
|
12479
12368
|
if (sessionRecord.state === "awaiting_resume") {
|
|
12480
12369
|
throw new RetryableTurnError(
|
|
12481
|
-
"
|
|
12370
|
+
"agent_continue",
|
|
12482
12371
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${sessionRecord.sliceId} version=${sessionRecord.version}`,
|
|
12483
12372
|
{
|
|
12484
12373
|
conversationId: timeoutResumeConversationId,
|
|
@@ -13871,7 +13760,7 @@ async function runAgentDispatchSlice(callback, deps = {}) {
|
|
|
13871
13760
|
});
|
|
13872
13761
|
return;
|
|
13873
13762
|
}
|
|
13874
|
-
if (isRetryableTurnError(error, "
|
|
13763
|
+
if (isRetryableTurnError(error, "agent_continue")) {
|
|
13875
13764
|
const version = error.metadata?.version;
|
|
13876
13765
|
if (typeof version === "number") {
|
|
13877
13766
|
const awaiting = await markDispatch({
|
|
@@ -13932,828 +13821,36 @@ async function POST(request, waitUntil) {
|
|
|
13932
13821
|
}
|
|
13933
13822
|
|
|
13934
13823
|
// src/handlers/heartbeat.ts
|
|
13935
|
-
import { timingSafeEqual as
|
|
13824
|
+
import { timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
13936
13825
|
|
|
13937
|
-
// src/chat/
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
13942
|
-
var CONVERSATION_WORK_PREFIX = "junior:conversation-work";
|
|
13943
|
-
var CONVERSATION_WORK_SCHEMA_VERSION = 1;
|
|
13944
|
-
var CONVERSATION_WORK_INDEX_MAX_LENGTH = 1e4;
|
|
13945
|
-
var CONVERSATION_WORK_INDEX_LOCK_TTL_MS = 1e4;
|
|
13946
|
-
var CONVERSATION_WORK_INDEX_LOCK_WAIT_MS = 2e3;
|
|
13947
|
-
var CONVERSATION_WORK_INDEX_LOCK_RETRY_MS = 25;
|
|
13948
|
-
var CONVERSATION_WORK_MUTATION_LOCK_TTL_MS = 1e4;
|
|
13949
|
-
var CONVERSATION_WORK_MUTATION_WAIT_MS = 1e4;
|
|
13950
|
-
var CONVERSATION_WORK_MUTATION_RETRY_MS = 25;
|
|
13951
|
-
var CONVERSATION_WORK_LEASE_TTL_MS = 9e4;
|
|
13952
|
-
var CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15e3;
|
|
13953
|
-
var CONVERSATION_WORK_STALE_ENQUEUE_MS = 6e4;
|
|
13954
|
-
var CONVERSATION_WORK_MAX_CONSECUTIVE_FAILURES = 5;
|
|
13955
|
-
function duplicateInboundNudgeIdempotencyKey(message, nowMs) {
|
|
13956
|
-
return `duplicate:${message.conversationId}:${message.inboundMessageId}:${nowMs}`;
|
|
13957
|
-
}
|
|
13958
|
-
function hasRecentEnqueueMarker(state, nowMs) {
|
|
13959
|
-
return typeof state.lastEnqueuedAtMs === "number" && state.lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > nowMs;
|
|
13960
|
-
}
|
|
13961
|
-
function stateKey(conversationId) {
|
|
13962
|
-
return `${CONVERSATION_WORK_PREFIX}:state:${conversationId}`;
|
|
13963
|
-
}
|
|
13964
|
-
function indexKey() {
|
|
13965
|
-
return `${CONVERSATION_WORK_PREFIX}:index`;
|
|
13966
|
-
}
|
|
13967
|
-
function indexLockKey() {
|
|
13968
|
-
return `${CONVERSATION_WORK_PREFIX}:index:lock`;
|
|
13969
|
-
}
|
|
13970
|
-
function mutationLockKey(conversationId) {
|
|
13971
|
-
return `${CONVERSATION_WORK_PREFIX}:mutation:${conversationId}`;
|
|
13972
|
-
}
|
|
13973
|
-
function now() {
|
|
13974
|
-
return Date.now();
|
|
13975
|
-
}
|
|
13976
|
-
function uniqueStrings(values) {
|
|
13977
|
-
return [
|
|
13978
|
-
...new Set(
|
|
13979
|
-
values.filter((value) => {
|
|
13980
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
13981
|
-
})
|
|
13982
|
-
)
|
|
13983
|
-
];
|
|
13984
|
-
}
|
|
13985
|
-
function compareMessages(left, right) {
|
|
13986
|
-
return left.createdAtMs - right.createdAtMs || left.receivedAtMs - right.receivedAtMs || left.inboundMessageId.localeCompare(right.inboundMessageId);
|
|
13987
|
-
}
|
|
13988
|
-
function normalizeSource(value) {
|
|
13989
|
-
if (value === "plugin" || value === "scheduler" || value === "slack") {
|
|
13990
|
-
return value;
|
|
13991
|
-
}
|
|
13992
|
-
return void 0;
|
|
13993
|
-
}
|
|
13994
|
-
function normalizeMetadata2(value) {
|
|
13995
|
-
if (!isRecord(value)) {
|
|
13996
|
-
return void 0;
|
|
13997
|
-
}
|
|
13998
|
-
return value;
|
|
13999
|
-
}
|
|
14000
|
-
function normalizeInput(value) {
|
|
14001
|
-
if (!isRecord(value)) {
|
|
14002
|
-
return void 0;
|
|
14003
|
-
}
|
|
14004
|
-
const text = toOptionalString(value.text);
|
|
14005
|
-
if (!text) {
|
|
14006
|
-
return void 0;
|
|
14007
|
-
}
|
|
14008
|
-
return {
|
|
14009
|
-
text,
|
|
14010
|
-
authorId: toOptionalString(value.authorId),
|
|
14011
|
-
attachments: Array.isArray(value.attachments) ? [...value.attachments] : void 0,
|
|
14012
|
-
metadata: normalizeMetadata2(value.metadata)
|
|
14013
|
-
};
|
|
13826
|
+
// src/chat/task-execution/heartbeat.ts
|
|
13827
|
+
var DEFAULT_RECOVERY_LIMIT = 25;
|
|
13828
|
+
function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
|
|
13829
|
+
return `heartbeat:${reason}:${conversationId}:${nowMs}`;
|
|
14014
13830
|
}
|
|
14015
|
-
function
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
}
|
|
14029
|
-
return {
|
|
14030
|
-
conversationId,
|
|
14031
|
-
destination,
|
|
14032
|
-
inboundMessageId,
|
|
14033
|
-
source,
|
|
14034
|
-
createdAtMs,
|
|
14035
|
-
receivedAtMs,
|
|
14036
|
-
input,
|
|
14037
|
-
injectedAtMs: toOptionalNumber(value.injectedAtMs)
|
|
14038
|
-
};
|
|
13831
|
+
async function sendRecoveryNudge(args) {
|
|
13832
|
+
await args.queue.send(
|
|
13833
|
+
{
|
|
13834
|
+
conversationId: args.conversationId,
|
|
13835
|
+
destination: args.destination
|
|
13836
|
+
},
|
|
13837
|
+
{ idempotencyKey: args.idempotencyKey }
|
|
13838
|
+
);
|
|
13839
|
+
await markConversationWorkEnqueued({
|
|
13840
|
+
conversationId: args.conversationId,
|
|
13841
|
+
nowMs: args.nowMs,
|
|
13842
|
+
state: args.state
|
|
13843
|
+
});
|
|
14039
13844
|
}
|
|
14040
|
-
function
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
const leaseToken = toOptionalString(value.leaseToken);
|
|
14045
|
-
const acquiredAtMs = toOptionalNumber(value.acquiredAtMs);
|
|
14046
|
-
const lastCheckInAtMs = toOptionalNumber(value.lastCheckInAtMs);
|
|
14047
|
-
const leaseExpiresAtMs = toOptionalNumber(value.leaseExpiresAtMs);
|
|
14048
|
-
if (!leaseToken || typeof acquiredAtMs !== "number" || typeof lastCheckInAtMs !== "number" || typeof leaseExpiresAtMs !== "number") {
|
|
14049
|
-
return void 0;
|
|
14050
|
-
}
|
|
14051
|
-
return {
|
|
14052
|
-
leaseToken,
|
|
14053
|
-
acquiredAtMs,
|
|
14054
|
-
lastCheckInAtMs,
|
|
14055
|
-
leaseExpiresAtMs
|
|
13845
|
+
async function recoverConversationWork(args) {
|
|
13846
|
+
const result = {
|
|
13847
|
+
expiredLeaseCount: 0,
|
|
13848
|
+
pendingCount: 0
|
|
14056
13849
|
};
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
if (!isRecord(value) || value.schemaVersion !== CONVERSATION_WORK_SCHEMA_VERSION) {
|
|
14060
|
-
return void 0;
|
|
14061
|
-
}
|
|
14062
|
-
const storedConversationId = toOptionalString(value.conversationId);
|
|
14063
|
-
const destination = parseDestination(value.destination);
|
|
14064
|
-
const updatedAtMs = toOptionalNumber(value.updatedAtMs);
|
|
14065
|
-
if (storedConversationId !== conversationId || !destination || typeof updatedAtMs !== "number") {
|
|
14066
|
-
return void 0;
|
|
14067
|
-
}
|
|
14068
|
-
const messages = Array.isArray(value.messages) ? value.messages.map(normalizeMessage).filter((message) => Boolean(message)).filter((message) => message.conversationId === conversationId).sort(compareMessages) : [];
|
|
14069
|
-
return {
|
|
14070
|
-
schemaVersion: CONVERSATION_WORK_SCHEMA_VERSION,
|
|
14071
|
-
conversationId,
|
|
14072
|
-
destination,
|
|
14073
|
-
messages,
|
|
14074
|
-
needsRun: value.needsRun === true,
|
|
14075
|
-
updatedAtMs,
|
|
14076
|
-
consecutiveFailureCount: toOptionalNumber(value.consecutiveFailureCount) ?? 0,
|
|
14077
|
-
lastEnqueuedAtMs: toOptionalNumber(value.lastEnqueuedAtMs),
|
|
14078
|
-
lastFailureAtMs: toOptionalNumber(value.lastFailureAtMs),
|
|
14079
|
-
lease: normalizeLease(value.lease),
|
|
14080
|
-
terminallyFailedAtMs: toOptionalNumber(value.terminallyFailedAtMs)
|
|
14081
|
-
};
|
|
14082
|
-
}
|
|
14083
|
-
function emptyWorkState(args) {
|
|
14084
|
-
return {
|
|
14085
|
-
schemaVersion: CONVERSATION_WORK_SCHEMA_VERSION,
|
|
14086
|
-
conversationId: args.conversationId,
|
|
14087
|
-
consecutiveFailureCount: 0,
|
|
14088
|
-
destination: args.destination,
|
|
14089
|
-
messages: [],
|
|
14090
|
-
needsRun: false,
|
|
14091
|
-
updatedAtMs: args.nowMs
|
|
14092
|
-
};
|
|
14093
|
-
}
|
|
14094
|
-
function isLeaseActive(lease, nowMs) {
|
|
14095
|
-
return Boolean(lease && lease.leaseExpiresAtMs > nowMs);
|
|
14096
|
-
}
|
|
14097
|
-
function pendingMessages(state) {
|
|
14098
|
-
return state.messages.filter((message) => message.injectedAtMs === void 0).sort(compareMessages);
|
|
14099
|
-
}
|
|
14100
|
-
function shouldKeepIndexed(state) {
|
|
14101
|
-
if (state.terminallyFailedAtMs !== void 0) {
|
|
14102
|
-
return false;
|
|
14103
|
-
}
|
|
14104
|
-
return state.needsRun || Boolean(state.lease) || pendingMessages(state).length > 0;
|
|
14105
|
-
}
|
|
14106
|
-
async function getConnectedState(stateAdapter) {
|
|
14107
|
-
const state = stateAdapter ?? getStateAdapter();
|
|
14108
|
-
await state.connect();
|
|
14109
|
-
return state;
|
|
14110
|
-
}
|
|
14111
|
-
async function sleep3(ms) {
|
|
14112
|
-
await new Promise((resolve) => {
|
|
14113
|
-
const timer = setTimeout(resolve, ms);
|
|
14114
|
-
timer.unref?.();
|
|
14115
|
-
});
|
|
14116
|
-
}
|
|
14117
|
-
async function withIndexLock(state, callback) {
|
|
14118
|
-
const startedAtMs = now();
|
|
14119
|
-
let lock;
|
|
14120
|
-
while (true) {
|
|
14121
|
-
lock = await state.acquireLock(
|
|
14122
|
-
indexLockKey(),
|
|
14123
|
-
CONVERSATION_WORK_INDEX_LOCK_TTL_MS
|
|
14124
|
-
);
|
|
14125
|
-
if (lock) {
|
|
14126
|
-
break;
|
|
14127
|
-
}
|
|
14128
|
-
if (now() - startedAtMs >= CONVERSATION_WORK_INDEX_LOCK_WAIT_MS) {
|
|
14129
|
-
throw new Error("Could not acquire conversation work index lock");
|
|
14130
|
-
}
|
|
14131
|
-
await sleep3(CONVERSATION_WORK_INDEX_LOCK_RETRY_MS);
|
|
14132
|
-
}
|
|
14133
|
-
try {
|
|
14134
|
-
return await callback();
|
|
14135
|
-
} finally {
|
|
14136
|
-
await state.releaseLock(lock);
|
|
14137
|
-
}
|
|
14138
|
-
}
|
|
14139
|
-
async function addToIndex(state, conversationId) {
|
|
14140
|
-
await withIndexLock(state, async () => {
|
|
14141
|
-
const existing = uniqueStrings(
|
|
14142
|
-
await state.get(indexKey()) ?? []
|
|
14143
|
-
);
|
|
14144
|
-
if (existing.includes(conversationId)) {
|
|
14145
|
-
return;
|
|
14146
|
-
}
|
|
14147
|
-
const indexed = [...existing, conversationId];
|
|
14148
|
-
const remove = /* @__PURE__ */ new Set();
|
|
14149
|
-
for (const id of indexed) {
|
|
14150
|
-
if (indexed.length - remove.size <= CONVERSATION_WORK_INDEX_MAX_LENGTH) {
|
|
14151
|
-
break;
|
|
14152
|
-
}
|
|
14153
|
-
const work = await readWorkState(state, id);
|
|
14154
|
-
if (!work || !shouldKeepIndexed(work)) {
|
|
14155
|
-
remove.add(id);
|
|
14156
|
-
}
|
|
14157
|
-
}
|
|
14158
|
-
await state.set(
|
|
14159
|
-
indexKey(),
|
|
14160
|
-
indexed.filter((id) => !remove.has(id)),
|
|
14161
|
-
JUNIOR_THREAD_STATE_TTL_MS
|
|
14162
|
-
);
|
|
14163
|
-
});
|
|
14164
|
-
}
|
|
14165
|
-
async function removeFromIndex(state, conversationId) {
|
|
14166
|
-
await withIndexLock(state, async () => {
|
|
14167
|
-
const existing = uniqueStrings(
|
|
14168
|
-
await state.get(indexKey()) ?? []
|
|
14169
|
-
);
|
|
14170
|
-
const next = existing.filter((id) => id !== conversationId);
|
|
14171
|
-
if (next.length === existing.length) {
|
|
14172
|
-
return;
|
|
14173
|
-
}
|
|
14174
|
-
await state.set(indexKey(), next, JUNIOR_THREAD_STATE_TTL_MS);
|
|
14175
|
-
});
|
|
14176
|
-
}
|
|
14177
|
-
async function acquireMutationLock(state, conversationId) {
|
|
14178
|
-
const startedAtMs = now();
|
|
14179
|
-
while (true) {
|
|
14180
|
-
const lock = await state.acquireLock(
|
|
14181
|
-
mutationLockKey(conversationId),
|
|
14182
|
-
CONVERSATION_WORK_MUTATION_LOCK_TTL_MS
|
|
14183
|
-
);
|
|
14184
|
-
if (lock) {
|
|
14185
|
-
return lock;
|
|
14186
|
-
}
|
|
14187
|
-
if (now() - startedAtMs >= CONVERSATION_WORK_MUTATION_WAIT_MS) {
|
|
14188
|
-
throw new Error(
|
|
14189
|
-
`Could not acquire conversation work mutation lock for ${conversationId}`
|
|
14190
|
-
);
|
|
14191
|
-
}
|
|
14192
|
-
await sleep3(CONVERSATION_WORK_MUTATION_RETRY_MS);
|
|
14193
|
-
}
|
|
14194
|
-
}
|
|
14195
|
-
async function withConversationMutation(args, callback) {
|
|
14196
|
-
const state = await getConnectedState(args.state);
|
|
14197
|
-
const lock = await acquireMutationLock(state, args.conversationId);
|
|
14198
|
-
try {
|
|
14199
|
-
return await callback(state);
|
|
14200
|
-
} finally {
|
|
14201
|
-
await state.releaseLock(lock);
|
|
14202
|
-
}
|
|
14203
|
-
}
|
|
14204
|
-
async function readWorkState(state, conversationId) {
|
|
14205
|
-
const raw = await state.get(stateKey(conversationId));
|
|
14206
|
-
if (raw == null) {
|
|
14207
|
-
return void 0;
|
|
14208
|
-
}
|
|
14209
|
-
const work = normalizeWorkState(conversationId, raw);
|
|
14210
|
-
if (!work) {
|
|
14211
|
-
throw new Error(`Conversation work state is invalid for ${conversationId}`);
|
|
14212
|
-
}
|
|
14213
|
-
return work;
|
|
14214
|
-
}
|
|
14215
|
-
async function writeWorkState(state, work) {
|
|
14216
|
-
await state.set(
|
|
14217
|
-
stateKey(work.conversationId),
|
|
14218
|
-
work,
|
|
14219
|
-
JUNIOR_THREAD_STATE_TTL_MS
|
|
14220
|
-
);
|
|
14221
|
-
if (shouldKeepIndexed(work)) {
|
|
14222
|
-
await addToIndex(state, work.conversationId);
|
|
14223
|
-
} else {
|
|
14224
|
-
await removeFromIndex(state, work.conversationId);
|
|
14225
|
-
}
|
|
14226
|
-
}
|
|
14227
|
-
function hasRunnableWork(state) {
|
|
14228
|
-
if (state.terminallyFailedAtMs !== void 0) {
|
|
14229
|
-
return false;
|
|
14230
|
-
}
|
|
14231
|
-
return state.needsRun || pendingMessages(state).length > 0;
|
|
14232
|
-
}
|
|
14233
|
-
function assertSameConversationDestination(args) {
|
|
14234
|
-
if (sameDestination(args.current, args.next)) {
|
|
14235
|
-
return;
|
|
14236
|
-
}
|
|
14237
|
-
throw new Error(
|
|
14238
|
-
`Conversation work destination changed for ${args.conversationId}`
|
|
14239
|
-
);
|
|
14240
|
-
}
|
|
14241
|
-
async function getConversationWorkState(args) {
|
|
14242
|
-
const state = await getConnectedState(args.state);
|
|
14243
|
-
return await readWorkState(state, args.conversationId);
|
|
14244
|
-
}
|
|
14245
|
-
function countPendingConversationMessages(state) {
|
|
14246
|
-
return pendingMessages(state).length;
|
|
14247
|
-
}
|
|
14248
|
-
function hasRunnableConversationWork(state) {
|
|
14249
|
-
return hasRunnableWork(state);
|
|
14250
|
-
}
|
|
14251
|
-
async function appendInboundMessage(args) {
|
|
14252
|
-
const nowMs = args.nowMs ?? now();
|
|
14253
|
-
return await withConversationMutation(
|
|
14254
|
-
{ conversationId: args.message.conversationId, state: args.state },
|
|
14255
|
-
async (state) => {
|
|
14256
|
-
const current = await readWorkState(state, args.message.conversationId) ?? emptyWorkState({
|
|
14257
|
-
conversationId: args.message.conversationId,
|
|
14258
|
-
destination: args.message.destination,
|
|
14259
|
-
nowMs
|
|
14260
|
-
});
|
|
14261
|
-
assertSameConversationDestination({
|
|
14262
|
-
conversationId: args.message.conversationId,
|
|
14263
|
-
current: current.destination,
|
|
14264
|
-
next: args.message.destination
|
|
14265
|
-
});
|
|
14266
|
-
const existing = current.messages.find(
|
|
14267
|
-
(message) => message.inboundMessageId === args.message.inboundMessageId
|
|
14268
|
-
);
|
|
14269
|
-
if (existing) {
|
|
14270
|
-
const next2 = {
|
|
14271
|
-
...current,
|
|
14272
|
-
needsRun: current.needsRun || existing.injectedAtMs === void 0,
|
|
14273
|
-
updatedAtMs: nowMs
|
|
14274
|
-
};
|
|
14275
|
-
await writeWorkState(state, next2);
|
|
14276
|
-
return { status: "duplicate" };
|
|
14277
|
-
}
|
|
14278
|
-
const next = {
|
|
14279
|
-
...current,
|
|
14280
|
-
consecutiveFailureCount: 0,
|
|
14281
|
-
lastFailureAtMs: void 0,
|
|
14282
|
-
messages: [...current.messages, args.message].sort(compareMessages),
|
|
14283
|
-
needsRun: true,
|
|
14284
|
-
terminallyFailedAtMs: void 0,
|
|
14285
|
-
updatedAtMs: nowMs
|
|
14286
|
-
};
|
|
14287
|
-
await writeWorkState(state, next);
|
|
14288
|
-
return { status: "appended" };
|
|
14289
|
-
}
|
|
14290
|
-
);
|
|
14291
|
-
}
|
|
14292
|
-
async function appendAndEnqueueInboundMessage(args) {
|
|
14293
|
-
const nowMs = args.nowMs ?? now();
|
|
14294
|
-
const appendResult = await appendInboundMessage({
|
|
14295
|
-
message: args.message,
|
|
14296
|
-
nowMs,
|
|
14297
|
-
state: args.state
|
|
14298
|
-
});
|
|
14299
|
-
let idempotencyKey = args.message.inboundMessageId;
|
|
14300
|
-
if (appendResult.status === "duplicate") {
|
|
14301
|
-
const work = await getConversationWorkState({
|
|
14302
|
-
conversationId: args.message.conversationId,
|
|
14303
|
-
state: args.state
|
|
14304
|
-
});
|
|
14305
|
-
if (!work || hasRecentEnqueueMarker(work, nowMs)) {
|
|
14306
|
-
return appendResult;
|
|
14307
|
-
}
|
|
14308
|
-
idempotencyKey = duplicateInboundNudgeIdempotencyKey(args.message, nowMs);
|
|
14309
|
-
}
|
|
14310
|
-
const queueResult = await args.queue.send(
|
|
14311
|
-
{
|
|
14312
|
-
conversationId: args.message.conversationId,
|
|
14313
|
-
destination: args.message.destination
|
|
14314
|
-
},
|
|
14315
|
-
{ idempotencyKey }
|
|
14316
|
-
);
|
|
14317
|
-
await markConversationWorkEnqueued({
|
|
14318
|
-
conversationId: args.message.conversationId,
|
|
14319
|
-
nowMs,
|
|
14320
|
-
state: args.state
|
|
14321
|
-
});
|
|
14322
|
-
return {
|
|
14323
|
-
...appendResult,
|
|
14324
|
-
queueMessageId: queueResult?.messageId
|
|
14325
|
-
};
|
|
14326
|
-
}
|
|
14327
|
-
async function requestConversationWork(args) {
|
|
14328
|
-
const nowMs = args.nowMs ?? now();
|
|
14329
|
-
return await withConversationMutation(args, async (state) => {
|
|
14330
|
-
const existing = await readWorkState(state, args.conversationId);
|
|
14331
|
-
if (existing) {
|
|
14332
|
-
assertSameConversationDestination({
|
|
14333
|
-
conversationId: args.conversationId,
|
|
14334
|
-
current: existing.destination,
|
|
14335
|
-
next: args.destination
|
|
14336
|
-
});
|
|
14337
|
-
}
|
|
14338
|
-
const current = existing ?? emptyWorkState({
|
|
14339
|
-
conversationId: args.conversationId,
|
|
14340
|
-
destination: args.destination,
|
|
14341
|
-
nowMs
|
|
14342
|
-
});
|
|
14343
|
-
await writeWorkState(state, {
|
|
14344
|
-
...current,
|
|
14345
|
-
needsRun: true,
|
|
14346
|
-
updatedAtMs: nowMs
|
|
14347
|
-
});
|
|
14348
|
-
return { status: existing === void 0 ? "created" : "updated" };
|
|
14349
|
-
});
|
|
14350
|
-
}
|
|
14351
|
-
async function markConversationWorkEnqueued(args) {
|
|
14352
|
-
const nowMs = args.nowMs ?? now();
|
|
14353
|
-
await withConversationMutation(args, async (state) => {
|
|
14354
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14355
|
-
if (!current) {
|
|
14356
|
-
return;
|
|
14357
|
-
}
|
|
14358
|
-
await writeWorkState(state, {
|
|
14359
|
-
...current,
|
|
14360
|
-
lastEnqueuedAtMs: nowMs,
|
|
14361
|
-
updatedAtMs: nowMs
|
|
14362
|
-
});
|
|
14363
|
-
});
|
|
14364
|
-
}
|
|
14365
|
-
async function startConversationWork(args) {
|
|
14366
|
-
const nowMs = args.nowMs ?? now();
|
|
14367
|
-
return await withConversationMutation(args, async (state) => {
|
|
14368
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14369
|
-
if (!current) {
|
|
14370
|
-
return { status: "no_work" };
|
|
14371
|
-
}
|
|
14372
|
-
if (isLeaseActive(current.lease, nowMs)) {
|
|
14373
|
-
return {
|
|
14374
|
-
status: "active",
|
|
14375
|
-
leaseExpiresAtMs: current.lease.leaseExpiresAtMs
|
|
14376
|
-
};
|
|
14377
|
-
}
|
|
14378
|
-
if (!hasRunnableWork(current)) {
|
|
14379
|
-
return { status: "no_work" };
|
|
14380
|
-
}
|
|
14381
|
-
const lease = {
|
|
14382
|
-
leaseToken: randomUUID4(),
|
|
14383
|
-
acquiredAtMs: nowMs,
|
|
14384
|
-
lastCheckInAtMs: nowMs,
|
|
14385
|
-
leaseExpiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
|
|
14386
|
-
};
|
|
14387
|
-
await writeWorkState(state, {
|
|
14388
|
-
...current,
|
|
14389
|
-
lease,
|
|
14390
|
-
needsRun: false,
|
|
14391
|
-
updatedAtMs: nowMs
|
|
14392
|
-
});
|
|
14393
|
-
return {
|
|
14394
|
-
status: "acquired",
|
|
14395
|
-
leaseToken: lease.leaseToken,
|
|
14396
|
-
leaseExpiresAtMs: lease.leaseExpiresAtMs
|
|
14397
|
-
};
|
|
14398
|
-
});
|
|
14399
|
-
}
|
|
14400
|
-
async function checkInConversationWork(args) {
|
|
14401
|
-
const nowMs = args.nowMs ?? now();
|
|
14402
|
-
return await withConversationMutation(args, async (state) => {
|
|
14403
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14404
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14405
|
-
return false;
|
|
14406
|
-
}
|
|
14407
|
-
await writeWorkState(state, {
|
|
14408
|
-
...current,
|
|
14409
|
-
lease: {
|
|
14410
|
-
...current.lease,
|
|
14411
|
-
lastCheckInAtMs: nowMs,
|
|
14412
|
-
leaseExpiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
|
|
14413
|
-
},
|
|
14414
|
-
updatedAtMs: nowMs
|
|
14415
|
-
});
|
|
14416
|
-
return true;
|
|
14417
|
-
});
|
|
14418
|
-
}
|
|
14419
|
-
async function drainConversationMailbox(args) {
|
|
14420
|
-
const nowMs = args.nowMs ?? now();
|
|
14421
|
-
const pending = await withConversationMutation(args, async (state) => {
|
|
14422
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14423
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14424
|
-
throw new Error(
|
|
14425
|
-
`Conversation work lease is not held for ${args.conversationId}`
|
|
14426
|
-
);
|
|
14427
|
-
}
|
|
14428
|
-
return pendingMessages(current);
|
|
14429
|
-
});
|
|
14430
|
-
if (pending.length === 0) {
|
|
14431
|
-
return [];
|
|
14432
|
-
}
|
|
14433
|
-
await args.inject(pending);
|
|
14434
|
-
await withConversationMutation(args, async (state) => {
|
|
14435
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14436
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14437
|
-
throw new Error(
|
|
14438
|
-
`Conversation work lease is not held for ${args.conversationId}`
|
|
14439
|
-
);
|
|
14440
|
-
}
|
|
14441
|
-
const drainedIds = new Set(
|
|
14442
|
-
pending.map((message) => message.inboundMessageId)
|
|
14443
|
-
);
|
|
14444
|
-
const messages = current.messages.map(
|
|
14445
|
-
(message) => drainedIds.has(message.inboundMessageId) ? { ...message, injectedAtMs: nowMs } : message
|
|
14446
|
-
);
|
|
14447
|
-
const hasPending = messages.some(
|
|
14448
|
-
(message) => message.injectedAtMs === void 0
|
|
14449
|
-
);
|
|
14450
|
-
await writeWorkState(state, {
|
|
14451
|
-
...current,
|
|
14452
|
-
consecutiveFailureCount: 0,
|
|
14453
|
-
lastFailureAtMs: void 0,
|
|
14454
|
-
messages,
|
|
14455
|
-
needsRun: hasPending,
|
|
14456
|
-
updatedAtMs: nowMs
|
|
14457
|
-
});
|
|
14458
|
-
});
|
|
14459
|
-
return pending;
|
|
14460
|
-
}
|
|
14461
|
-
async function markConversationMessagesInjected(args) {
|
|
14462
|
-
const nowMs = args.nowMs ?? now();
|
|
14463
|
-
const inboundMessageIds = new Set(args.inboundMessageIds);
|
|
14464
|
-
return await withConversationMutation(args, async (state) => {
|
|
14465
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14466
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14467
|
-
return false;
|
|
14468
|
-
}
|
|
14469
|
-
if (inboundMessageIds.size === 0) {
|
|
14470
|
-
return true;
|
|
14471
|
-
}
|
|
14472
|
-
let changed = false;
|
|
14473
|
-
const messages = current.messages.map((message) => {
|
|
14474
|
-
if (!inboundMessageIds.has(message.inboundMessageId) || message.injectedAtMs !== void 0) {
|
|
14475
|
-
return message;
|
|
14476
|
-
}
|
|
14477
|
-
changed = true;
|
|
14478
|
-
return { ...message, injectedAtMs: nowMs };
|
|
14479
|
-
});
|
|
14480
|
-
if (!changed) {
|
|
14481
|
-
return true;
|
|
14482
|
-
}
|
|
14483
|
-
await writeWorkState(state, {
|
|
14484
|
-
...current,
|
|
14485
|
-
consecutiveFailureCount: 0,
|
|
14486
|
-
lastFailureAtMs: void 0,
|
|
14487
|
-
messages,
|
|
14488
|
-
updatedAtMs: nowMs
|
|
14489
|
-
});
|
|
14490
|
-
return true;
|
|
14491
|
-
});
|
|
14492
|
-
}
|
|
14493
|
-
async function requestConversationContinuation(args) {
|
|
14494
|
-
const nowMs = args.nowMs ?? now();
|
|
14495
|
-
return await withConversationMutation(args, async (state) => {
|
|
14496
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14497
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14498
|
-
return false;
|
|
14499
|
-
}
|
|
14500
|
-
assertSameConversationDestination({
|
|
14501
|
-
conversationId: args.conversationId,
|
|
14502
|
-
current: current.destination,
|
|
14503
|
-
next: args.destination
|
|
14504
|
-
});
|
|
14505
|
-
await writeWorkState(state, {
|
|
14506
|
-
...current,
|
|
14507
|
-
needsRun: true,
|
|
14508
|
-
updatedAtMs: nowMs
|
|
14509
|
-
});
|
|
14510
|
-
return true;
|
|
14511
|
-
});
|
|
14512
|
-
}
|
|
14513
|
-
async function releaseConversationWork(args) {
|
|
14514
|
-
const nowMs = args.nowMs ?? now();
|
|
14515
|
-
return await withConversationMutation(args, async (state) => {
|
|
14516
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14517
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14518
|
-
return false;
|
|
14519
|
-
}
|
|
14520
|
-
await writeWorkState(state, {
|
|
14521
|
-
...current,
|
|
14522
|
-
lease: void 0,
|
|
14523
|
-
updatedAtMs: nowMs
|
|
14524
|
-
});
|
|
14525
|
-
return true;
|
|
14526
|
-
});
|
|
14527
|
-
}
|
|
14528
|
-
async function completeConversationWork(args) {
|
|
14529
|
-
const nowMs = args.nowMs ?? now();
|
|
14530
|
-
return await withConversationMutation(args, async (state) => {
|
|
14531
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14532
|
-
if (!current || current.lease?.leaseToken !== args.leaseToken) {
|
|
14533
|
-
return "lost_lease";
|
|
14534
|
-
}
|
|
14535
|
-
const hasPending = pendingMessages(current).length > 0;
|
|
14536
|
-
const hasRunnableWork2 = current.needsRun || hasPending;
|
|
14537
|
-
await writeWorkState(state, {
|
|
14538
|
-
...current,
|
|
14539
|
-
consecutiveFailureCount: 0,
|
|
14540
|
-
lastFailureAtMs: void 0,
|
|
14541
|
-
lease: void 0,
|
|
14542
|
-
needsRun: hasRunnableWork2,
|
|
14543
|
-
updatedAtMs: nowMs
|
|
14544
|
-
});
|
|
14545
|
-
return hasRunnableWork2 ? "pending" : "completed";
|
|
14546
|
-
});
|
|
14547
|
-
}
|
|
14548
|
-
async function clearExpiredConversationLease(args) {
|
|
14549
|
-
const nowMs = args.nowMs ?? now();
|
|
14550
|
-
return await withConversationMutation(args, async (state) => {
|
|
14551
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14552
|
-
if (!current?.lease || current.lease.leaseExpiresAtMs > nowMs) {
|
|
14553
|
-
return false;
|
|
14554
|
-
}
|
|
14555
|
-
await writeWorkState(state, {
|
|
14556
|
-
...current,
|
|
14557
|
-
lease: void 0,
|
|
14558
|
-
needsRun: true,
|
|
14559
|
-
updatedAtMs: nowMs
|
|
14560
|
-
});
|
|
14561
|
-
return true;
|
|
14562
|
-
});
|
|
14563
|
-
}
|
|
14564
|
-
async function recordConversationWorkFailure(args) {
|
|
14565
|
-
const nowMs = args.nowMs ?? now();
|
|
14566
|
-
return await withConversationMutation(args, async (state) => {
|
|
14567
|
-
const current = await readWorkState(state, args.conversationId);
|
|
14568
|
-
if (!current) {
|
|
14569
|
-
return {
|
|
14570
|
-
abandoned: false,
|
|
14571
|
-
consecutiveFailureCount: 0,
|
|
14572
|
-
releasedLease: false
|
|
14573
|
-
};
|
|
14574
|
-
}
|
|
14575
|
-
const consecutiveFailureCount = current.consecutiveFailureCount + 1;
|
|
14576
|
-
const abandoned = consecutiveFailureCount >= CONVERSATION_WORK_MAX_CONSECUTIVE_FAILURES;
|
|
14577
|
-
if (!abandoned) {
|
|
14578
|
-
await writeWorkState(state, {
|
|
14579
|
-
...current,
|
|
14580
|
-
consecutiveFailureCount,
|
|
14581
|
-
lastFailureAtMs: nowMs,
|
|
14582
|
-
updatedAtMs: nowMs
|
|
14583
|
-
});
|
|
14584
|
-
return {
|
|
14585
|
-
abandoned: false,
|
|
14586
|
-
consecutiveFailureCount,
|
|
14587
|
-
releasedLease: false
|
|
14588
|
-
};
|
|
14589
|
-
}
|
|
14590
|
-
const releasedLease = Boolean(current.lease);
|
|
14591
|
-
const drainedMessages = current.messages.filter(
|
|
14592
|
-
(message) => message.injectedAtMs !== void 0
|
|
14593
|
-
);
|
|
14594
|
-
await writeWorkState(state, {
|
|
14595
|
-
...current,
|
|
14596
|
-
consecutiveFailureCount,
|
|
14597
|
-
lastFailureAtMs: nowMs,
|
|
14598
|
-
lease: void 0,
|
|
14599
|
-
messages: drainedMessages,
|
|
14600
|
-
needsRun: false,
|
|
14601
|
-
terminallyFailedAtMs: nowMs,
|
|
14602
|
-
updatedAtMs: nowMs
|
|
14603
|
-
});
|
|
14604
|
-
return {
|
|
14605
|
-
abandoned: true,
|
|
14606
|
-
consecutiveFailureCount,
|
|
14607
|
-
releasedLease
|
|
14608
|
-
};
|
|
14609
|
-
});
|
|
14610
|
-
}
|
|
14611
|
-
async function listConversationWorkIds(args = {}) {
|
|
14612
|
-
const state = await getConnectedState(args.state);
|
|
14613
|
-
const ids = uniqueStrings(await state.get(indexKey()) ?? []);
|
|
14614
|
-
return ids.slice(0, args.limit ?? ids.length);
|
|
14615
|
-
}
|
|
14616
|
-
|
|
14617
|
-
// src/chat/services/timeout-resume.ts
|
|
14618
|
-
var TURN_TIMEOUT_RESUME_HMAC_CONTEXT = "junior.turn_timeout_resume.v1";
|
|
14619
|
-
var TURN_TIMEOUT_RESUME_SIGNATURE_VERSION = "v1";
|
|
14620
|
-
var TURN_TIMEOUT_RESUME_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
14621
|
-
var TURN_TIMEOUT_RESUME_TIMESTAMP_HEADER = "x-junior-resume-timestamp";
|
|
14622
|
-
var TURN_TIMEOUT_RESUME_SIGNATURE_HEADER = "x-junior-resume-signature";
|
|
14623
|
-
async function getAwaitingTurnContinuationRequest(args) {
|
|
14624
|
-
const sessionRecord = await getAgentTurnSessionRecord(
|
|
14625
|
-
args.conversationId,
|
|
14626
|
-
args.sessionId
|
|
14627
|
-
);
|
|
14628
|
-
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" && sessionRecord.resumeReason !== "yield" || sessionRecord.resumeReason === "timeout" && sessionRecord.sliceId < 2) {
|
|
14629
|
-
return void 0;
|
|
14630
|
-
}
|
|
14631
|
-
if (!sessionRecord.destination) {
|
|
14632
|
-
return void 0;
|
|
14633
|
-
}
|
|
14634
|
-
return {
|
|
14635
|
-
conversationId: args.conversationId,
|
|
14636
|
-
destination: sessionRecord.destination,
|
|
14637
|
-
sessionId: args.sessionId,
|
|
14638
|
-
expectedVersion: sessionRecord.version
|
|
14639
|
-
};
|
|
14640
|
-
}
|
|
14641
|
-
function getTurnTimeoutResumeSecret() {
|
|
14642
|
-
return process.env.JUNIOR_SECRET?.trim() || void 0;
|
|
14643
|
-
}
|
|
14644
|
-
function buildSignedPayload2(timestamp, body) {
|
|
14645
|
-
return `${TURN_TIMEOUT_RESUME_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
14646
|
-
}
|
|
14647
|
-
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
14648
|
-
const digest = createHmac3("sha256", secret).update(buildSignedPayload2(timestamp, body)).digest("hex");
|
|
14649
|
-
return `${TURN_TIMEOUT_RESUME_SIGNATURE_VERSION}=${digest}`;
|
|
14650
|
-
}
|
|
14651
|
-
function timingSafeMatch3(expected, actual) {
|
|
14652
|
-
const expectedBuffer = Buffer.from(expected);
|
|
14653
|
-
const actualBuffer = Buffer.from(actual);
|
|
14654
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
14655
|
-
return false;
|
|
14656
|
-
}
|
|
14657
|
-
return timingSafeEqual3(expectedBuffer, actualBuffer);
|
|
14658
|
-
}
|
|
14659
|
-
function parseTurnTimeoutResumeRequest(value) {
|
|
14660
|
-
if (!value || typeof value !== "object") {
|
|
14661
|
-
return void 0;
|
|
14662
|
-
}
|
|
14663
|
-
const record = value;
|
|
14664
|
-
const destination = parseDestination(record.destination);
|
|
14665
|
-
let expectedVersion = record.expectedVersion;
|
|
14666
|
-
if (typeof expectedVersion !== "number") {
|
|
14667
|
-
expectedVersion = record.expectedCheckpointVersion;
|
|
14668
|
-
}
|
|
14669
|
-
if (typeof record.conversationId !== "string" || typeof record.sessionId !== "string" || typeof expectedVersion !== "number" || !destination) {
|
|
14670
|
-
return void 0;
|
|
14671
|
-
}
|
|
14672
|
-
return {
|
|
14673
|
-
conversationId: record.conversationId,
|
|
14674
|
-
destination,
|
|
14675
|
-
sessionId: record.sessionId,
|
|
14676
|
-
expectedVersion
|
|
14677
|
-
};
|
|
14678
|
-
}
|
|
14679
|
-
async function scheduleTurnTimeoutResume(request, options = {}) {
|
|
14680
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
14681
|
-
await requestConversationWork({
|
|
14682
|
-
conversationId: request.conversationId,
|
|
14683
|
-
destination: request.destination,
|
|
14684
|
-
nowMs,
|
|
14685
|
-
state: options.state
|
|
14686
|
-
});
|
|
14687
|
-
const queue = options.queue ?? getVercelConversationWorkQueue();
|
|
14688
|
-
await queue.send(
|
|
14689
|
-
{
|
|
14690
|
-
conversationId: request.conversationId,
|
|
14691
|
-
destination: request.destination
|
|
14692
|
-
},
|
|
14693
|
-
{
|
|
14694
|
-
idempotencyKey: [
|
|
14695
|
-
"timeout",
|
|
14696
|
-
request.conversationId,
|
|
14697
|
-
request.sessionId,
|
|
14698
|
-
request.expectedVersion
|
|
14699
|
-
].join(":")
|
|
14700
|
-
}
|
|
14701
|
-
);
|
|
14702
|
-
await markConversationWorkEnqueued({
|
|
14703
|
-
conversationId: request.conversationId,
|
|
14704
|
-
nowMs,
|
|
14705
|
-
state: options.state
|
|
14706
|
-
});
|
|
14707
|
-
}
|
|
14708
|
-
async function verifyTurnTimeoutResumeRequest(request) {
|
|
14709
|
-
const timestamp = request.headers.get(TURN_TIMEOUT_RESUME_TIMESTAMP_HEADER)?.trim() ?? "";
|
|
14710
|
-
const signature = request.headers.get(TURN_TIMEOUT_RESUME_SIGNATURE_HEADER)?.trim() ?? "";
|
|
14711
|
-
const secret = getTurnTimeoutResumeSecret();
|
|
14712
|
-
if (!timestamp || !signature || !secret) {
|
|
14713
|
-
return void 0;
|
|
14714
|
-
}
|
|
14715
|
-
const parsedTimestamp = Number.parseInt(timestamp, 10);
|
|
14716
|
-
if (!Number.isFinite(parsedTimestamp) || Math.abs(Date.now() - parsedTimestamp) > TURN_TIMEOUT_RESUME_MAX_SKEW_MS) {
|
|
14717
|
-
return void 0;
|
|
14718
|
-
}
|
|
14719
|
-
const body = await request.text();
|
|
14720
|
-
const expectedSignature = signTurnTimeoutResumeBody(secret, timestamp, body);
|
|
14721
|
-
if (!timingSafeMatch3(expectedSignature, signature)) {
|
|
14722
|
-
return void 0;
|
|
14723
|
-
}
|
|
14724
|
-
try {
|
|
14725
|
-
return parseTurnTimeoutResumeRequest(JSON.parse(body));
|
|
14726
|
-
} catch {
|
|
14727
|
-
return void 0;
|
|
14728
|
-
}
|
|
14729
|
-
}
|
|
14730
|
-
|
|
14731
|
-
// src/chat/task-execution/heartbeat.ts
|
|
14732
|
-
var DEFAULT_RECOVERY_LIMIT = 25;
|
|
14733
|
-
function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
|
|
14734
|
-
return `heartbeat:${reason}:${conversationId}:${nowMs}`;
|
|
14735
|
-
}
|
|
14736
|
-
async function sendRecoveryNudge(args) {
|
|
14737
|
-
await args.queue.send(
|
|
14738
|
-
{
|
|
14739
|
-
conversationId: args.conversationId,
|
|
14740
|
-
destination: args.destination
|
|
14741
|
-
},
|
|
14742
|
-
{ idempotencyKey: args.idempotencyKey }
|
|
14743
|
-
);
|
|
14744
|
-
await markConversationWorkEnqueued({
|
|
14745
|
-
conversationId: args.conversationId,
|
|
14746
|
-
nowMs: args.nowMs,
|
|
14747
|
-
state: args.state
|
|
14748
|
-
});
|
|
14749
|
-
}
|
|
14750
|
-
async function recoverConversationWork(args) {
|
|
14751
|
-
const result = {
|
|
14752
|
-
expiredLeaseCount: 0,
|
|
14753
|
-
pendingCount: 0
|
|
14754
|
-
};
|
|
14755
|
-
const ids = await listConversationWorkIds({
|
|
13850
|
+
const staleBeforeMs = args.nowMs - CONVERSATION_WORK_STALE_ENQUEUE_MS;
|
|
13851
|
+
const ids = await listActiveConversationIds({
|
|
14756
13852
|
limit: args.limit ?? DEFAULT_RECOVERY_LIMIT,
|
|
13853
|
+
staleBeforeMs,
|
|
14757
13854
|
state: args.state
|
|
14758
13855
|
});
|
|
14759
13856
|
for (const conversationId of ids) {
|
|
@@ -14763,9 +13860,24 @@ async function recoverConversationWork(args) {
|
|
|
14763
13860
|
state: args.state
|
|
14764
13861
|
});
|
|
14765
13862
|
if (!work) {
|
|
13863
|
+
await removeActiveConversation({
|
|
13864
|
+
conversationId,
|
|
13865
|
+
state: args.state
|
|
13866
|
+
});
|
|
13867
|
+
continue;
|
|
13868
|
+
}
|
|
13869
|
+
if (work.execution.status === "idle") {
|
|
13870
|
+
await removeActiveConversation({
|
|
13871
|
+
conversationId,
|
|
13872
|
+
state: args.state
|
|
13873
|
+
});
|
|
13874
|
+
continue;
|
|
13875
|
+
}
|
|
13876
|
+
const destination = work.destination;
|
|
13877
|
+
if (!destination) {
|
|
14766
13878
|
continue;
|
|
14767
13879
|
}
|
|
14768
|
-
if (work.lease && work.lease.
|
|
13880
|
+
if (work.execution.lease && work.execution.lease.expiresAtMs <= args.nowMs) {
|
|
14769
13881
|
const cleared = await clearExpiredConversationLease({
|
|
14770
13882
|
conversationId,
|
|
14771
13883
|
nowMs: args.nowMs,
|
|
@@ -14776,7 +13888,7 @@ async function recoverConversationWork(args) {
|
|
|
14776
13888
|
}
|
|
14777
13889
|
await sendRecoveryNudge({
|
|
14778
13890
|
conversationId,
|
|
14779
|
-
destination
|
|
13891
|
+
destination,
|
|
14780
13892
|
idempotencyKey: heartbeatIdempotencyKey(
|
|
14781
13893
|
"lease",
|
|
14782
13894
|
conversationId,
|
|
@@ -14795,15 +13907,15 @@ async function recoverConversationWork(args) {
|
|
|
14795
13907
|
);
|
|
14796
13908
|
continue;
|
|
14797
13909
|
}
|
|
14798
|
-
if (work.lease || !hasRunnableConversationWork(work)) {
|
|
13910
|
+
if (work.execution.lease || !hasRunnableConversationWork(work)) {
|
|
14799
13911
|
continue;
|
|
14800
13912
|
}
|
|
14801
|
-
if (typeof work.lastEnqueuedAtMs === "number" && work.lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > args.nowMs) {
|
|
13913
|
+
if (typeof work.execution.lastEnqueuedAtMs === "number" && work.execution.lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > args.nowMs) {
|
|
14802
13914
|
continue;
|
|
14803
13915
|
}
|
|
14804
13916
|
await sendRecoveryNudge({
|
|
14805
13917
|
conversationId,
|
|
14806
|
-
destination
|
|
13918
|
+
destination,
|
|
14807
13919
|
idempotencyKey: heartbeatIdempotencyKey(
|
|
14808
13920
|
"pending",
|
|
14809
13921
|
conversationId,
|
|
@@ -15016,8 +14128,6 @@ var DEFAULT_RECOVERY_LIMIT2 = 25;
|
|
|
15016
14128
|
var DEFAULT_PLUGIN_LIMIT = 25;
|
|
15017
14129
|
var DISPATCH_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
15018
14130
|
var PLUGIN_HEARTBEAT_TIMEOUT_MS = 25e3;
|
|
15019
|
-
var TIMEOUT_RESUME_STALE_MS = 2 * 60 * 1e3;
|
|
15020
|
-
var TIMEOUT_RESUME_RECOVERY_SCAN_LIMIT = 500;
|
|
15021
14131
|
function isStaleDispatch(args) {
|
|
15022
14132
|
if (args.record.status === "running") {
|
|
15023
14133
|
return typeof args.record.leaseExpiresAtMs === "number" && args.record.leaseExpiresAtMs <= args.nowMs;
|
|
@@ -15062,63 +14172,6 @@ async function runWithTimeout(promise, timeoutMs) {
|
|
|
15062
14172
|
}
|
|
15063
14173
|
}
|
|
15064
14174
|
}
|
|
15065
|
-
async function recoverStaleTimeoutResumes(args) {
|
|
15066
|
-
const summaries = await listAgentTurnSessionSummaries(
|
|
15067
|
-
TIMEOUT_RESUME_RECOVERY_SCAN_LIMIT
|
|
15068
|
-
);
|
|
15069
|
-
let recovered = 0;
|
|
15070
|
-
for (const summary of summaries) {
|
|
15071
|
-
if (recovered >= (args.limit ?? DEFAULT_RECOVERY_LIMIT2)) {
|
|
15072
|
-
break;
|
|
15073
|
-
}
|
|
15074
|
-
if (summary.state !== "awaiting_resume" || summary.resumeReason !== "timeout" && summary.resumeReason !== "yield" || summary.updatedAtMs + TIMEOUT_RESUME_STALE_MS > args.nowMs) {
|
|
15075
|
-
continue;
|
|
15076
|
-
}
|
|
15077
|
-
try {
|
|
15078
|
-
const persistedState = await getPersistedThreadState(
|
|
15079
|
-
summary.conversationId
|
|
15080
|
-
);
|
|
15081
|
-
const conversation = coerceThreadConversationState(persistedState);
|
|
15082
|
-
if (conversation.processing.activeTurnId !== summary.sessionId) {
|
|
15083
|
-
continue;
|
|
15084
|
-
}
|
|
15085
|
-
const request = await getAwaitingTurnContinuationRequest({
|
|
15086
|
-
conversationId: summary.conversationId,
|
|
15087
|
-
sessionId: summary.sessionId
|
|
15088
|
-
});
|
|
15089
|
-
if (!request) {
|
|
15090
|
-
continue;
|
|
15091
|
-
}
|
|
15092
|
-
await scheduleTurnTimeoutResume(request, {
|
|
15093
|
-
queue: args.conversationWorkQueue
|
|
15094
|
-
});
|
|
15095
|
-
recovered += 1;
|
|
15096
|
-
logInfo(
|
|
15097
|
-
"agent_turn_timeout_resume_recovery_scheduled",
|
|
15098
|
-
{},
|
|
15099
|
-
{
|
|
15100
|
-
"app.ai.conversation_id": summary.conversationId,
|
|
15101
|
-
"app.ai.session_id": summary.sessionId,
|
|
15102
|
-
"app.ai.resume_session_version": request.expectedVersion,
|
|
15103
|
-
"app.ai.resume_slice_id": summary.sliceId
|
|
15104
|
-
},
|
|
15105
|
-
"Heartbeat rescheduled stale timeout resume"
|
|
15106
|
-
);
|
|
15107
|
-
} catch (error) {
|
|
15108
|
-
logException(
|
|
15109
|
-
error,
|
|
15110
|
-
"agent_turn_timeout_resume_recovery_failed",
|
|
15111
|
-
{},
|
|
15112
|
-
{
|
|
15113
|
-
"app.ai.conversation_id": summary.conversationId,
|
|
15114
|
-
"app.ai.session_id": summary.sessionId
|
|
15115
|
-
},
|
|
15116
|
-
"Heartbeat timeout resume recovery failed"
|
|
15117
|
-
);
|
|
15118
|
-
}
|
|
15119
|
-
}
|
|
15120
|
-
return recovered;
|
|
15121
|
-
}
|
|
15122
14175
|
async function recoverStaleDispatches(args) {
|
|
15123
14176
|
const ids = await listIncompleteDispatchIds();
|
|
15124
14177
|
let recovered = 0;
|
|
@@ -15216,10 +14269,6 @@ async function runHeartbeat(args) {
|
|
|
15216
14269
|
nowMs: args.nowMs,
|
|
15217
14270
|
queue: args.conversationWorkQueue ?? getVercelConversationWorkQueue()
|
|
15218
14271
|
});
|
|
15219
|
-
await recoverStaleTimeoutResumes({
|
|
15220
|
-
conversationWorkQueue: args.conversationWorkQueue,
|
|
15221
|
-
nowMs: args.nowMs
|
|
15222
|
-
});
|
|
15223
14272
|
await recoverStaleDispatches({ nowMs: args.nowMs });
|
|
15224
14273
|
await runPluginHeartbeats({ nowMs: args.nowMs });
|
|
15225
14274
|
}
|
|
@@ -15239,7 +14288,7 @@ function verifyHeartbeatRequest(request) {
|
|
|
15239
14288
|
}
|
|
15240
14289
|
const actual = Buffer.from(authorization.slice("Bearer ".length));
|
|
15241
14290
|
const expected = Buffer.from(secret);
|
|
15242
|
-
return actual.length === expected.length &&
|
|
14291
|
+
return actual.length === expected.length && timingSafeEqual3(actual, expected);
|
|
15243
14292
|
}
|
|
15244
14293
|
async function GET2(request, waitUntil, options = {}) {
|
|
15245
14294
|
if (!verifyHeartbeatRequest(request)) {
|
|
@@ -15399,7 +14448,7 @@ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
|
15399
14448
|
var STATUS_MIN_VISIBLE_MS = 1200;
|
|
15400
14449
|
var STATUS_ROTATION_INTERVAL_MS = 3e4;
|
|
15401
14450
|
function createAssistantStatusScheduler(args) {
|
|
15402
|
-
const
|
|
14451
|
+
const now2 = args.now ?? (() => Date.now());
|
|
15403
14452
|
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
15404
14453
|
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
15405
14454
|
const random = args.random ?? Math.random;
|
|
@@ -15463,7 +14512,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
15463
14512
|
}
|
|
15464
14513
|
currentVisibleStatus = text;
|
|
15465
14514
|
currentLoadingMessages = nextLoadingMessages;
|
|
15466
|
-
lastStatusAt =
|
|
14515
|
+
lastStatusAt = now2();
|
|
15467
14516
|
scheduleRotation();
|
|
15468
14517
|
await enqueueStatusUpdate(async () => {
|
|
15469
14518
|
await args.sendStatus(text, nextLoadingMessages);
|
|
@@ -15546,7 +14595,7 @@ function createAssistantStatusScheduler(args) {
|
|
|
15546
14595
|
}
|
|
15547
14596
|
return;
|
|
15548
14597
|
}
|
|
15549
|
-
const elapsed =
|
|
14598
|
+
const elapsed = now2() - lastStatusAt;
|
|
15550
14599
|
const waitMs = Math.max(
|
|
15551
14600
|
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
15552
14601
|
STATUS_MIN_VISIBLE_MS - elapsed,
|
|
@@ -16224,7 +15273,7 @@ async function resumeSlackTurn(args) {
|
|
|
16224
15273
|
deferredPauseHandler = async () => {
|
|
16225
15274
|
await onAuthPause(error);
|
|
16226
15275
|
};
|
|
16227
|
-
} else if (isRetryableTurnError(error, "
|
|
15276
|
+
} else if (isRetryableTurnError(error, "agent_continue") && onTimeoutPause) {
|
|
16228
15277
|
deferredPauseKind = "timeout";
|
|
16229
15278
|
deferredPauseHandler = async () => {
|
|
16230
15279
|
await onTimeoutPause(error);
|
|
@@ -16332,6 +15381,56 @@ async function persistAuthPauseTurnState(args) {
|
|
|
16332
15381
|
await persistThreadStateById(args.threadStateId, { conversation });
|
|
16333
15382
|
}
|
|
16334
15383
|
|
|
15384
|
+
// src/chat/services/agent-continue.ts
|
|
15385
|
+
async function getAwaitingAgentContinueRequest(args) {
|
|
15386
|
+
const sessionRecord = await getAgentTurnSessionRecord(
|
|
15387
|
+
args.conversationId,
|
|
15388
|
+
args.sessionId
|
|
15389
|
+
);
|
|
15390
|
+
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" && sessionRecord.resumeReason !== "yield" || sessionRecord.resumeReason === "timeout" && sessionRecord.sliceId < 2) {
|
|
15391
|
+
return void 0;
|
|
15392
|
+
}
|
|
15393
|
+
if (!sessionRecord.destination) {
|
|
15394
|
+
return void 0;
|
|
15395
|
+
}
|
|
15396
|
+
return {
|
|
15397
|
+
conversationId: args.conversationId,
|
|
15398
|
+
destination: sessionRecord.destination,
|
|
15399
|
+
sessionId: args.sessionId,
|
|
15400
|
+
expectedVersion: sessionRecord.version
|
|
15401
|
+
};
|
|
15402
|
+
}
|
|
15403
|
+
async function scheduleAgentContinue(request, options = {}) {
|
|
15404
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
15405
|
+
await requestConversationWork({
|
|
15406
|
+
conversationId: request.conversationId,
|
|
15407
|
+
destination: request.destination,
|
|
15408
|
+
nowMs,
|
|
15409
|
+
state: options.state
|
|
15410
|
+
});
|
|
15411
|
+
const queue = options.queue ?? getVercelConversationWorkQueue();
|
|
15412
|
+
await queue.send(
|
|
15413
|
+
{
|
|
15414
|
+
conversationId: request.conversationId,
|
|
15415
|
+
destination: request.destination
|
|
15416
|
+
},
|
|
15417
|
+
{
|
|
15418
|
+
idempotencyKey: [
|
|
15419
|
+
"agent-continue",
|
|
15420
|
+
request.conversationId,
|
|
15421
|
+
request.sessionId,
|
|
15422
|
+
request.expectedVersion,
|
|
15423
|
+
nowMs
|
|
15424
|
+
].join(":")
|
|
15425
|
+
}
|
|
15426
|
+
);
|
|
15427
|
+
await markConversationWorkEnqueued({
|
|
15428
|
+
conversationId: request.conversationId,
|
|
15429
|
+
nowMs,
|
|
15430
|
+
state: options.state
|
|
15431
|
+
});
|
|
15432
|
+
}
|
|
15433
|
+
|
|
16335
15434
|
// src/handlers/oauth-html.ts
|
|
16336
15435
|
function htmlCallbackResponse(title, message, status) {
|
|
16337
15436
|
const html = `<!DOCTYPE html>
|
|
@@ -16351,87 +15450,6 @@ function htmlCallbackResponse(title, message, status) {
|
|
|
16351
15450
|
});
|
|
16352
15451
|
}
|
|
16353
15452
|
|
|
16354
|
-
// src/chat/slack/user.ts
|
|
16355
|
-
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
16356
|
-
var userCache = /* @__PURE__ */ new Map();
|
|
16357
|
-
function readFromCache(userId) {
|
|
16358
|
-
const hit = userCache.get(userId);
|
|
16359
|
-
if (!hit) return null;
|
|
16360
|
-
if (hit.expiresAt < Date.now()) {
|
|
16361
|
-
userCache.delete(userId);
|
|
16362
|
-
return null;
|
|
16363
|
-
}
|
|
16364
|
-
return hit.value;
|
|
16365
|
-
}
|
|
16366
|
-
function writeToCache(userId, value) {
|
|
16367
|
-
userCache.set(userId, {
|
|
16368
|
-
value,
|
|
16369
|
-
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
16370
|
-
});
|
|
16371
|
-
}
|
|
16372
|
-
async function lookupSlackUser(userId) {
|
|
16373
|
-
if (!userId) {
|
|
16374
|
-
return null;
|
|
16375
|
-
}
|
|
16376
|
-
const cached = readFromCache(userId);
|
|
16377
|
-
if (cached) {
|
|
16378
|
-
return cached;
|
|
16379
|
-
}
|
|
16380
|
-
const token = getSlackBotToken();
|
|
16381
|
-
if (!token) {
|
|
16382
|
-
return null;
|
|
16383
|
-
}
|
|
16384
|
-
try {
|
|
16385
|
-
const response = await fetch(
|
|
16386
|
-
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
16387
|
-
{
|
|
16388
|
-
headers: {
|
|
16389
|
-
authorization: `Bearer ${token}`
|
|
16390
|
-
}
|
|
16391
|
-
}
|
|
16392
|
-
);
|
|
16393
|
-
if (!response.ok) {
|
|
16394
|
-
logWarn(
|
|
16395
|
-
"slack_user_lookup_failed",
|
|
16396
|
-
{},
|
|
16397
|
-
{
|
|
16398
|
-
"enduser.id": userId,
|
|
16399
|
-
"http.response.status_code": response.status
|
|
16400
|
-
},
|
|
16401
|
-
"Slack user lookup request failed"
|
|
16402
|
-
);
|
|
16403
|
-
return null;
|
|
16404
|
-
}
|
|
16405
|
-
const payload = await response.json();
|
|
16406
|
-
if (!payload.ok || !payload.user) {
|
|
16407
|
-
return null;
|
|
16408
|
-
}
|
|
16409
|
-
const userName = payload.user.name?.trim() || void 0;
|
|
16410
|
-
const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
|
|
16411
|
-
const result = {
|
|
16412
|
-
userName,
|
|
16413
|
-
fullName,
|
|
16414
|
-
email: payload.user.profile?.email?.trim() || void 0
|
|
16415
|
-
};
|
|
16416
|
-
writeToCache(userId, result);
|
|
16417
|
-
return result;
|
|
16418
|
-
} catch (error) {
|
|
16419
|
-
logWarn(
|
|
16420
|
-
"slack_user_lookup_failed",
|
|
16421
|
-
{},
|
|
16422
|
-
{
|
|
16423
|
-
"enduser.id": userId,
|
|
16424
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
16425
|
-
},
|
|
16426
|
-
"Slack user lookup failed with exception"
|
|
16427
|
-
);
|
|
16428
|
-
return null;
|
|
16429
|
-
}
|
|
16430
|
-
}
|
|
16431
|
-
async function lookupSlackActorIdentity(userId) {
|
|
16432
|
-
return slackActorIdentity(userId, await lookupSlackUser(userId));
|
|
16433
|
-
}
|
|
16434
|
-
|
|
16435
15453
|
// src/handlers/mcp-oauth-callback.ts
|
|
16436
15454
|
var CALLBACK_PAGES = {
|
|
16437
15455
|
missing_state: {
|
|
@@ -16622,6 +15640,22 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16622
15640
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16623
15641
|
authSession.channelId
|
|
16624
15642
|
);
|
|
15643
|
+
let requester;
|
|
15644
|
+
try {
|
|
15645
|
+
requester = createRequesterFromStoredSlackRequester({
|
|
15646
|
+
requester: lockedSessionRecord.requester,
|
|
15647
|
+
teamId: destination.teamId,
|
|
15648
|
+
userId: authSession.userId
|
|
15649
|
+
});
|
|
15650
|
+
} catch {
|
|
15651
|
+
await failAgentTurnSessionRecord({
|
|
15652
|
+
conversationId: authSession.conversationId,
|
|
15653
|
+
expectedVersion: lockedSessionRecord.version,
|
|
15654
|
+
sessionId: lockedSessionId,
|
|
15655
|
+
errorMessage: "Stored Slack requester identity did not match OAuth requester"
|
|
15656
|
+
});
|
|
15657
|
+
return false;
|
|
15658
|
+
}
|
|
16625
15659
|
await recordAuthorizationCompleted({
|
|
16626
15660
|
conversationId: authSession.conversationId,
|
|
16627
15661
|
kind: "mcp",
|
|
@@ -16633,13 +15667,12 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16633
15667
|
}),
|
|
16634
15668
|
ttlMs: THREAD_STATE_TTL_MS4
|
|
16635
15669
|
});
|
|
16636
|
-
const requester = await lookupSlackActorIdentity(authSession.userId);
|
|
16637
15670
|
return {
|
|
16638
15671
|
messageText: lockedUserMessage.text,
|
|
16639
15672
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
16640
15673
|
replyContext: {
|
|
16641
15674
|
credentialContext: {
|
|
16642
|
-
actor: { type: "user", userId:
|
|
15675
|
+
actor: { type: "user", userId: requester.userId }
|
|
16643
15676
|
},
|
|
16644
15677
|
requester,
|
|
16645
15678
|
destination,
|
|
@@ -16648,7 +15681,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16648
15681
|
turnId: lockedSessionId,
|
|
16649
15682
|
channelId: authSession.channelId,
|
|
16650
15683
|
threadTs: authSession.threadTs,
|
|
16651
|
-
requesterId:
|
|
15684
|
+
requesterId: requester.userId
|
|
16652
15685
|
},
|
|
16653
15686
|
toolChannelId: authSession.toolChannelId ?? lockedArtifacts.assistantContextChannelId ?? authSession.channelId,
|
|
16654
15687
|
conversationContext: lockedConversationContext,
|
|
@@ -16720,16 +15753,16 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
16720
15753
|
);
|
|
16721
15754
|
},
|
|
16722
15755
|
onTimeoutPause: async (error) => {
|
|
16723
|
-
if (!isRetryableTurnError(error, "
|
|
15756
|
+
if (!isRetryableTurnError(error, "agent_continue")) {
|
|
16724
15757
|
throw error;
|
|
16725
15758
|
}
|
|
16726
15759
|
const version = error.metadata?.version;
|
|
16727
15760
|
if (typeof version !== "number") {
|
|
16728
15761
|
throw new Error(
|
|
16729
|
-
"
|
|
15762
|
+
"MCP OAuth agent continuation did not include a session record version"
|
|
16730
15763
|
);
|
|
16731
15764
|
}
|
|
16732
|
-
await
|
|
15765
|
+
await scheduleAgentContinue({
|
|
16733
15766
|
conversationId: authSession.conversationId,
|
|
16734
15767
|
destination,
|
|
16735
15768
|
sessionId: lockedSessionId,
|
|
@@ -16920,28 +15953,118 @@ async function buildHomeView(userId, userTokenStore) {
|
|
|
16920
15953
|
},
|
|
16921
15954
|
{ type: "divider" },
|
|
16922
15955
|
{
|
|
16923
|
-
type: "header",
|
|
16924
|
-
text: {
|
|
16925
|
-
type: "plain_text",
|
|
16926
|
-
text: "Connected accounts"
|
|
16927
|
-
}
|
|
15956
|
+
type: "header",
|
|
15957
|
+
text: {
|
|
15958
|
+
type: "plain_text",
|
|
15959
|
+
text: "Connected accounts"
|
|
15960
|
+
}
|
|
15961
|
+
},
|
|
15962
|
+
...accountBlocks,
|
|
15963
|
+
{
|
|
15964
|
+
type: "context",
|
|
15965
|
+
elements: [
|
|
15966
|
+
{
|
|
15967
|
+
type: "mrkdwn",
|
|
15968
|
+
text: `*junior version:* \`${runtimeMetadata.version ?? "unknown"}\``
|
|
15969
|
+
}
|
|
15970
|
+
]
|
|
15971
|
+
}
|
|
15972
|
+
]
|
|
15973
|
+
};
|
|
15974
|
+
}
|
|
15975
|
+
async function publishAppHomeView(slackClient, userId, userTokenStore) {
|
|
15976
|
+
const view = await buildHomeView(userId, userTokenStore);
|
|
15977
|
+
await slackClient.views.publish({ user_id: userId, view });
|
|
15978
|
+
}
|
|
15979
|
+
|
|
15980
|
+
// src/chat/slack/user.ts
|
|
15981
|
+
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
15982
|
+
var userCache = /* @__PURE__ */ new Map();
|
|
15983
|
+
function userCacheKey(teamId, userId) {
|
|
15984
|
+
return `${teamId}:${userId}`;
|
|
15985
|
+
}
|
|
15986
|
+
function readFromCache(teamId, userId) {
|
|
15987
|
+
const hit = userCache.get(userCacheKey(teamId, userId));
|
|
15988
|
+
if (!hit) return null;
|
|
15989
|
+
if (hit.expiresAt < Date.now()) {
|
|
15990
|
+
userCache.delete(userCacheKey(teamId, userId));
|
|
15991
|
+
return null;
|
|
15992
|
+
}
|
|
15993
|
+
return hit.value;
|
|
15994
|
+
}
|
|
15995
|
+
function writeToCache(teamId, userId, value) {
|
|
15996
|
+
userCache.set(userCacheKey(teamId, userId), {
|
|
15997
|
+
value,
|
|
15998
|
+
expiresAt: Date.now() + USER_CACHE_TTL_MS
|
|
15999
|
+
});
|
|
16000
|
+
}
|
|
16001
|
+
async function lookupSlackUser(teamId, userId) {
|
|
16002
|
+
if (!teamId || !userId) {
|
|
16003
|
+
return null;
|
|
16004
|
+
}
|
|
16005
|
+
const cached = readFromCache(teamId, userId);
|
|
16006
|
+
if (cached) {
|
|
16007
|
+
return cached;
|
|
16008
|
+
}
|
|
16009
|
+
const token = getSlackBotToken();
|
|
16010
|
+
if (!token) {
|
|
16011
|
+
return null;
|
|
16012
|
+
}
|
|
16013
|
+
try {
|
|
16014
|
+
const response = await fetch(
|
|
16015
|
+
`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
|
|
16016
|
+
{
|
|
16017
|
+
headers: {
|
|
16018
|
+
authorization: `Bearer ${token}`
|
|
16019
|
+
}
|
|
16020
|
+
}
|
|
16021
|
+
);
|
|
16022
|
+
if (!response.ok) {
|
|
16023
|
+
logWarn(
|
|
16024
|
+
"slack_user_lookup_failed",
|
|
16025
|
+
{},
|
|
16026
|
+
{
|
|
16027
|
+
"enduser.id": userId,
|
|
16028
|
+
"app.slack.team_id": teamId,
|
|
16029
|
+
"http.response.status_code": response.status
|
|
16030
|
+
},
|
|
16031
|
+
"Slack user lookup request failed"
|
|
16032
|
+
);
|
|
16033
|
+
return null;
|
|
16034
|
+
}
|
|
16035
|
+
const payload = await response.json();
|
|
16036
|
+
if (!payload.ok || !payload.user) {
|
|
16037
|
+
return null;
|
|
16038
|
+
}
|
|
16039
|
+
const userName = payload.user.name?.trim() || void 0;
|
|
16040
|
+
const fullName = payload.user.profile?.display_name?.trim() || payload.user.profile?.real_name?.trim() || payload.user.real_name?.trim() || void 0;
|
|
16041
|
+
const result = {
|
|
16042
|
+
userName,
|
|
16043
|
+
fullName,
|
|
16044
|
+
email: payload.user.profile?.email?.trim() || void 0
|
|
16045
|
+
};
|
|
16046
|
+
writeToCache(teamId, userId, result);
|
|
16047
|
+
return result;
|
|
16048
|
+
} catch (error) {
|
|
16049
|
+
logWarn(
|
|
16050
|
+
"slack_user_lookup_failed",
|
|
16051
|
+
{},
|
|
16052
|
+
{
|
|
16053
|
+
"enduser.id": userId,
|
|
16054
|
+
"app.slack.team_id": teamId,
|
|
16055
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
16928
16056
|
},
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
{
|
|
16934
|
-
type: "mrkdwn",
|
|
16935
|
-
text: `*junior version:* \`${runtimeMetadata.version ?? "unknown"}\``
|
|
16936
|
-
}
|
|
16937
|
-
]
|
|
16938
|
-
}
|
|
16939
|
-
]
|
|
16940
|
-
};
|
|
16057
|
+
"Slack user lookup failed with exception"
|
|
16058
|
+
);
|
|
16059
|
+
return null;
|
|
16060
|
+
}
|
|
16941
16061
|
}
|
|
16942
|
-
async function
|
|
16943
|
-
|
|
16944
|
-
|
|
16062
|
+
async function lookupSlackRequester(teamId, userId) {
|
|
16063
|
+
return createSlackRequester(
|
|
16064
|
+
teamId,
|
|
16065
|
+
userId,
|
|
16066
|
+
await lookupSlackUser(teamId, userId)
|
|
16067
|
+
);
|
|
16945
16068
|
}
|
|
16946
16069
|
|
|
16947
16070
|
// src/handlers/oauth-callback.ts
|
|
@@ -17124,6 +16247,22 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17124
16247
|
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
17125
16248
|
stored.channelId
|
|
17126
16249
|
);
|
|
16250
|
+
let requester;
|
|
16251
|
+
try {
|
|
16252
|
+
requester = createRequesterFromStoredSlackRequester({
|
|
16253
|
+
requester: lockedSessionRecord.requester,
|
|
16254
|
+
teamId: destination.teamId,
|
|
16255
|
+
userId: lockedUserMessage.author.userId
|
|
16256
|
+
});
|
|
16257
|
+
} catch {
|
|
16258
|
+
await failAgentTurnSessionRecord({
|
|
16259
|
+
conversationId: stored.resumeConversationId,
|
|
16260
|
+
expectedVersion: lockedSessionRecord.version,
|
|
16261
|
+
sessionId: lockedSessionId,
|
|
16262
|
+
errorMessage: "Stored Slack requester identity did not match OAuth requester"
|
|
16263
|
+
});
|
|
16264
|
+
return false;
|
|
16265
|
+
}
|
|
17127
16266
|
await recordAuthorizationCompleted({
|
|
17128
16267
|
conversationId: stored.resumeConversationId,
|
|
17129
16268
|
kind: "plugin",
|
|
@@ -17135,9 +16274,6 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17135
16274
|
}),
|
|
17136
16275
|
ttlMs: THREAD_STATE_TTL_MS5
|
|
17137
16276
|
});
|
|
17138
|
-
const requester = await lookupSlackActorIdentity(
|
|
17139
|
-
lockedUserMessage.author.userId
|
|
17140
|
-
);
|
|
17141
16277
|
return {
|
|
17142
16278
|
messageText: lockedPendingAuth ? lockedUserMessage.text : stored.pendingMessage ?? lockedUserMessage.text,
|
|
17143
16279
|
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
@@ -17145,7 +16281,7 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17145
16281
|
credentialContext: {
|
|
17146
16282
|
actor: {
|
|
17147
16283
|
type: "user",
|
|
17148
|
-
userId:
|
|
16284
|
+
userId: requester.userId
|
|
17149
16285
|
}
|
|
17150
16286
|
},
|
|
17151
16287
|
requester,
|
|
@@ -17155,7 +16291,7 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17155
16291
|
turnId: lockedSessionId,
|
|
17156
16292
|
channelId: stored.channelId,
|
|
17157
16293
|
threadTs: stored.threadTs,
|
|
17158
|
-
requesterId:
|
|
16294
|
+
requesterId: requester.userId
|
|
17159
16295
|
},
|
|
17160
16296
|
toolChannelId: lockedArtifacts.assistantContextChannelId ?? stored.channelId,
|
|
17161
16297
|
artifactState: lockedArtifacts,
|
|
@@ -17215,16 +16351,16 @@ async function resumeOAuthSessionRecordTurn(stored) {
|
|
|
17215
16351
|
});
|
|
17216
16352
|
},
|
|
17217
16353
|
onTimeoutPause: async (error) => {
|
|
17218
|
-
if (!isRetryableTurnError(error, "
|
|
16354
|
+
if (!isRetryableTurnError(error, "agent_continue")) {
|
|
17219
16355
|
throw error;
|
|
17220
16356
|
}
|
|
17221
16357
|
const version = error.metadata?.version;
|
|
17222
16358
|
if (typeof version !== "number") {
|
|
17223
16359
|
throw new Error(
|
|
17224
|
-
"
|
|
16360
|
+
"OAuth agent continuation did not include a session record version"
|
|
17225
16361
|
);
|
|
17226
16362
|
}
|
|
17227
|
-
await
|
|
16363
|
+
await scheduleAgentContinue({
|
|
17228
16364
|
conversationId: stored.resumeConversationId,
|
|
17229
16365
|
destination,
|
|
17230
16366
|
sessionId: lockedSessionId,
|
|
@@ -17248,7 +16384,10 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
17248
16384
|
const conversationContext = buildConversationContext(conversation, {
|
|
17249
16385
|
excludeMessageId: latestUserMessage?.id
|
|
17250
16386
|
});
|
|
17251
|
-
const requester = await
|
|
16387
|
+
const requester = await lookupSlackRequester(
|
|
16388
|
+
stored.destination.teamId,
|
|
16389
|
+
stored.userId
|
|
16390
|
+
);
|
|
17252
16391
|
await resumeAuthorizedRequest({
|
|
17253
16392
|
messageText: stored.pendingMessage,
|
|
17254
16393
|
channelId: stored.channelId,
|
|
@@ -17325,8 +16464,8 @@ async function GET4(request, provider, waitUntil) {
|
|
|
17325
16464
|
);
|
|
17326
16465
|
}
|
|
17327
16466
|
const stateAdapter = getStateAdapter();
|
|
17328
|
-
const
|
|
17329
|
-
const stored = parseOAuthStatePayload(await stateAdapter.get(
|
|
16467
|
+
const stateKey = `oauth-state:${state}`;
|
|
16468
|
+
const stored = parseOAuthStatePayload(await stateAdapter.get(stateKey));
|
|
17330
16469
|
if (!stored) {
|
|
17331
16470
|
return htmlErrorResponse(
|
|
17332
16471
|
"Link expired",
|
|
@@ -17341,7 +16480,7 @@ async function GET4(request, provider, waitUntil) {
|
|
|
17341
16480
|
400
|
|
17342
16481
|
);
|
|
17343
16482
|
}
|
|
17344
|
-
await stateAdapter.delete(
|
|
16483
|
+
await stateAdapter.delete(stateKey);
|
|
17345
16484
|
const clientId = process.env[providerConfig.clientIdEnv]?.trim();
|
|
17346
16485
|
const clientSecret = process.env[providerConfig.clientSecretEnv]?.trim();
|
|
17347
16486
|
if (!clientId || !clientSecret) {
|
|
@@ -17648,9 +16787,9 @@ function buildJwksUrl(value) {
|
|
|
17648
16787
|
return url;
|
|
17649
16788
|
}
|
|
17650
16789
|
async function getJwks(issuer) {
|
|
17651
|
-
const
|
|
16790
|
+
const now2 = Date.now();
|
|
17652
16791
|
const cached = jwksByIssuer.get(issuer);
|
|
17653
|
-
if (cached && cached.expiresAtMs >
|
|
16792
|
+
if (cached && cached.expiresAtMs > now2) {
|
|
17654
16793
|
return cached.jwks;
|
|
17655
16794
|
}
|
|
17656
16795
|
if (cached) {
|
|
@@ -17675,7 +16814,7 @@ async function getJwks(issuer) {
|
|
|
17675
16814
|
}
|
|
17676
16815
|
jwksByIssuer.set(issuer, {
|
|
17677
16816
|
jwks,
|
|
17678
|
-
expiresAtMs:
|
|
16817
|
+
expiresAtMs: now2 + OIDC_DISCOVERY_CACHE_TTL_MS
|
|
17679
16818
|
});
|
|
17680
16819
|
return jwks;
|
|
17681
16820
|
}
|
|
@@ -18459,292 +17598,22 @@ async function proxySandboxEgressRequest(request, deps = {}) {
|
|
|
18459
17598
|
...provider === "github" ? githubPermissionHeaders(upstream) : {}
|
|
18460
17599
|
});
|
|
18461
17600
|
}
|
|
18462
|
-
}
|
|
18463
|
-
return new Response(upstream.body, {
|
|
18464
|
-
status: upstream.status,
|
|
18465
|
-
statusText: upstream.statusText,
|
|
18466
|
-
headers: responseHeaders(upstream)
|
|
18467
|
-
});
|
|
18468
|
-
}
|
|
18469
|
-
|
|
18470
|
-
// src/handlers/sandbox-egress-proxy.ts
|
|
18471
|
-
async function ALL(request, options = {}) {
|
|
18472
|
-
return await proxySandboxEgressRequest(request, {
|
|
18473
|
-
interceptHttp: options.interceptHttp
|
|
18474
|
-
});
|
|
18475
|
-
}
|
|
18476
|
-
function isSandboxEgressRequest(request) {
|
|
18477
|
-
return isSandboxEgressForwardedRequest(request);
|
|
18478
|
-
}
|
|
18479
|
-
|
|
18480
|
-
// src/chat/runtime/timeout-resume-runner.ts
|
|
18481
|
-
var TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
18482
|
-
function sleep4(ms) {
|
|
18483
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18484
|
-
}
|
|
18485
|
-
async function persistCompletedReplyState2(args) {
|
|
18486
|
-
const currentState = await getPersistedThreadState(
|
|
18487
|
-
args.sessionRecord.conversationId
|
|
18488
|
-
);
|
|
18489
|
-
const conversation = coerceThreadConversationState(currentState);
|
|
18490
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
18491
|
-
const userMessage2 = getTurnUserMessage(
|
|
18492
|
-
conversation,
|
|
18493
|
-
args.sessionRecord.sessionId
|
|
18494
|
-
);
|
|
18495
|
-
const statePatch = buildDeliveredTurnStatePatch({
|
|
18496
|
-
artifacts,
|
|
18497
|
-
conversation,
|
|
18498
|
-
reply: args.reply,
|
|
18499
|
-
sessionId: args.sessionRecord.sessionId,
|
|
18500
|
-
userMessageId: userMessage2?.id
|
|
18501
|
-
});
|
|
18502
|
-
await persistThreadStateById(args.sessionRecord.conversationId, {
|
|
18503
|
-
...statePatch
|
|
18504
|
-
});
|
|
18505
|
-
}
|
|
18506
|
-
async function failSessionRecordBestEffort3(args) {
|
|
18507
|
-
try {
|
|
18508
|
-
await failAgentTurnSessionRecord({
|
|
18509
|
-
conversationId: args.sessionRecord.conversationId,
|
|
18510
|
-
expectedVersion: args.sessionRecord.version,
|
|
18511
|
-
sessionId: args.sessionRecord.sessionId,
|
|
18512
|
-
errorMessage: args.errorMessage
|
|
18513
|
-
});
|
|
18514
|
-
} catch (error) {
|
|
18515
|
-
logException(
|
|
18516
|
-
error,
|
|
18517
|
-
"timeout_resume_session_record_fail_persist_failed",
|
|
18518
|
-
{},
|
|
18519
|
-
{
|
|
18520
|
-
"app.ai.conversation_id": args.sessionRecord.conversationId,
|
|
18521
|
-
"app.ai.session_id": args.sessionRecord.sessionId
|
|
18522
|
-
},
|
|
18523
|
-
"Failed to mark timed-out turn session record failed"
|
|
18524
|
-
);
|
|
18525
|
-
}
|
|
18526
|
-
}
|
|
18527
|
-
async function persistFailedReplyState2(sessionRecord) {
|
|
18528
|
-
const currentState = await getPersistedThreadState(
|
|
18529
|
-
sessionRecord.conversationId
|
|
18530
|
-
);
|
|
18531
|
-
const conversation = coerceThreadConversationState(currentState);
|
|
18532
|
-
clearPendingAuth(conversation, sessionRecord.sessionId);
|
|
18533
|
-
markTurnFailed({
|
|
18534
|
-
conversation,
|
|
18535
|
-
nowMs: Date.now(),
|
|
18536
|
-
sessionId: sessionRecord.sessionId,
|
|
18537
|
-
userMessageId: getTurnUserMessage(conversation, sessionRecord.sessionId)?.id,
|
|
18538
|
-
markConversationMessage,
|
|
18539
|
-
updateConversationStats
|
|
18540
|
-
});
|
|
18541
|
-
await failSessionRecordBestEffort3({
|
|
18542
|
-
sessionRecord,
|
|
18543
|
-
errorMessage: "Timed-out turn failed while resuming"
|
|
18544
|
-
});
|
|
18545
|
-
await persistThreadStateById(sessionRecord.conversationId, {
|
|
18546
|
-
conversation
|
|
18547
|
-
});
|
|
18548
|
-
}
|
|
18549
|
-
async function resumeTimedOutTurn(payload, options = {}) {
|
|
18550
|
-
const thread = parseSlackThreadId(payload.conversationId);
|
|
18551
|
-
if (!thread) {
|
|
18552
|
-
throw new Error(
|
|
18553
|
-
`Timeout resume requires a Slack thread conversation id, got "${payload.conversationId}"`
|
|
18554
|
-
);
|
|
18555
|
-
}
|
|
18556
|
-
const scheduleTurnTimeoutResume2 = options.scheduleTurnTimeoutResume ?? scheduleTurnTimeoutResume;
|
|
18557
|
-
return await resumeSlackTurn({
|
|
18558
|
-
messageText: "",
|
|
18559
|
-
channelId: thread.channelId,
|
|
18560
|
-
threadTs: thread.threadTs,
|
|
18561
|
-
lockKey: payload.conversationId,
|
|
18562
|
-
beforeStart: async () => {
|
|
18563
|
-
const sessionRecord = await getAgentTurnSessionRecord(
|
|
18564
|
-
payload.conversationId,
|
|
18565
|
-
payload.sessionId
|
|
18566
|
-
);
|
|
18567
|
-
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" && sessionRecord.resumeReason !== "yield" || sessionRecord.version !== payload.expectedVersion) {
|
|
18568
|
-
return false;
|
|
18569
|
-
}
|
|
18570
|
-
const currentState = await getPersistedThreadState(
|
|
18571
|
-
payload.conversationId
|
|
18572
|
-
);
|
|
18573
|
-
const conversation = coerceThreadConversationState(currentState);
|
|
18574
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
18575
|
-
const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
|
|
18576
|
-
if (!userMessage2?.author?.userId) {
|
|
18577
|
-
throw new Error(
|
|
18578
|
-
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
18579
|
-
);
|
|
18580
|
-
}
|
|
18581
|
-
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
18582
|
-
return false;
|
|
18583
|
-
}
|
|
18584
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
18585
|
-
thread.channelId
|
|
18586
|
-
);
|
|
18587
|
-
const conversationContext = buildConversationContext(conversation, {
|
|
18588
|
-
excludeMessageId: userMessage2.id
|
|
18589
|
-
});
|
|
18590
|
-
const sandbox = getPersistedSandboxState(currentState);
|
|
18591
|
-
const requester = await lookupSlackActorIdentity(
|
|
18592
|
-
userMessage2.author.userId
|
|
18593
|
-
);
|
|
18594
|
-
return {
|
|
18595
|
-
messageText: userMessage2.text,
|
|
18596
|
-
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
18597
|
-
replyContext: {
|
|
18598
|
-
credentialContext: {
|
|
18599
|
-
actor: {
|
|
18600
|
-
type: "user",
|
|
18601
|
-
userId: userMessage2.author.userId
|
|
18602
|
-
}
|
|
18603
|
-
},
|
|
18604
|
-
requester,
|
|
18605
|
-
destination: payload.destination,
|
|
18606
|
-
correlation: {
|
|
18607
|
-
conversationId: payload.conversationId,
|
|
18608
|
-
turnId: payload.sessionId,
|
|
18609
|
-
channelId: thread.channelId,
|
|
18610
|
-
threadTs: thread.threadTs,
|
|
18611
|
-
requesterId: userMessage2.author.userId
|
|
18612
|
-
},
|
|
18613
|
-
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
18614
|
-
artifactState: artifacts,
|
|
18615
|
-
pendingAuth: conversation.processing.pendingAuth,
|
|
18616
|
-
conversationContext,
|
|
18617
|
-
channelConfiguration,
|
|
18618
|
-
piMessages: conversation.piMessages,
|
|
18619
|
-
sandbox,
|
|
18620
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
18621
|
-
await applyPendingAuthUpdate({
|
|
18622
|
-
conversation,
|
|
18623
|
-
conversationId: payload.conversationId,
|
|
18624
|
-
nextPendingAuth
|
|
18625
|
-
});
|
|
18626
|
-
await persistThreadStateById(payload.conversationId, {
|
|
18627
|
-
conversation
|
|
18628
|
-
});
|
|
18629
|
-
},
|
|
18630
|
-
...getTurnUserReplyAttachmentContext(userMessage2)
|
|
18631
|
-
},
|
|
18632
|
-
onSuccess: async (reply) => {
|
|
18633
|
-
await persistCompletedReplyState2({ sessionRecord, reply });
|
|
18634
|
-
},
|
|
18635
|
-
onFailure: async () => {
|
|
18636
|
-
await persistFailedReplyState2(sessionRecord);
|
|
18637
|
-
},
|
|
18638
|
-
onPostDeliveryCommitFailure: async () => {
|
|
18639
|
-
await failAgentTurnSessionRecord({
|
|
18640
|
-
conversationId: sessionRecord.conversationId,
|
|
18641
|
-
expectedVersion: sessionRecord.version,
|
|
18642
|
-
sessionId: sessionRecord.sessionId,
|
|
18643
|
-
errorMessage: "Timed-out turn reply was delivered but completion state did not persist"
|
|
18644
|
-
});
|
|
18645
|
-
},
|
|
18646
|
-
onAuthPause: async () => {
|
|
18647
|
-
await persistAuthPauseTurnState({
|
|
18648
|
-
sessionId: payload.sessionId,
|
|
18649
|
-
threadStateId: payload.conversationId
|
|
18650
|
-
});
|
|
18651
|
-
logWarn(
|
|
18652
|
-
"timeout_resume_reparked_for_auth",
|
|
18653
|
-
{},
|
|
18654
|
-
{
|
|
18655
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
18656
|
-
"app.ai.session_id": payload.sessionId
|
|
18657
|
-
},
|
|
18658
|
-
"Resumed timed-out turn parked for auth"
|
|
18659
|
-
);
|
|
18660
|
-
},
|
|
18661
|
-
onTimeoutPause: async (error) => {
|
|
18662
|
-
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
18663
|
-
throw error;
|
|
18664
|
-
}
|
|
18665
|
-
const version = error.metadata?.version;
|
|
18666
|
-
if (typeof version !== "number") {
|
|
18667
|
-
throw new Error(
|
|
18668
|
-
"Timed-out resume turn did not include a turn-session version"
|
|
18669
|
-
);
|
|
18670
|
-
}
|
|
18671
|
-
await scheduleTurnTimeoutResume2({
|
|
18672
|
-
conversationId: payload.conversationId,
|
|
18673
|
-
destination: payload.destination,
|
|
18674
|
-
sessionId: payload.sessionId,
|
|
18675
|
-
expectedVersion: version
|
|
18676
|
-
});
|
|
18677
|
-
}
|
|
18678
|
-
};
|
|
18679
|
-
}
|
|
18680
|
-
});
|
|
18681
|
-
}
|
|
18682
|
-
async function resumeTimedOutTurnWithLockRetry(payload, options = {}) {
|
|
18683
|
-
const scheduleTurnTimeoutResume2 = options.scheduleTurnTimeoutResume ?? scheduleTurnTimeoutResume;
|
|
18684
|
-
for (const [attempt, delayMs] of [
|
|
18685
|
-
...TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS,
|
|
18686
|
-
void 0
|
|
18687
|
-
].entries()) {
|
|
18688
|
-
try {
|
|
18689
|
-
return await resumeTimedOutTurn(payload, options);
|
|
18690
|
-
} catch (error) {
|
|
18691
|
-
if (!(error instanceof ResumeTurnBusyError)) {
|
|
18692
|
-
throw error;
|
|
18693
|
-
}
|
|
18694
|
-
if (typeof delayMs !== "number") {
|
|
18695
|
-
logWarn(
|
|
18696
|
-
"timeout_resume_lock_busy",
|
|
18697
|
-
{},
|
|
18698
|
-
{
|
|
18699
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
18700
|
-
"app.ai.session_id": payload.sessionId,
|
|
18701
|
-
"app.ai.resume_lock_retry_count": attempt
|
|
18702
|
-
},
|
|
18703
|
-
"Rescheduling timeout resume because another turn still owns the thread lock"
|
|
18704
|
-
);
|
|
18705
|
-
await scheduleTurnTimeoutResume2(payload);
|
|
18706
|
-
return true;
|
|
18707
|
-
}
|
|
18708
|
-
logWarn(
|
|
18709
|
-
"timeout_resume_lock_busy_retrying",
|
|
18710
|
-
{},
|
|
18711
|
-
{
|
|
18712
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
18713
|
-
"app.ai.session_id": payload.sessionId,
|
|
18714
|
-
"app.ai.resume_lock_retry_attempt": attempt + 1,
|
|
18715
|
-
"app.ai.resume_lock_retry_delay_ms": delayMs
|
|
18716
|
-
},
|
|
18717
|
-
"Timeout resume lock was busy; retrying"
|
|
18718
|
-
);
|
|
18719
|
-
await sleep4(delayMs);
|
|
18720
|
-
}
|
|
18721
|
-
}
|
|
18722
|
-
return true;
|
|
18723
|
-
}
|
|
18724
|
-
|
|
18725
|
-
// src/handlers/turn-resume.ts
|
|
18726
|
-
async function POST2(request, waitUntil, options = {}) {
|
|
18727
|
-
const payload = await verifyTurnTimeoutResumeRequest(request);
|
|
18728
|
-
if (!payload) {
|
|
18729
|
-
return new Response("Unauthorized", { status: 401 });
|
|
18730
|
-
}
|
|
18731
|
-
waitUntil(
|
|
18732
|
-
() => runWithTurnRequestDeadline(
|
|
18733
|
-
() => resumeTimedOutTurnWithLockRetry(payload, options).catch((error) => {
|
|
18734
|
-
logException(
|
|
18735
|
-
error,
|
|
18736
|
-
"timeout_resume_handler_failed",
|
|
18737
|
-
{},
|
|
18738
|
-
{
|
|
18739
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
18740
|
-
"app.ai.session_id": payload.sessionId
|
|
18741
|
-
},
|
|
18742
|
-
"Timeout resume handler failed"
|
|
18743
|
-
);
|
|
18744
|
-
})
|
|
18745
|
-
)
|
|
18746
|
-
);
|
|
18747
|
-
return new Response("Accepted", { status: 202 });
|
|
17601
|
+
}
|
|
17602
|
+
return new Response(upstream.body, {
|
|
17603
|
+
status: upstream.status,
|
|
17604
|
+
statusText: upstream.statusText,
|
|
17605
|
+
headers: responseHeaders(upstream)
|
|
17606
|
+
});
|
|
17607
|
+
}
|
|
17608
|
+
|
|
17609
|
+
// src/handlers/sandbox-egress-proxy.ts
|
|
17610
|
+
async function ALL(request, options = {}) {
|
|
17611
|
+
return await proxySandboxEgressRequest(request, {
|
|
17612
|
+
interceptHttp: options.interceptHttp
|
|
17613
|
+
});
|
|
17614
|
+
}
|
|
17615
|
+
function isSandboxEgressRequest(request) {
|
|
17616
|
+
return isSandboxEgressForwardedRequest(request);
|
|
18748
17617
|
}
|
|
18749
17618
|
|
|
18750
17619
|
// src/chat/services/subscribed-decision.ts
|
|
@@ -19098,44 +17967,46 @@ function combineTurnText(queuedMessages, latestText) {
|
|
|
19098
17967
|
|
|
19099
17968
|
// src/chat/services/message-actor-identity.ts
|
|
19100
17969
|
var messageActors = /* @__PURE__ */ new WeakMap();
|
|
19101
|
-
function canonicalUserId(author,
|
|
17970
|
+
function canonicalUserId(author, requester) {
|
|
19102
17971
|
const authorUserId = parseActorUserId(author.userId);
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
throw new Error("Message actor identity user id mismatch");
|
|
17972
|
+
if (authorUserId && authorUserId !== requester.userId) {
|
|
17973
|
+
throw new Error("Message requester user id mismatch");
|
|
19106
17974
|
}
|
|
19107
|
-
const userId = authorUserId ??
|
|
17975
|
+
const userId = authorUserId ?? requester.userId;
|
|
19108
17976
|
if (!userId) {
|
|
19109
|
-
throw new Error("Message
|
|
17977
|
+
throw new Error("Message requester requires a user id");
|
|
19110
17978
|
}
|
|
19111
17979
|
return userId;
|
|
19112
17980
|
}
|
|
19113
|
-
function
|
|
17981
|
+
function requesterFromAuthor(author) {
|
|
19114
17982
|
const userId = parseActorUserId(author.userId);
|
|
19115
17983
|
return userId ? { userId } : void 0;
|
|
19116
17984
|
}
|
|
19117
|
-
function
|
|
19118
|
-
if (!isActorUserId(
|
|
19119
|
-
throw new Error("Message
|
|
17985
|
+
function applyRequesterToAuthor(author, requester) {
|
|
17986
|
+
if (!isActorUserId(requester.userId)) {
|
|
17987
|
+
throw new Error("Message requester requires a user id");
|
|
19120
17988
|
}
|
|
19121
|
-
author.userId =
|
|
19122
|
-
author.userName =
|
|
19123
|
-
author.fullName =
|
|
17989
|
+
author.userId = requester.userId;
|
|
17990
|
+
author.userName = requester.userName ?? "";
|
|
17991
|
+
author.fullName = requester.fullName ?? "";
|
|
19124
17992
|
}
|
|
19125
|
-
function bindMessageActorIdentity(message,
|
|
19126
|
-
const userId = canonicalUserId(message.author,
|
|
19127
|
-
const
|
|
19128
|
-
|
|
19129
|
-
|
|
17993
|
+
function bindMessageActorIdentity(message, requester) {
|
|
17994
|
+
const userId = canonicalUserId(message.author, requester);
|
|
17995
|
+
const actorRequester = createRequester(requester, {
|
|
17996
|
+
teamId: requester.teamId,
|
|
17997
|
+
userId
|
|
17998
|
+
});
|
|
17999
|
+
if (!actorRequester) {
|
|
18000
|
+
throw new Error("Message requester requires a user id");
|
|
19130
18001
|
}
|
|
19131
|
-
messageActors.set(message,
|
|
19132
|
-
|
|
19133
|
-
return
|
|
18002
|
+
messageActors.set(message, actorRequester);
|
|
18003
|
+
applyRequesterToAuthor(message.author, actorRequester);
|
|
18004
|
+
return actorRequester;
|
|
19134
18005
|
}
|
|
19135
18006
|
function getMessageActorIdentity(message) {
|
|
19136
|
-
return messageActors.get(message) ??
|
|
18007
|
+
return messageActors.get(message) ?? requesterFromAuthor(message.author);
|
|
19137
18008
|
}
|
|
19138
|
-
async function ensureSlackMessageActorIdentity(message, lookupSlackUser2) {
|
|
18009
|
+
async function ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser2) {
|
|
19139
18010
|
const existing = messageActors.get(message);
|
|
19140
18011
|
if (existing) {
|
|
19141
18012
|
return existing;
|
|
@@ -19146,7 +18017,7 @@ async function ensureSlackMessageActorIdentity(message, lookupSlackUser2) {
|
|
|
19146
18017
|
}
|
|
19147
18018
|
return bindMessageActorIdentity(
|
|
19148
18019
|
message,
|
|
19149
|
-
|
|
18020
|
+
createSlackRequester(teamId, userId, await lookupSlackUser2(teamId, userId))
|
|
19150
18021
|
);
|
|
19151
18022
|
}
|
|
19152
18023
|
|
|
@@ -20533,9 +19404,9 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
20533
19404
|
replyExecutor: {
|
|
20534
19405
|
contextCompactor: overrides.replyExecutor?.contextCompactor ?? contextCompactor,
|
|
20535
19406
|
generateAssistantReply: overrides.replyExecutor?.generateAssistantReply ?? generateAssistantReply,
|
|
20536
|
-
|
|
19407
|
+
getAwaitingAgentContinueRequest: overrides.replyExecutor?.getAwaitingAgentContinueRequest ?? getAwaitingAgentContinueRequest,
|
|
20537
19408
|
lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
|
|
20538
|
-
|
|
19409
|
+
scheduleAgentContinue: overrides.replyExecutor?.scheduleAgentContinue ?? scheduleAgentContinue,
|
|
20539
19410
|
generateThreadTitle: conversationMemory.generateThreadTitle
|
|
20540
19411
|
},
|
|
20541
19412
|
subscribedReplyPolicy: createSubscribedReplyPolicy({
|
|
@@ -20683,14 +19554,8 @@ function collectCanvasUrls(artifacts) {
|
|
|
20683
19554
|
].filter((url) => typeof url === "string" && url !== "")
|
|
20684
19555
|
);
|
|
20685
19556
|
}
|
|
20686
|
-
function turnRequester(
|
|
20687
|
-
|
|
20688
|
-
...identity.email ? { email: identity.email } : {},
|
|
20689
|
-
...identity.fullName ? { fullName: identity.fullName } : {},
|
|
20690
|
-
...identity.userId ? { slackUserId: identity.userId } : {},
|
|
20691
|
-
...identity.userName ? { slackUserName: identity.userName } : {}
|
|
20692
|
-
};
|
|
20693
|
-
return requester;
|
|
19557
|
+
function turnRequester(requester) {
|
|
19558
|
+
return toStoredSlackRequester(requester);
|
|
20694
19559
|
}
|
|
20695
19560
|
async function resolveChannelName(thread) {
|
|
20696
19561
|
const existingName = thread.channel.name?.trim();
|
|
@@ -20804,12 +19669,14 @@ function createReplyToThread(deps) {
|
|
|
20804
19669
|
(options.queuedMessages ?? []).map(
|
|
20805
19670
|
(queued) => ensureSlackMessageActorIdentity(
|
|
20806
19671
|
queued.message,
|
|
19672
|
+
teamId,
|
|
20807
19673
|
deps.services.lookupSlackUser
|
|
20808
19674
|
)
|
|
20809
19675
|
)
|
|
20810
19676
|
);
|
|
20811
19677
|
const requesterIdentity = await ensureSlackMessageActorIdentity(
|
|
20812
19678
|
message,
|
|
19679
|
+
teamId,
|
|
20813
19680
|
deps.services.lookupSlackUser
|
|
20814
19681
|
);
|
|
20815
19682
|
const requester = turnRequester(requesterIdentity);
|
|
@@ -20878,24 +19745,24 @@ function createReplyToThread(deps) {
|
|
|
20878
19745
|
return;
|
|
20879
19746
|
}
|
|
20880
19747
|
if (conversationId && activeTurnId) {
|
|
20881
|
-
const resumeRequest = await deps.services.
|
|
19748
|
+
const resumeRequest = await deps.services.getAwaitingAgentContinueRequest({
|
|
20882
19749
|
conversationId,
|
|
20883
19750
|
sessionId: activeTurnId
|
|
20884
19751
|
});
|
|
20885
19752
|
if (resumeRequest) {
|
|
20886
19753
|
try {
|
|
20887
|
-
await deps.services.
|
|
19754
|
+
await deps.services.scheduleAgentContinue(resumeRequest);
|
|
20888
19755
|
} catch (error) {
|
|
20889
19756
|
logException(
|
|
20890
19757
|
error,
|
|
20891
|
-
"
|
|
19758
|
+
"agent_continue_schedule_failed",
|
|
20892
19759
|
turnTraceContext,
|
|
20893
19760
|
{
|
|
20894
19761
|
"app.ai.resume_session_version": resumeRequest.expectedVersion,
|
|
20895
19762
|
"app.ai.resume_session_id": resumeRequest.sessionId,
|
|
20896
19763
|
...messageTs ? { "messaging.message.id": messageTs } : {}
|
|
20897
19764
|
},
|
|
20898
|
-
"Failed to reschedule active
|
|
19765
|
+
"Failed to reschedule active agent continuation"
|
|
20899
19766
|
);
|
|
20900
19767
|
throw error;
|
|
20901
19768
|
}
|
|
@@ -20923,7 +19790,7 @@ function createReplyToThread(deps) {
|
|
|
20923
19790
|
conversationId,
|
|
20924
19791
|
expectedVersion: sessionRecord.version,
|
|
20925
19792
|
sessionId: activeTurnId,
|
|
20926
|
-
errorMessage: "Awaiting
|
|
19793
|
+
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
20927
19794
|
});
|
|
20928
19795
|
markTurnFailed({
|
|
20929
19796
|
conversation: preparedState.conversation,
|
|
@@ -21448,13 +20315,13 @@ function createReplyToThread(deps) {
|
|
|
21448
20315
|
shouldPersistFailureState = false;
|
|
21449
20316
|
return;
|
|
21450
20317
|
}
|
|
21451
|
-
if (isRetryableTurnError(error, "
|
|
20318
|
+
if (isRetryableTurnError(error, "agent_continue")) {
|
|
21452
20319
|
const conversationIdForResume = error.metadata?.conversationId;
|
|
21453
20320
|
const sessionIdForResume = error.metadata?.sessionId;
|
|
21454
20321
|
const version = error.metadata?.version;
|
|
21455
20322
|
if (conversationIdForResume && sessionIdForResume && typeof version === "number" && destination) {
|
|
21456
20323
|
try {
|
|
21457
|
-
await deps.services.
|
|
20324
|
+
await deps.services.scheduleAgentContinue({
|
|
21458
20325
|
conversationId: conversationIdForResume,
|
|
21459
20326
|
destination,
|
|
21460
20327
|
sessionId: sessionIdForResume,
|
|
@@ -21464,13 +20331,13 @@ function createReplyToThread(deps) {
|
|
|
21464
20331
|
} catch (scheduleError) {
|
|
21465
20332
|
logException(
|
|
21466
20333
|
scheduleError,
|
|
21467
|
-
"
|
|
20334
|
+
"agent_continue_schedule_failed",
|
|
21468
20335
|
turnTraceContext,
|
|
21469
20336
|
{
|
|
21470
20337
|
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
21471
20338
|
"app.ai.resume_session_version": version
|
|
21472
20339
|
},
|
|
21473
|
-
"Failed to schedule
|
|
20340
|
+
"Failed to schedule agent continuation"
|
|
21474
20341
|
);
|
|
21475
20342
|
shouldPersistFailureState = true;
|
|
21476
20343
|
throw scheduleError;
|
|
@@ -21478,10 +20345,10 @@ function createReplyToThread(deps) {
|
|
|
21478
20345
|
return;
|
|
21479
20346
|
} else {
|
|
21480
20347
|
logWarn(
|
|
21481
|
-
"
|
|
20348
|
+
"agent_continue_metadata_missing",
|
|
21482
20349
|
turnTraceContext,
|
|
21483
20350
|
messageTs ? { "messaging.message.id": messageTs } : {},
|
|
21484
|
-
"
|
|
20351
|
+
"Agent continuation could not be scheduled because retry metadata was incomplete"
|
|
21485
20352
|
);
|
|
21486
20353
|
}
|
|
21487
20354
|
}
|
|
@@ -21840,6 +20707,11 @@ async function persistAssistantContextChannelId(args) {
|
|
|
21840
20707
|
artifacts: nextArtifacts
|
|
21841
20708
|
});
|
|
21842
20709
|
}
|
|
20710
|
+
function clearSkippedTurnIfActive(conversation, messageId) {
|
|
20711
|
+
if (conversation.processing.activeTurnId === buildDeterministicTurnId(messageId)) {
|
|
20712
|
+
conversation.processing.activeTurnId = void 0;
|
|
20713
|
+
}
|
|
20714
|
+
}
|
|
21843
20715
|
function createSlackRuntime(options) {
|
|
21844
20716
|
const services = createJuniorRuntimeServices(options.services);
|
|
21845
20717
|
const prepareTurnState = createPrepareTurnState({
|
|
@@ -21895,7 +20767,7 @@ function createSlackRuntime(options) {
|
|
|
21895
20767
|
skippedReason: decision.reason
|
|
21896
20768
|
}
|
|
21897
20769
|
});
|
|
21898
|
-
conversation.
|
|
20770
|
+
clearSkippedTurnIfActive(conversation, message.id);
|
|
21899
20771
|
conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
21900
20772
|
updateConversationStats(conversation);
|
|
21901
20773
|
await persistThreadState(thread, {
|
|
@@ -21904,6 +20776,7 @@ function createSlackRuntime(options) {
|
|
|
21904
20776
|
},
|
|
21905
20777
|
onSubscribedMessageSkipped: async ({
|
|
21906
20778
|
thread,
|
|
20779
|
+
message,
|
|
21907
20780
|
preparedState,
|
|
21908
20781
|
decision,
|
|
21909
20782
|
completedAtMs
|
|
@@ -21919,7 +20792,7 @@ function createSlackRuntime(options) {
|
|
|
21919
20792
|
skippedReason: decision.reason
|
|
21920
20793
|
}
|
|
21921
20794
|
);
|
|
21922
|
-
preparedState.conversation.
|
|
20795
|
+
clearSkippedTurnIfActive(preparedState.conversation, message.id);
|
|
21923
20796
|
preparedState.conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
21924
20797
|
updateConversationStats(preparedState.conversation);
|
|
21925
20798
|
await persistThreadState(thread, {
|
|
@@ -22014,7 +20887,7 @@ function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downlo
|
|
|
22014
20887
|
|
|
22015
20888
|
// src/chat/slack/adapter-context.ts
|
|
22016
20889
|
var initializedAdapters = /* @__PURE__ */ new WeakSet();
|
|
22017
|
-
async function
|
|
20890
|
+
async function getConnectedState(stateAdapter) {
|
|
22018
20891
|
const state = stateAdapter ?? getStateAdapter();
|
|
22019
20892
|
await state.connect();
|
|
22020
20893
|
return state;
|
|
@@ -22023,7 +20896,7 @@ async function ensureSlackAdapterInitialized(args) {
|
|
|
22023
20896
|
if (initializedAdapters.has(args.adapter)) {
|
|
22024
20897
|
return;
|
|
22025
20898
|
}
|
|
22026
|
-
const state = await
|
|
20899
|
+
const state = await getConnectedState(args.state);
|
|
22027
20900
|
await args.adapter.initialize({
|
|
22028
20901
|
getState: () => state
|
|
22029
20902
|
});
|
|
@@ -22086,7 +20959,7 @@ function requireSlackAuthorId(message) {
|
|
|
22086
20959
|
}
|
|
22087
20960
|
return authorId;
|
|
22088
20961
|
}
|
|
22089
|
-
function
|
|
20962
|
+
function getConnectedState2(stateAdapter) {
|
|
22090
20963
|
return stateAdapter ?? getStateAdapter();
|
|
22091
20964
|
}
|
|
22092
20965
|
function isSlackMetadata(value) {
|
|
@@ -22118,10 +20991,14 @@ async function bindSlackActorIdentities(args) {
|
|
|
22118
20991
|
}
|
|
22119
20992
|
await Promise.all(
|
|
22120
20993
|
[...byAuthorId].map(async ([authorId, messages]) => {
|
|
22121
|
-
const profile = await args.lookupSlackUser(authorId);
|
|
20994
|
+
const profile = await args.lookupSlackUser(args.teamId, authorId);
|
|
22122
20995
|
await Promise.all(
|
|
22123
20996
|
messages.map(
|
|
22124
|
-
(message) => ensureSlackMessageActorIdentity(
|
|
20997
|
+
(message) => ensureSlackMessageActorIdentity(
|
|
20998
|
+
message,
|
|
20999
|
+
args.teamId,
|
|
21000
|
+
async () => profile
|
|
21001
|
+
)
|
|
22125
21002
|
)
|
|
22126
21003
|
);
|
|
22127
21004
|
})
|
|
@@ -22147,47 +21024,6 @@ function restoreThread(args) {
|
|
|
22147
21024
|
isSubscribedContext: args.isSubscribedContext
|
|
22148
21025
|
});
|
|
22149
21026
|
}
|
|
22150
|
-
function isContinuationResume(summary) {
|
|
22151
|
-
return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield");
|
|
22152
|
-
}
|
|
22153
|
-
async function failUnresumableContinuation(args) {
|
|
22154
|
-
await failAgentTurnSessionRecord({
|
|
22155
|
-
conversationId: args.conversationId,
|
|
22156
|
-
expectedVersion: args.expectedVersion ?? args.summary.version,
|
|
22157
|
-
sessionId: args.summary.sessionId,
|
|
22158
|
-
errorMessage: args.errorMessage
|
|
22159
|
-
});
|
|
22160
|
-
}
|
|
22161
|
-
async function resumeAwaitingContinuation(conversationId) {
|
|
22162
|
-
const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
|
|
22163
|
-
for (const summary of summaries) {
|
|
22164
|
-
if (!isContinuationResume(summary)) {
|
|
22165
|
-
continue;
|
|
22166
|
-
}
|
|
22167
|
-
const request = await getAwaitingTurnContinuationRequest({
|
|
22168
|
-
conversationId,
|
|
22169
|
-
sessionId: summary.sessionId
|
|
22170
|
-
});
|
|
22171
|
-
if (!request) {
|
|
22172
|
-
await failUnresumableContinuation({
|
|
22173
|
-
conversationId,
|
|
22174
|
-
summary,
|
|
22175
|
-
errorMessage: "Awaiting turn continuation metadata could not be materialized"
|
|
22176
|
-
});
|
|
22177
|
-
continue;
|
|
22178
|
-
}
|
|
22179
|
-
if (await resumeTimedOutTurnWithLockRetry(request)) {
|
|
22180
|
-
return true;
|
|
22181
|
-
}
|
|
22182
|
-
await failUnresumableContinuation({
|
|
22183
|
-
conversationId,
|
|
22184
|
-
expectedVersion: request.expectedVersion,
|
|
22185
|
-
summary,
|
|
22186
|
-
errorMessage: "Awaiting turn continuation was stale before resuming"
|
|
22187
|
-
});
|
|
22188
|
-
}
|
|
22189
|
-
return false;
|
|
22190
|
-
}
|
|
22191
21027
|
function getInstallation(records) {
|
|
22192
21028
|
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
22193
21029
|
const metadata = records[index]?.input.metadata;
|
|
@@ -22201,18 +21037,14 @@ function getPendingRecords(work) {
|
|
|
22201
21037
|
if (!work) {
|
|
22202
21038
|
return [];
|
|
22203
21039
|
}
|
|
22204
|
-
return work.
|
|
21040
|
+
return work.execution.pendingMessages.sort(compareInboundMessages);
|
|
22205
21041
|
}
|
|
22206
21042
|
function createSlackConversationWorker(options) {
|
|
22207
21043
|
return async (context) => {
|
|
22208
21044
|
const adapter = options.getSlackAdapter();
|
|
22209
21045
|
const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
|
|
22210
|
-
const state =
|
|
21046
|
+
const state = getConnectedState2(options.state);
|
|
22211
21047
|
await state.connect();
|
|
22212
|
-
const resumeContinuation = options.resumeAwaitingContinuation ?? resumeAwaitingContinuation;
|
|
22213
|
-
if (await resumeContinuation(context.conversationId)) {
|
|
22214
|
-
return { status: "completed" };
|
|
22215
|
-
}
|
|
22216
21048
|
const records = getPendingRecords(
|
|
22217
21049
|
await getConversationWorkState({
|
|
22218
21050
|
conversationId: context.conversationId,
|
|
@@ -22220,7 +21052,7 @@ function createSlackConversationWorker(options) {
|
|
|
22220
21052
|
})
|
|
22221
21053
|
);
|
|
22222
21054
|
if (records.length === 0) {
|
|
22223
|
-
await
|
|
21055
|
+
await options.resumeAwaitingContinuation(context.conversationId);
|
|
22224
21056
|
return { status: "completed" };
|
|
22225
21057
|
}
|
|
22226
21058
|
const latestRecord = records[records.length - 1];
|
|
@@ -22246,7 +21078,8 @@ function createSlackConversationWorker(options) {
|
|
|
22246
21078
|
);
|
|
22247
21079
|
await bindSlackActorIdentities({
|
|
22248
21080
|
lookupSlackUser: actorLookup,
|
|
22249
|
-
messages
|
|
21081
|
+
messages,
|
|
21082
|
+
teamId: context.destination.teamId
|
|
22250
21083
|
});
|
|
22251
21084
|
const latestMessage = messages[messages.length - 1];
|
|
22252
21085
|
if (!latestMessage) {
|
|
@@ -22295,80 +21128,404 @@ function createSlackConversationWorker(options) {
|
|
|
22295
21128
|
const messages2 = pendingRecords.map(
|
|
22296
21129
|
(record) => restoreMessage({ adapter, record })
|
|
22297
21130
|
);
|
|
22298
|
-
restoredMessages = messages2;
|
|
22299
|
-
await inject(messages2);
|
|
22300
|
-
});
|
|
22301
|
-
return restoredMessages ?? drained.map((record) => restoreMessage({ adapter, record }));
|
|
22302
|
-
};
|
|
22303
|
-
try {
|
|
22304
|
-
if (route === "mention") {
|
|
22305
|
-
await options.runtime.handleNewMention(thread, latestMessage, {
|
|
22306
|
-
destination: context.destination,
|
|
22307
|
-
messageContext,
|
|
22308
|
-
drainSteeringMessages,
|
|
22309
|
-
onInputCommitted,
|
|
22310
|
-
shouldYield: context.shouldYield
|
|
21131
|
+
restoredMessages = messages2;
|
|
21132
|
+
await inject(messages2);
|
|
21133
|
+
});
|
|
21134
|
+
return restoredMessages ?? drained.map((record) => restoreMessage({ adapter, record }));
|
|
21135
|
+
};
|
|
21136
|
+
try {
|
|
21137
|
+
if (route === "mention") {
|
|
21138
|
+
await options.runtime.handleNewMention(thread, latestMessage, {
|
|
21139
|
+
destination: context.destination,
|
|
21140
|
+
messageContext,
|
|
21141
|
+
drainSteeringMessages,
|
|
21142
|
+
onInputCommitted,
|
|
21143
|
+
shouldYield: context.shouldYield
|
|
21144
|
+
});
|
|
21145
|
+
return;
|
|
21146
|
+
}
|
|
21147
|
+
await options.runtime.handleSubscribedMessage(thread, latestMessage, {
|
|
21148
|
+
destination: context.destination,
|
|
21149
|
+
messageContext,
|
|
21150
|
+
drainSteeringMessages,
|
|
21151
|
+
onInputCommitted,
|
|
21152
|
+
shouldYield: context.shouldYield
|
|
21153
|
+
});
|
|
21154
|
+
} catch (error) {
|
|
21155
|
+
if (isCooperativeTurnYieldError(error)) {
|
|
21156
|
+
return { status: "yielded" };
|
|
21157
|
+
}
|
|
21158
|
+
if (isTurnInputCommitLostError(error)) {
|
|
21159
|
+
return { status: "lost_lease" };
|
|
21160
|
+
}
|
|
21161
|
+
throw error;
|
|
21162
|
+
}
|
|
21163
|
+
}
|
|
21164
|
+
});
|
|
21165
|
+
if (turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
21166
|
+
return turnResult;
|
|
21167
|
+
}
|
|
21168
|
+
return { status: "completed" };
|
|
21169
|
+
};
|
|
21170
|
+
}
|
|
21171
|
+
function buildSlackInboundMessage(args) {
|
|
21172
|
+
const authorId = requireSlackAuthorId(args.message);
|
|
21173
|
+
const destination = createSlackDestination({
|
|
21174
|
+
channelId: args.thread.channelId,
|
|
21175
|
+
teamId: args.installation?.teamId
|
|
21176
|
+
});
|
|
21177
|
+
if (!destination) {
|
|
21178
|
+
throw new Error("Slack inbound message requires destination context");
|
|
21179
|
+
}
|
|
21180
|
+
return {
|
|
21181
|
+
conversationId: args.conversationId,
|
|
21182
|
+
destination,
|
|
21183
|
+
inboundMessageId: [
|
|
21184
|
+
"slack",
|
|
21185
|
+
args.installation?.teamId ?? args.installation?.enterpriseId ?? "unknown",
|
|
21186
|
+
args.conversationId,
|
|
21187
|
+
args.message.id
|
|
21188
|
+
].join(":"),
|
|
21189
|
+
source: "slack",
|
|
21190
|
+
createdAtMs: args.message.metadata.dateSent.getTime(),
|
|
21191
|
+
receivedAtMs: args.receivedAtMs,
|
|
21192
|
+
input: {
|
|
21193
|
+
text: args.message.text || " ",
|
|
21194
|
+
authorId,
|
|
21195
|
+
attachments: args.message.attachments,
|
|
21196
|
+
metadata: {
|
|
21197
|
+
platform: "slack",
|
|
21198
|
+
route: args.route,
|
|
21199
|
+
installation: args.installation,
|
|
21200
|
+
thread: args.thread.toJSON(),
|
|
21201
|
+
message: args.message.toJSON()
|
|
21202
|
+
}
|
|
21203
|
+
}
|
|
21204
|
+
};
|
|
21205
|
+
}
|
|
21206
|
+
|
|
21207
|
+
// src/chat/runtime/agent-continue-runner.ts
|
|
21208
|
+
var AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
21209
|
+
function sleep3(ms) {
|
|
21210
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21211
|
+
}
|
|
21212
|
+
async function persistCompletedReplyState2(args) {
|
|
21213
|
+
const currentState = await getPersistedThreadState(
|
|
21214
|
+
args.sessionRecord.conversationId
|
|
21215
|
+
);
|
|
21216
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
21217
|
+
const artifacts = coerceThreadArtifactsState(currentState);
|
|
21218
|
+
const userMessage2 = getTurnUserMessage(
|
|
21219
|
+
conversation,
|
|
21220
|
+
args.sessionRecord.sessionId
|
|
21221
|
+
);
|
|
21222
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
21223
|
+
artifacts,
|
|
21224
|
+
conversation,
|
|
21225
|
+
reply: args.reply,
|
|
21226
|
+
sessionId: args.sessionRecord.sessionId,
|
|
21227
|
+
userMessageId: userMessage2?.id
|
|
21228
|
+
});
|
|
21229
|
+
await persistThreadStateById(args.sessionRecord.conversationId, {
|
|
21230
|
+
...statePatch
|
|
21231
|
+
});
|
|
21232
|
+
}
|
|
21233
|
+
async function failSessionRecordBestEffort3(args) {
|
|
21234
|
+
try {
|
|
21235
|
+
await failAgentTurnSessionRecord({
|
|
21236
|
+
conversationId: args.sessionRecord.conversationId,
|
|
21237
|
+
expectedVersion: args.sessionRecord.version,
|
|
21238
|
+
sessionId: args.sessionRecord.sessionId,
|
|
21239
|
+
errorMessage: args.errorMessage
|
|
21240
|
+
});
|
|
21241
|
+
} catch (error) {
|
|
21242
|
+
logException(
|
|
21243
|
+
error,
|
|
21244
|
+
"agent_continue_session_record_fail_persist_failed",
|
|
21245
|
+
{},
|
|
21246
|
+
{
|
|
21247
|
+
"app.ai.conversation_id": args.sessionRecord.conversationId,
|
|
21248
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
21249
|
+
},
|
|
21250
|
+
"Failed to mark paused agent run session record failed"
|
|
21251
|
+
);
|
|
21252
|
+
}
|
|
21253
|
+
}
|
|
21254
|
+
async function persistFailedReplyState2(sessionRecord) {
|
|
21255
|
+
const currentState = await getPersistedThreadState(
|
|
21256
|
+
sessionRecord.conversationId
|
|
21257
|
+
);
|
|
21258
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
21259
|
+
clearPendingAuth(conversation, sessionRecord.sessionId);
|
|
21260
|
+
markTurnFailed({
|
|
21261
|
+
conversation,
|
|
21262
|
+
nowMs: Date.now(),
|
|
21263
|
+
sessionId: sessionRecord.sessionId,
|
|
21264
|
+
userMessageId: getTurnUserMessage(conversation, sessionRecord.sessionId)?.id,
|
|
21265
|
+
markConversationMessage,
|
|
21266
|
+
updateConversationStats
|
|
21267
|
+
});
|
|
21268
|
+
await failSessionRecordBestEffort3({
|
|
21269
|
+
sessionRecord,
|
|
21270
|
+
errorMessage: "Paused agent run failed while continuing"
|
|
21271
|
+
});
|
|
21272
|
+
await persistThreadStateById(sessionRecord.conversationId, {
|
|
21273
|
+
conversation
|
|
21274
|
+
});
|
|
21275
|
+
}
|
|
21276
|
+
async function failContinuationStartup(args) {
|
|
21277
|
+
try {
|
|
21278
|
+
await persistFailedReplyState2(args.sessionRecord);
|
|
21279
|
+
} catch (persistError) {
|
|
21280
|
+
await failSessionRecordBestEffort3({
|
|
21281
|
+
sessionRecord: args.sessionRecord,
|
|
21282
|
+
errorMessage: "Paused agent run failed while preparing continuation"
|
|
21283
|
+
});
|
|
21284
|
+
logException(
|
|
21285
|
+
persistError,
|
|
21286
|
+
"agent_continue_startup_failure_persist_failed",
|
|
21287
|
+
{},
|
|
21288
|
+
{
|
|
21289
|
+
"app.ai.conversation_id": args.sessionRecord.conversationId,
|
|
21290
|
+
"app.ai.session_id": args.sessionRecord.sessionId
|
|
21291
|
+
},
|
|
21292
|
+
"Failed to persist paused agent run startup failure"
|
|
21293
|
+
);
|
|
21294
|
+
}
|
|
21295
|
+
}
|
|
21296
|
+
function isContinuationResume(summary) {
|
|
21297
|
+
return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield");
|
|
21298
|
+
}
|
|
21299
|
+
async function failUnresumableContinuation(args) {
|
|
21300
|
+
await failAgentTurnSessionRecord({
|
|
21301
|
+
conversationId: args.conversationId,
|
|
21302
|
+
expectedVersion: args.expectedVersion ?? args.summary.version,
|
|
21303
|
+
sessionId: args.summary.sessionId,
|
|
21304
|
+
errorMessage: args.errorMessage
|
|
21305
|
+
});
|
|
21306
|
+
}
|
|
21307
|
+
async function continueSlackAgentRun(payload, options = {}) {
|
|
21308
|
+
const thread = parseSlackThreadId(payload.conversationId);
|
|
21309
|
+
if (!thread) {
|
|
21310
|
+
throw new Error(
|
|
21311
|
+
`Agent continuation requires a Slack thread conversation id, got "${payload.conversationId}"`
|
|
21312
|
+
);
|
|
21313
|
+
}
|
|
21314
|
+
const scheduleAgentContinue2 = options.scheduleAgentContinue ?? scheduleAgentContinue;
|
|
21315
|
+
const resumeTurn = options.resumeTurn ?? resumeSlackTurn;
|
|
21316
|
+
return await resumeTurn({
|
|
21317
|
+
messageText: "",
|
|
21318
|
+
channelId: thread.channelId,
|
|
21319
|
+
threadTs: thread.threadTs,
|
|
21320
|
+
lockKey: payload.conversationId,
|
|
21321
|
+
generateReply: options.generateReply,
|
|
21322
|
+
beforeStart: async () => {
|
|
21323
|
+
let sessionRecord;
|
|
21324
|
+
try {
|
|
21325
|
+
sessionRecord = await getAgentTurnSessionRecord(
|
|
21326
|
+
payload.conversationId,
|
|
21327
|
+
payload.sessionId
|
|
21328
|
+
);
|
|
21329
|
+
if (!sessionRecord || sessionRecord.state !== "awaiting_resume" || sessionRecord.resumeReason !== "timeout" && sessionRecord.resumeReason !== "yield" || sessionRecord.version !== payload.expectedVersion) {
|
|
21330
|
+
return false;
|
|
21331
|
+
}
|
|
21332
|
+
const activeSessionRecord = sessionRecord;
|
|
21333
|
+
const currentState = await getPersistedThreadState(
|
|
21334
|
+
payload.conversationId
|
|
21335
|
+
);
|
|
21336
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
21337
|
+
const artifacts = coerceThreadArtifactsState(currentState);
|
|
21338
|
+
const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
|
|
21339
|
+
if (!userMessage2?.author?.userId) {
|
|
21340
|
+
throw new Error(
|
|
21341
|
+
`Unable to locate the persisted user message for agent continuation session "${payload.sessionId}"`
|
|
21342
|
+
);
|
|
21343
|
+
}
|
|
21344
|
+
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
21345
|
+
return false;
|
|
21346
|
+
}
|
|
21347
|
+
const channelConfiguration = getChannelConfigurationServiceById(
|
|
21348
|
+
thread.channelId
|
|
21349
|
+
);
|
|
21350
|
+
const conversationContext = buildConversationContext(conversation, {
|
|
21351
|
+
excludeMessageId: userMessage2.id
|
|
21352
|
+
});
|
|
21353
|
+
const sandbox = getPersistedSandboxState(currentState);
|
|
21354
|
+
const requester = createRequesterFromStoredSlackRequester({
|
|
21355
|
+
requester: activeSessionRecord.requester,
|
|
21356
|
+
teamId: payload.destination.teamId,
|
|
21357
|
+
userId: userMessage2.author.userId
|
|
21358
|
+
});
|
|
21359
|
+
return {
|
|
21360
|
+
messageText: userMessage2.text,
|
|
21361
|
+
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
21362
|
+
replyContext: {
|
|
21363
|
+
credentialContext: {
|
|
21364
|
+
actor: {
|
|
21365
|
+
type: "user",
|
|
21366
|
+
userId: requester.userId
|
|
21367
|
+
}
|
|
21368
|
+
},
|
|
21369
|
+
requester,
|
|
21370
|
+
destination: payload.destination,
|
|
21371
|
+
correlation: {
|
|
21372
|
+
conversationId: payload.conversationId,
|
|
21373
|
+
turnId: payload.sessionId,
|
|
21374
|
+
channelId: thread.channelId,
|
|
21375
|
+
threadTs: thread.threadTs,
|
|
21376
|
+
requesterId: requester.userId
|
|
21377
|
+
},
|
|
21378
|
+
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
21379
|
+
artifactState: artifacts,
|
|
21380
|
+
pendingAuth: conversation.processing.pendingAuth,
|
|
21381
|
+
conversationContext,
|
|
21382
|
+
channelConfiguration,
|
|
21383
|
+
piMessages: conversation.piMessages,
|
|
21384
|
+
sandbox,
|
|
21385
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
21386
|
+
await applyPendingAuthUpdate({
|
|
21387
|
+
conversation,
|
|
21388
|
+
conversationId: payload.conversationId,
|
|
21389
|
+
nextPendingAuth
|
|
21390
|
+
});
|
|
21391
|
+
await persistThreadStateById(payload.conversationId, {
|
|
21392
|
+
conversation
|
|
21393
|
+
});
|
|
21394
|
+
},
|
|
21395
|
+
...getTurnUserReplyAttachmentContext(userMessage2)
|
|
21396
|
+
},
|
|
21397
|
+
onSuccess: async (reply) => {
|
|
21398
|
+
await persistCompletedReplyState2({
|
|
21399
|
+
sessionRecord: activeSessionRecord,
|
|
21400
|
+
reply
|
|
21401
|
+
});
|
|
21402
|
+
},
|
|
21403
|
+
onFailure: async () => {
|
|
21404
|
+
await persistFailedReplyState2(activeSessionRecord);
|
|
21405
|
+
},
|
|
21406
|
+
onPostDeliveryCommitFailure: async () => {
|
|
21407
|
+
await failAgentTurnSessionRecord({
|
|
21408
|
+
conversationId: activeSessionRecord.conversationId,
|
|
21409
|
+
expectedVersion: activeSessionRecord.version,
|
|
21410
|
+
sessionId: activeSessionRecord.sessionId,
|
|
21411
|
+
errorMessage: "Continued agent reply was delivered but completion state did not persist"
|
|
21412
|
+
});
|
|
21413
|
+
},
|
|
21414
|
+
onAuthPause: async () => {
|
|
21415
|
+
await persistAuthPauseTurnState({
|
|
21416
|
+
sessionId: payload.sessionId,
|
|
21417
|
+
threadStateId: payload.conversationId
|
|
21418
|
+
});
|
|
21419
|
+
logWarn(
|
|
21420
|
+
"agent_continue_reparked_for_auth",
|
|
21421
|
+
{},
|
|
21422
|
+
{
|
|
21423
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
21424
|
+
"app.ai.session_id": payload.sessionId
|
|
21425
|
+
},
|
|
21426
|
+
"Continued agent run parked for auth"
|
|
21427
|
+
);
|
|
21428
|
+
},
|
|
21429
|
+
onTimeoutPause: async (error) => {
|
|
21430
|
+
if (!isRetryableTurnError(error, "agent_continue")) {
|
|
21431
|
+
throw error;
|
|
21432
|
+
}
|
|
21433
|
+
const version = error.metadata?.version;
|
|
21434
|
+
if (typeof version !== "number") {
|
|
21435
|
+
throw new Error(
|
|
21436
|
+
"Agent continuation did not include a session record version"
|
|
21437
|
+
);
|
|
21438
|
+
}
|
|
21439
|
+
await scheduleAgentContinue2({
|
|
21440
|
+
conversationId: payload.conversationId,
|
|
21441
|
+
destination: payload.destination,
|
|
21442
|
+
sessionId: payload.sessionId,
|
|
21443
|
+
expectedVersion: version
|
|
22311
21444
|
});
|
|
22312
|
-
return;
|
|
22313
21445
|
}
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
shouldYield: context.shouldYield
|
|
21446
|
+
};
|
|
21447
|
+
} catch (error) {
|
|
21448
|
+
if (sessionRecord) {
|
|
21449
|
+
await failContinuationStartup({
|
|
21450
|
+
sessionRecord
|
|
22320
21451
|
});
|
|
22321
|
-
} catch (error) {
|
|
22322
|
-
if (isCooperativeTurnYieldError(error)) {
|
|
22323
|
-
return { status: "yielded" };
|
|
22324
|
-
}
|
|
22325
|
-
if (isTurnInputCommitLostError(error)) {
|
|
22326
|
-
return { status: "lost_lease" };
|
|
22327
|
-
}
|
|
22328
|
-
throw error;
|
|
22329
21452
|
}
|
|
21453
|
+
throw error;
|
|
22330
21454
|
}
|
|
22331
|
-
});
|
|
22332
|
-
if (turnResult?.status === "yielded" || turnResult?.status === "lost_lease") {
|
|
22333
|
-
return turnResult;
|
|
22334
21455
|
}
|
|
22335
|
-
return { status: "completed" };
|
|
22336
|
-
};
|
|
22337
|
-
}
|
|
22338
|
-
function buildSlackInboundMessage(args) {
|
|
22339
|
-
const authorId = requireSlackAuthorId(args.message);
|
|
22340
|
-
const destination = createSlackDestination({
|
|
22341
|
-
channelId: args.thread.channelId,
|
|
22342
|
-
teamId: args.installation?.teamId
|
|
22343
21456
|
});
|
|
22344
|
-
|
|
22345
|
-
|
|
21457
|
+
}
|
|
21458
|
+
async function resumeAwaitingSlackContinuation(conversationId) {
|
|
21459
|
+
const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
|
|
21460
|
+
for (const summary of summaries) {
|
|
21461
|
+
if (!isContinuationResume(summary)) {
|
|
21462
|
+
continue;
|
|
21463
|
+
}
|
|
21464
|
+
const request = await getAwaitingAgentContinueRequest({
|
|
21465
|
+
conversationId,
|
|
21466
|
+
sessionId: summary.sessionId
|
|
21467
|
+
});
|
|
21468
|
+
if (!request) {
|
|
21469
|
+
await failUnresumableContinuation({
|
|
21470
|
+
conversationId,
|
|
21471
|
+
summary,
|
|
21472
|
+
errorMessage: "Awaiting agent continuation metadata could not be materialized"
|
|
21473
|
+
});
|
|
21474
|
+
continue;
|
|
21475
|
+
}
|
|
21476
|
+
if (await continueSlackAgentRunWithLockRetry(request)) {
|
|
21477
|
+
return true;
|
|
21478
|
+
}
|
|
21479
|
+
await failUnresumableContinuation({
|
|
21480
|
+
conversationId,
|
|
21481
|
+
expectedVersion: request.expectedVersion,
|
|
21482
|
+
summary,
|
|
21483
|
+
errorMessage: "Awaiting agent continuation was stale before it could run"
|
|
21484
|
+
});
|
|
22346
21485
|
}
|
|
22347
|
-
return
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
21486
|
+
return false;
|
|
21487
|
+
}
|
|
21488
|
+
async function continueSlackAgentRunWithLockRetry(payload, options = {}) {
|
|
21489
|
+
const scheduleAgentContinue2 = options.scheduleAgentContinue ?? scheduleAgentContinue;
|
|
21490
|
+
for (const [attempt, delayMs] of [
|
|
21491
|
+
...AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS,
|
|
21492
|
+
void 0
|
|
21493
|
+
].entries()) {
|
|
21494
|
+
try {
|
|
21495
|
+
return await continueSlackAgentRun(payload, options);
|
|
21496
|
+
} catch (error) {
|
|
21497
|
+
if (!(error instanceof ResumeTurnBusyError)) {
|
|
21498
|
+
throw error;
|
|
21499
|
+
}
|
|
21500
|
+
if (typeof delayMs !== "number") {
|
|
21501
|
+
logWarn(
|
|
21502
|
+
"agent_continue_lock_busy",
|
|
21503
|
+
{},
|
|
21504
|
+
{
|
|
21505
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
21506
|
+
"app.ai.session_id": payload.sessionId,
|
|
21507
|
+
"app.ai.resume_lock_retry_count": attempt
|
|
21508
|
+
},
|
|
21509
|
+
"Rescheduling agent continuation because another run still owns the thread lock"
|
|
21510
|
+
);
|
|
21511
|
+
await scheduleAgentContinue2(payload);
|
|
21512
|
+
return true;
|
|
22369
21513
|
}
|
|
21514
|
+
logWarn(
|
|
21515
|
+
"agent_continue_lock_busy_retrying",
|
|
21516
|
+
{},
|
|
21517
|
+
{
|
|
21518
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
21519
|
+
"app.ai.session_id": payload.sessionId,
|
|
21520
|
+
"app.ai.resume_lock_retry_attempt": attempt + 1,
|
|
21521
|
+
"app.ai.resume_lock_retry_delay_ms": delayMs
|
|
21522
|
+
},
|
|
21523
|
+
"Agent continuation lock was busy; retrying"
|
|
21524
|
+
);
|
|
21525
|
+
await sleep3(delayMs);
|
|
22370
21526
|
}
|
|
22371
|
-
}
|
|
21527
|
+
}
|
|
21528
|
+
return true;
|
|
22372
21529
|
}
|
|
22373
21530
|
|
|
22374
21531
|
// src/chat/app/production.ts
|
|
@@ -22414,6 +21571,7 @@ function getProductionConversationWorkOptions() {
|
|
|
22414
21571
|
queue: getVercelConversationWorkQueue(),
|
|
22415
21572
|
run: createSlackConversationWorker({
|
|
22416
21573
|
getSlackAdapter: getProductionSlackAdapter,
|
|
21574
|
+
resumeAwaitingContinuation: resumeAwaitingSlackContinuation,
|
|
22417
21575
|
runtime
|
|
22418
21576
|
})
|
|
22419
21577
|
};
|
|
@@ -22883,13 +22041,16 @@ async function handleSlashCommandForm(args) {
|
|
|
22883
22041
|
args.params.get("user_id"),
|
|
22884
22042
|
"Slack slash command payload"
|
|
22885
22043
|
);
|
|
22886
|
-
const
|
|
22044
|
+
const teamId = args.params.get("team_id") ?? void 0;
|
|
22045
|
+
const userIdentity = createRequester(
|
|
22887
22046
|
{
|
|
22047
|
+
platform: "slack",
|
|
22048
|
+
teamId,
|
|
22888
22049
|
userId,
|
|
22889
22050
|
userName: args.params.get("user_name") ?? void 0,
|
|
22890
22051
|
fullName: args.params.get("user_name") ?? void 0
|
|
22891
22052
|
},
|
|
22892
|
-
userId
|
|
22053
|
+
{ teamId, userId }
|
|
22893
22054
|
);
|
|
22894
22055
|
if (!userIdentity?.userId) {
|
|
22895
22056
|
throw new Error("Slack slash command payload actor identity is invalid");
|
|
@@ -23248,7 +22409,7 @@ async function handlePlatformWebhook(request, platform, waitUntil, legacyBot) {
|
|
|
23248
22409
|
}
|
|
23249
22410
|
});
|
|
23250
22411
|
}
|
|
23251
|
-
async function
|
|
22412
|
+
async function POST2(request, platform, waitUntil) {
|
|
23252
22413
|
return handlePlatformWebhook(request, platform, waitUntil);
|
|
23253
22414
|
}
|
|
23254
22415
|
|
|
@@ -23277,7 +22438,7 @@ function isConversationQueueMessageRejectedError(error) {
|
|
|
23277
22438
|
// src/chat/task-execution/worker.ts
|
|
23278
22439
|
var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
|
|
23279
22440
|
var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
|
|
23280
|
-
function
|
|
22441
|
+
function now(options) {
|
|
23281
22442
|
return options.nowMs?.() ?? Date.now();
|
|
23282
22443
|
}
|
|
23283
22444
|
function nudgeIdempotencyKey(reason, conversationId, nowMs) {
|
|
@@ -23294,21 +22455,11 @@ async function sendWakeNudge(args) {
|
|
|
23294
22455
|
idempotencyKey: args.idempotencyKey
|
|
23295
22456
|
}
|
|
23296
22457
|
);
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
});
|
|
23303
|
-
} catch (error) {
|
|
23304
|
-
logException(
|
|
23305
|
-
error,
|
|
23306
|
-
"conversation_work_enqueue_marker_failed",
|
|
23307
|
-
{ conversationId: args.conversationId },
|
|
23308
|
-
{},
|
|
23309
|
-
"Conversation work enqueue marker failed after queue acceptance"
|
|
23310
|
-
);
|
|
23311
|
-
}
|
|
22458
|
+
await markConversationWorkEnqueued({
|
|
22459
|
+
conversationId: args.conversationId,
|
|
22460
|
+
nowMs: args.nowMs,
|
|
22461
|
+
state: args.options.state
|
|
22462
|
+
});
|
|
23312
22463
|
}
|
|
23313
22464
|
async function requestLostLeaseRecovery(args) {
|
|
23314
22465
|
const continuationMarked = await requestConversationContinuation({
|
|
@@ -23344,7 +22495,7 @@ async function requestLostLeaseRecovery(args) {
|
|
|
23344
22495
|
}
|
|
23345
22496
|
function startLeaseCheckIn(args) {
|
|
23346
22497
|
const timer = setInterval(() => {
|
|
23347
|
-
const nowMs =
|
|
22498
|
+
const nowMs = now(args.options);
|
|
23348
22499
|
void checkInConversationWork({
|
|
23349
22500
|
conversationId: args.conversationId,
|
|
23350
22501
|
leaseToken: args.leaseToken,
|
|
@@ -23382,10 +22533,10 @@ async function processConversationWork(message, options) {
|
|
|
23382
22533
|
conversationId,
|
|
23383
22534
|
state: options.state
|
|
23384
22535
|
});
|
|
23385
|
-
if (!initial || countPendingConversationMessages(initial) === 0 &&
|
|
22536
|
+
if (!initial || countPendingConversationMessages(initial) === 0 && initial.execution.status === "idle" && !initial.execution.lease) {
|
|
23386
22537
|
return { status: "no_work" };
|
|
23387
22538
|
}
|
|
23388
|
-
if (!sameDestination(initial.destination, message.destination)) {
|
|
22539
|
+
if (!initial.destination || !sameDestination(initial.destination, message.destination)) {
|
|
23389
22540
|
throw new ConversationQueueMessageRejectedError(
|
|
23390
22541
|
"destination_mismatch",
|
|
23391
22542
|
`Conversation work queue destination changed for ${conversationId}`,
|
|
@@ -23393,50 +22544,24 @@ async function processConversationWork(message, options) {
|
|
|
23393
22544
|
);
|
|
23394
22545
|
}
|
|
23395
22546
|
const destination = initial.destination;
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
|
|
23400
|
-
|
|
23401
|
-
state: options.state
|
|
23402
|
-
});
|
|
23403
|
-
} catch (error) {
|
|
23404
|
-
logException(
|
|
23405
|
-
error,
|
|
23406
|
-
"conversation_work_lease_acquire_failed",
|
|
23407
|
-
{ conversationId },
|
|
23408
|
-
{},
|
|
23409
|
-
"Conversation work lease acquisition failed; heartbeat will recover"
|
|
23410
|
-
);
|
|
23411
|
-
return { status: "no_work" };
|
|
23412
|
-
}
|
|
22547
|
+
const lease = await startConversationWork({
|
|
22548
|
+
conversationId,
|
|
22549
|
+
nowMs: now(options),
|
|
22550
|
+
state: options.state
|
|
22551
|
+
});
|
|
23413
22552
|
if (lease.status === "no_work") {
|
|
23414
22553
|
return { status: "no_work" };
|
|
23415
22554
|
}
|
|
23416
22555
|
if (lease.status === "active") {
|
|
23417
|
-
const nudgeNowMs =
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
|
|
23421
|
-
|
|
23422
|
-
|
|
23423
|
-
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
nudgeNowMs
|
|
23427
|
-
),
|
|
23428
|
-
nowMs: nudgeNowMs,
|
|
23429
|
-
options
|
|
23430
|
-
});
|
|
23431
|
-
} catch (error) {
|
|
23432
|
-
logException(
|
|
23433
|
-
error,
|
|
23434
|
-
"conversation_work_active_nudge_failed",
|
|
23435
|
-
{ conversationId },
|
|
23436
|
-
{},
|
|
23437
|
-
"Conversation work active-lease nudge failed; heartbeat will recover"
|
|
23438
|
-
);
|
|
23439
|
-
}
|
|
22556
|
+
const nudgeNowMs = now(options);
|
|
22557
|
+
await sendWakeNudge({
|
|
22558
|
+
conversationId,
|
|
22559
|
+
destination,
|
|
22560
|
+
delayMs: CONVERSATION_WORK_DEFER_DELAY_MS,
|
|
22561
|
+
idempotencyKey: nudgeIdempotencyKey("active", conversationId, nudgeNowMs),
|
|
22562
|
+
nowMs: nudgeNowMs,
|
|
22563
|
+
options
|
|
22564
|
+
});
|
|
23440
22565
|
logInfo(
|
|
23441
22566
|
"conversation_work_nudge_deferred_for_active_lease",
|
|
23442
22567
|
{ conversationId },
|
|
@@ -23447,7 +22572,7 @@ async function processConversationWork(message, options) {
|
|
|
23447
22572
|
);
|
|
23448
22573
|
return { status: "active" };
|
|
23449
22574
|
}
|
|
23450
|
-
const startedAtMs =
|
|
22575
|
+
const startedAtMs = now(options);
|
|
23451
22576
|
const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
|
|
23452
22577
|
let leaseLost = false;
|
|
23453
22578
|
const markLeaseLost = () => {
|
|
@@ -23472,12 +22597,12 @@ async function processConversationWork(message, options) {
|
|
|
23472
22597
|
conversationId,
|
|
23473
22598
|
destination,
|
|
23474
22599
|
leaseToken: lease.leaseToken,
|
|
23475
|
-
shouldYield: () => leaseLost ||
|
|
22600
|
+
shouldYield: () => leaseLost || now(options) >= softYieldDeadlineMs,
|
|
23476
22601
|
checkIn: async () => {
|
|
23477
22602
|
const checkedIn = await checkInConversationWork({
|
|
23478
22603
|
conversationId,
|
|
23479
22604
|
leaseToken: lease.leaseToken,
|
|
23480
|
-
nowMs:
|
|
22605
|
+
nowMs: now(options),
|
|
23481
22606
|
state: options.state
|
|
23482
22607
|
});
|
|
23483
22608
|
if (!checkedIn) {
|
|
@@ -23489,7 +22614,7 @@ async function processConversationWork(message, options) {
|
|
|
23489
22614
|
conversationId,
|
|
23490
22615
|
leaseToken: lease.leaseToken,
|
|
23491
22616
|
inject,
|
|
23492
|
-
nowMs:
|
|
22617
|
+
nowMs: now(options),
|
|
23493
22618
|
state: options.state
|
|
23494
22619
|
})
|
|
23495
22620
|
};
|
|
@@ -23500,7 +22625,7 @@ async function processConversationWork(message, options) {
|
|
|
23500
22625
|
conversationId,
|
|
23501
22626
|
destination,
|
|
23502
22627
|
leaseToken: lease.leaseToken,
|
|
23503
|
-
nowMs:
|
|
22628
|
+
nowMs: now(options),
|
|
23504
22629
|
options
|
|
23505
22630
|
});
|
|
23506
22631
|
return { status: "lost_lease" };
|
|
@@ -23510,13 +22635,13 @@ async function processConversationWork(message, options) {
|
|
|
23510
22635
|
conversationId,
|
|
23511
22636
|
destination,
|
|
23512
22637
|
leaseToken: lease.leaseToken,
|
|
23513
|
-
nowMs:
|
|
22638
|
+
nowMs: now(options),
|
|
23514
22639
|
options
|
|
23515
22640
|
});
|
|
23516
22641
|
return { status: "lost_lease" };
|
|
23517
22642
|
}
|
|
23518
22643
|
if (result.status === "yielded") {
|
|
23519
|
-
const yieldNowMs =
|
|
22644
|
+
const yieldNowMs = now(options);
|
|
23520
22645
|
const continuationMarked = await requestConversationContinuation({
|
|
23521
22646
|
conversationId,
|
|
23522
22647
|
destination,
|
|
@@ -23548,7 +22673,7 @@ async function processConversationWork(message, options) {
|
|
|
23548
22673
|
"conversation_work_cooperative_yield",
|
|
23549
22674
|
{ conversationId },
|
|
23550
22675
|
{
|
|
23551
|
-
"app.worker.elapsed_ms":
|
|
22676
|
+
"app.worker.elapsed_ms": now(options) - startedAtMs,
|
|
23552
22677
|
"app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
|
|
23553
22678
|
},
|
|
23554
22679
|
"Conversation work yielded cooperatively"
|
|
@@ -23558,14 +22683,14 @@ async function processConversationWork(message, options) {
|
|
|
23558
22683
|
const completion = await completeConversationWork({
|
|
23559
22684
|
conversationId,
|
|
23560
22685
|
leaseToken: lease.leaseToken,
|
|
23561
|
-
nowMs:
|
|
22686
|
+
nowMs: now(options),
|
|
23562
22687
|
state: options.state
|
|
23563
22688
|
});
|
|
23564
22689
|
if (completion === "lost_lease") {
|
|
23565
22690
|
return { status: "lost_lease" };
|
|
23566
22691
|
}
|
|
23567
22692
|
if (completion === "pending") {
|
|
23568
|
-
const nudgeNowMs =
|
|
22693
|
+
const nudgeNowMs = now(options);
|
|
23569
22694
|
await sendWakeNudge({
|
|
23570
22695
|
conversationId,
|
|
23571
22696
|
destination,
|
|
@@ -23583,104 +22708,42 @@ async function processConversationWork(message, options) {
|
|
|
23583
22708
|
"conversation_work_completed",
|
|
23584
22709
|
{ conversationId },
|
|
23585
22710
|
{
|
|
23586
|
-
"app.worker.elapsed_ms":
|
|
22711
|
+
"app.worker.elapsed_ms": now(options) - startedAtMs
|
|
23587
22712
|
},
|
|
23588
22713
|
"Conversation work completed"
|
|
23589
22714
|
);
|
|
23590
22715
|
return { status: "completed" };
|
|
23591
22716
|
} catch (error) {
|
|
23592
|
-
const errorNowMs =
|
|
23593
|
-
let failure2;
|
|
22717
|
+
const errorNowMs = now(options);
|
|
23594
22718
|
try {
|
|
23595
|
-
|
|
22719
|
+
const continuationMarked = await requestConversationContinuation({
|
|
23596
22720
|
conversationId,
|
|
22721
|
+
destination,
|
|
22722
|
+
leaseToken: lease.leaseToken,
|
|
23597
22723
|
nowMs: errorNowMs,
|
|
23598
22724
|
state: options.state
|
|
23599
22725
|
});
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
recordError,
|
|
23603
|
-
"conversation_work_failure_record_failed",
|
|
23604
|
-
{ conversationId },
|
|
23605
|
-
{},
|
|
23606
|
-
"Conversation work failure counter update failed"
|
|
23607
|
-
);
|
|
23608
|
-
}
|
|
23609
|
-
if (!isProviderRetryError(error)) {
|
|
23610
|
-
logException(
|
|
23611
|
-
error,
|
|
23612
|
-
"conversation_work_failed",
|
|
23613
|
-
{ conversationId },
|
|
23614
|
-
{
|
|
23615
|
-
"app.worker.consecutive_failure_count": failure2?.consecutiveFailureCount ?? null,
|
|
23616
|
-
"app.worker.elapsed_ms": now2(options) - startedAtMs
|
|
23617
|
-
},
|
|
23618
|
-
"Conversation work failed"
|
|
23619
|
-
);
|
|
23620
|
-
}
|
|
23621
|
-
if (failure2?.abandoned) {
|
|
23622
|
-
logWarn(
|
|
23623
|
-
"conversation_work_abandoned",
|
|
23624
|
-
{ conversationId },
|
|
23625
|
-
{
|
|
23626
|
-
"app.worker.consecutive_failure_count": failure2.consecutiveFailureCount,
|
|
23627
|
-
"app.worker.max_consecutive_failures": CONVERSATION_WORK_MAX_CONSECUTIVE_FAILURES
|
|
23628
|
-
},
|
|
23629
|
-
"Conversation work abandoned after repeated failures; stopping retries"
|
|
23630
|
-
);
|
|
23631
|
-
if (!failure2.releasedLease) {
|
|
23632
|
-
try {
|
|
23633
|
-
await releaseConversationWork({
|
|
23634
|
-
conversationId,
|
|
23635
|
-
leaseToken: lease.leaseToken,
|
|
23636
|
-
nowMs: errorNowMs,
|
|
23637
|
-
state: options.state
|
|
23638
|
-
});
|
|
23639
|
-
} catch (releaseError) {
|
|
23640
|
-
logException(
|
|
23641
|
-
releaseError,
|
|
23642
|
-
"conversation_work_release_failed",
|
|
23643
|
-
{ conversationId },
|
|
23644
|
-
{},
|
|
23645
|
-
"Conversation work release failed after abandoning"
|
|
23646
|
-
);
|
|
23647
|
-
}
|
|
23648
|
-
}
|
|
23649
|
-
return { status: "abandoned" };
|
|
23650
|
-
}
|
|
23651
|
-
let requeueSucceeded = false;
|
|
23652
|
-
if (failure2) {
|
|
23653
|
-
try {
|
|
23654
|
-
const continuationMarked = await requestConversationContinuation({
|
|
22726
|
+
if (continuationMarked) {
|
|
22727
|
+
await sendWakeNudge({
|
|
23655
22728
|
conversationId,
|
|
23656
22729
|
destination,
|
|
23657
|
-
|
|
22730
|
+
idempotencyKey: nudgeIdempotencyKey(
|
|
22731
|
+
"error",
|
|
22732
|
+
conversationId,
|
|
22733
|
+
errorNowMs
|
|
22734
|
+
),
|
|
23658
22735
|
nowMs: errorNowMs,
|
|
23659
|
-
|
|
22736
|
+
options
|
|
23660
22737
|
});
|
|
23661
|
-
if (continuationMarked) {
|
|
23662
|
-
await sendWakeNudge({
|
|
23663
|
-
conversationId,
|
|
23664
|
-
destination,
|
|
23665
|
-
idempotencyKey: nudgeIdempotencyKey(
|
|
23666
|
-
"error",
|
|
23667
|
-
conversationId,
|
|
23668
|
-
errorNowMs
|
|
23669
|
-
),
|
|
23670
|
-
nowMs: errorNowMs,
|
|
23671
|
-
options
|
|
23672
|
-
});
|
|
23673
|
-
requeueSucceeded = true;
|
|
23674
|
-
}
|
|
23675
|
-
} catch (requeueError) {
|
|
23676
|
-
logException(
|
|
23677
|
-
requeueError,
|
|
23678
|
-
"conversation_work_requeue_failed",
|
|
23679
|
-
{ conversationId },
|
|
23680
|
-
{},
|
|
23681
|
-
"Conversation work requeue failed after runner error"
|
|
23682
|
-
);
|
|
23683
22738
|
}
|
|
22739
|
+
} catch (requeueError) {
|
|
22740
|
+
logException(
|
|
22741
|
+
requeueError,
|
|
22742
|
+
"conversation_work_requeue_failed",
|
|
22743
|
+
{ conversationId },
|
|
22744
|
+
{},
|
|
22745
|
+
"Conversation work requeue failed after runner error"
|
|
22746
|
+
);
|
|
23684
22747
|
}
|
|
23685
22748
|
try {
|
|
23686
22749
|
await releaseConversationWork({
|
|
@@ -23698,8 +22761,16 @@ async function processConversationWork(message, options) {
|
|
|
23698
22761
|
"Conversation work release failed after runner error"
|
|
23699
22762
|
);
|
|
23700
22763
|
}
|
|
23701
|
-
if (
|
|
23702
|
-
|
|
22764
|
+
if (!isProviderRetryError(error)) {
|
|
22765
|
+
logException(
|
|
22766
|
+
error,
|
|
22767
|
+
"conversation_work_failed",
|
|
22768
|
+
{ conversationId },
|
|
22769
|
+
{
|
|
22770
|
+
"app.worker.elapsed_ms": now(options) - startedAtMs
|
|
22771
|
+
},
|
|
22772
|
+
"Conversation work failed"
|
|
22773
|
+
);
|
|
23703
22774
|
}
|
|
23704
22775
|
throw error;
|
|
23705
22776
|
} finally {
|
|
@@ -24044,9 +23115,6 @@ async function createApp(options) {
|
|
|
24044
23115
|
app.get("/api/oauth/callback/:provider", (c) => {
|
|
24045
23116
|
return GET4(c.req.raw, c.req.param("provider"), waitUntil);
|
|
24046
23117
|
});
|
|
24047
|
-
app.post("/api/internal/turn-resume", (c) => {
|
|
24048
|
-
return POST2(c.req.raw, waitUntil);
|
|
24049
|
-
});
|
|
24050
23118
|
app.post("/api/internal/agent-dispatch", (c) => {
|
|
24051
23119
|
return POST(c.req.raw, waitUntil);
|
|
24052
23120
|
});
|
|
@@ -24069,7 +23137,7 @@ async function createApp(options) {
|
|
|
24069
23137
|
return GET2(c.req.raw, waitUntil);
|
|
24070
23138
|
});
|
|
24071
23139
|
app.post("/api/webhooks/:platform", (c) => {
|
|
24072
|
-
return
|
|
23140
|
+
return POST2(c.req.raw, c.req.param("platform"), waitUntil);
|
|
24073
23141
|
});
|
|
24074
23142
|
return app;
|
|
24075
23143
|
}
|