@sentry/junior 0.183.0 → 0.185.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-SNRBZYAU.js → agent-hooks-NGZQLL5O.js} +13 -11
- package/dist/api/acp/auth.d.ts +46 -0
- package/dist/api/acp/authorization-page.d.ts +11 -0
- package/dist/api/acp/conversations.d.ts +70 -0
- package/dist/api/acp/errors.d.ts +6 -0
- package/dist/api/acp/route.d.ts +16 -0
- package/dist/api/acp/schema.d.ts +115 -0
- package/dist/api/acp/sleep.d.ts +2 -0
- package/dist/api/acp/state.d.ts +22 -0
- package/dist/api/acp/transport.d.ts +113 -0
- package/dist/api/code/overview.d.ts +39 -0
- package/dist/api/locations/detail.d.ts +1 -1
- package/dist/api/people/code.d.ts +22 -0
- package/dist/api/schema/code.d.ts +46 -0
- package/dist/api/schema/conversation.d.ts +100 -20
- package/dist/api/schema/location.d.ts +1 -1
- package/dist/api/schema/person.d.ts +1 -1
- package/dist/api/schema.d.ts +4 -4
- package/dist/api/schema.js +12 -1
- package/dist/api.js +358 -76
- package/dist/app.d.ts +0 -4
- package/dist/app.js +2450 -493
- package/dist/{catalog-runtime-3I3JOIIH.js → catalog-runtime-AAEAO2RL.js} +4 -4
- package/dist/chat/api-turns/accepted-reply.d.ts +11 -0
- package/dist/chat/api-turns/cancellation.d.ts +3 -5
- package/dist/chat/api-turns/work.d.ts +4 -9
- package/dist/chat/app/conversation-work.d.ts +1 -1
- package/dist/chat/app/factory.d.ts +4 -0
- package/dist/chat/app/services.d.ts +4 -3
- package/dist/chat/conversations/history.d.ts +83 -9
- package/dist/chat/conversations/turn-lifecycle.d.ts +4 -3
- package/dist/chat/providers/slack/input.d.ts +32 -0
- package/dist/chat/{runtime → providers/slack}/processing-reaction.d.ts +6 -8
- package/dist/chat/{runtime/slack-resume.d.ts → providers/slack/resume.d.ts} +16 -19
- package/dist/chat/providers/slack/runtime.d.ts +1 -10
- package/dist/chat/providers/slack/turn.d.ts +11 -24
- package/dist/chat/resource-events/store.d.ts +1 -1
- package/dist/chat/resource-events/tool-support.d.ts +0 -5
- package/dist/chat/runtime/turn-execution.d.ts +7 -2
- package/dist/chat/sandbox/egress/credentials.d.ts +3 -3
- package/dist/chat/sandbox/egress/session.d.ts +5 -6
- package/dist/chat/scheduled-tasks/tool-support.d.ts +5 -5
- package/dist/chat/scheduled-tasks/tools/create.d.ts +2 -2
- package/dist/chat/scheduled-tasks/tools/delete.d.ts +2 -2
- package/dist/chat/scheduled-tasks/tools/list.d.ts +2 -2
- package/dist/chat/scheduled-tasks/tools/run-now.d.ts +2 -2
- package/dist/chat/scheduled-tasks/tools/update.d.ts +2 -2
- package/dist/chat/scheduled-tasks/types.d.ts +1 -1
- package/dist/chat/services/provider-retry.d.ts +2 -0
- package/dist/chat/services/turn-failure-response.d.ts +4 -0
- package/dist/chat/task-execution/paused-turn.d.ts +0 -2
- package/dist/chat/task-execution/state.d.ts +2 -2
- package/dist/chat/task-execution/store.d.ts +1 -1
- package/dist/chat/task-execution/turn-wake.d.ts +12 -0
- package/dist/chat/tool-support/action-review.d.ts +1 -1
- package/dist/chat/tools/resource-events.d.ts +1 -1
- package/dist/chat/tools/search-conversation-events.d.ts +1 -1
- package/dist/chat/tools/types.d.ts +1 -1
- package/dist/chat/tools/watch-resource-events.d.ts +2 -2
- package/dist/{chunk-2EPYYVJC.js → chunk-4HA36IWS.js} +9 -94
- package/dist/{chunk-WVIA7V3M.js → chunk-4K25ODAO.js} +27 -1
- package/dist/{chunk-V64JCZUV.js → chunk-4S47VHJR.js} +3 -3
- package/dist/{chunk-QZV76CM4.js → chunk-6DOAUXLD.js} +231 -198
- package/dist/{chunk-M3QYTBQ6.js → chunk-6Q7YZLCL.js} +2 -1
- package/dist/{chunk-W46NIB5H.js → chunk-7GAP2XG4.js} +2 -3
- package/dist/{chunk-EENQGEIP.js → chunk-7MHNIGHP.js} +1 -1
- package/dist/{chunk-AX3UDIMA.js → chunk-ABAX75GG.js} +10 -384
- package/dist/{chunk-KTXOGLNE.js → chunk-ATUWN6Z3.js} +2 -2
- package/dist/{chunk-3HJ4F4L7.js → chunk-C6TS47CQ.js} +4 -4
- package/dist/chunk-D4YV5TDV.js +45 -0
- package/dist/{chunk-HUK5XIZL.js → chunk-D63IUQQE.js} +4 -4
- package/dist/{chunk-WOKJRLM2.js → chunk-DAPC2HTM.js} +24 -6
- package/dist/{chunk-2ADFGSUS.js → chunk-EIJCPJOY.js} +1 -1
- package/dist/{chunk-X52ESOBP.js → chunk-EJYP3NQ4.js} +4 -4
- package/dist/{chunk-35PM4YY5.js → chunk-EWEH2PJW.js} +6 -6
- package/dist/{chunk-W5JMPEBO.js → chunk-FFHXG2FX.js} +13 -11
- package/dist/{chunk-FJMTMGBC.js → chunk-FTVMXDNN.js} +1 -1
- package/dist/chunk-GFBR4S3Q.js +406 -0
- package/dist/{chunk-E3OBGRHN.js → chunk-GJTIZQGJ.js} +1 -1
- package/dist/{chunk-IO6KFXYE.js → chunk-HJPC32TQ.js} +12 -50
- package/dist/{chunk-RF6J3DIO.js → chunk-INQHUE72.js} +84 -1
- package/dist/{chunk-LMNMMESF.js → chunk-LRI5IRGY.js} +19 -44
- package/dist/{chunk-6UXDTPEN.js → chunk-LVK4WJKM.js} +1 -1
- package/dist/{chunk-AYW7J6K4.js → chunk-MWOH6CIK.js} +178 -148
- package/dist/{chunk-LZBPGX5A.js → chunk-OMQ2J6F6.js} +1 -1
- package/dist/{chunk-G2B2XZQW.js → chunk-PNYJOEVV.js} +1 -1
- package/dist/{chunk-37IQ6DGU.js → chunk-QCT4226Q.js} +3 -3
- package/dist/{chunk-R767Y7QV.js → chunk-QZKHHG3B.js} +34 -8
- package/dist/{chunk-VOV3SCM3.js → chunk-TVVAMOLS.js} +2 -2
- package/dist/{chunk-56XONJPF.js → chunk-U4CTAVWN.js} +1 -1
- package/dist/{chunk-4WGFWHWP.js → chunk-UUULGCKK.js} +1 -1
- package/dist/{chunk-AHMWPFIW.js → chunk-VH4IU3UC.js} +928 -984
- package/dist/chunk-W7GCLNIV.js +58 -0
- package/dist/{chunk-24OJZ5OU.js → chunk-WLKWN4CD.js} +22 -12
- package/dist/{chunk-T5MTGX2E.js → chunk-X2SS6BF7.js} +201 -210
- package/dist/{chunk-SQHFVIMZ.js → chunk-X7NCJQAA.js} +1 -1
- package/dist/{chunk-PLZT45VC.js → chunk-Y5G23T2D.js} +5 -5
- package/dist/{chunk-3L7CWDQQ.js → chunk-ZIF2S464.js} +1 -1
- package/dist/{chunk-QI5JW5AG.js → chunk-ZKKGFUWM.js} +6 -6
- package/dist/cli/chat.js +35 -33
- package/dist/cli/check.js +5 -5
- package/dist/cli/plugins.js +14 -12
- package/dist/cli/snapshot-create.js +10 -9
- package/dist/cli/upgrade.js +7 -6
- package/dist/{db-K2VMNLHQ.js → db-TRJ77FAU.js} +8 -6
- package/dist/instrumentation.js +3 -3
- package/dist/nitro.js +7 -6
- package/dist/{oauth-callback-server-DZZMTKSS.js → oauth-callback-server-XD5SV3MS.js} +30 -27
- package/dist/{runner-IIAKWDXI.js → runner-KFX7NG6D.js} +24 -19
- package/dist/{skills-EXIQ7BP5.js → skills-J2AH26CW.js} +5 -5
- package/dist/spawn-VU3CSRI6.js +41 -0
- package/dist/task-runner-5JG3VIIY.js +34 -0
- package/dist/{validation-XUGIWBJF.js → validation-O5J4VOPF.js} +5 -5
- package/dist/version.js +1 -1
- package/dist/{work-G4RLJ634.js → work-ECPQTG3U.js} +24 -19
- package/dist/worker-RS3DXUDO.js +24 -0
- package/package.json +5 -6
- package/dist/app-adapter.d.ts +0 -85
- package/dist/chat/api-turns/adapter-conversations.d.ts +0 -15
- package/dist/spawn-ZR6OOEAN.js +0 -37
- package/dist/task-runner-C52HYCVD.js +0 -32
- package/dist/worker-R2JEHK67.js +0 -22
package/dist/app.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
processConversationWork
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-C6TS47CQ.js";
|
|
4
4
|
import {
|
|
5
5
|
bindSpawnAgent
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-TVVAMOLS.js";
|
|
7
7
|
import {
|
|
8
8
|
createAgentInvocationWorker,
|
|
9
9
|
enqueueAgentInvocation,
|
|
10
10
|
listPendingAgentInvocationMailboxAppends,
|
|
11
11
|
routeAgentInvocationWork
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HJPC32TQ.js";
|
|
13
13
|
import {
|
|
14
14
|
GET as GET2,
|
|
15
15
|
GET2 as GET3,
|
|
16
16
|
buildAuthPauseResponse,
|
|
17
17
|
buildSlackOutputMessage,
|
|
18
|
+
createPausedTurns,
|
|
18
19
|
createSlackAdapterAssistantStatusSession,
|
|
19
20
|
getAssistantThreadContext,
|
|
20
21
|
getChannelId,
|
|
@@ -33,10 +34,10 @@ import {
|
|
|
33
34
|
sendSlackReply,
|
|
34
35
|
shouldKeepProcessingReactionForToolInvocation,
|
|
35
36
|
splitSlackReplyText,
|
|
36
|
-
|
|
37
|
+
startProcessingReaction,
|
|
37
38
|
stripLeadingBotMention,
|
|
38
39
|
wakePausedTurn
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-X2SS6BF7.js";
|
|
40
41
|
import "./chunk-FA6POGLH.js";
|
|
41
42
|
import {
|
|
42
43
|
GET,
|
|
@@ -53,8 +54,12 @@ import {
|
|
|
53
54
|
resolveSlackConversationContext,
|
|
54
55
|
routeApiTurnWork,
|
|
55
56
|
webActorFromEmail
|
|
56
|
-
} from "./chunk-
|
|
57
|
-
import "./chunk-
|
|
57
|
+
} from "./chunk-MWOH6CIK.js";
|
|
58
|
+
import "./chunk-UUULGCKK.js";
|
|
59
|
+
import {
|
|
60
|
+
AgentRunError,
|
|
61
|
+
executeTurn
|
|
62
|
+
} from "./chunk-W7GCLNIV.js";
|
|
58
63
|
import {
|
|
59
64
|
buildDeliveredTurnStatePatch,
|
|
60
65
|
completeAuthPauseTurn,
|
|
@@ -65,7 +70,7 @@ import {
|
|
|
65
70
|
getTurnUserSlackMessageTs,
|
|
66
71
|
persistAuthPauseTurnState,
|
|
67
72
|
requireTurnFailureEventId
|
|
68
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-DAPC2HTM.js";
|
|
69
74
|
import {
|
|
70
75
|
claimDispatchMailboxAppend,
|
|
71
76
|
confirmDispatchMailboxAppend,
|
|
@@ -85,10 +90,10 @@ import {
|
|
|
85
90
|
recordTaskExecution,
|
|
86
91
|
resolveTurnSessionRouting,
|
|
87
92
|
scheduleSessionCompletedPluginTasks
|
|
88
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-FFHXG2FX.js";
|
|
89
94
|
import {
|
|
90
95
|
ConversationTurnLifecycleService
|
|
91
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-6Q7YZLCL.js";
|
|
92
97
|
import {
|
|
93
98
|
coerceThreadConversationState,
|
|
94
99
|
getLocationConfigurationService,
|
|
@@ -97,17 +102,17 @@ import {
|
|
|
97
102
|
persistThreadRuntimeState,
|
|
98
103
|
persistThreadState,
|
|
99
104
|
persistThreadStateById
|
|
100
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-Y5G23T2D.js";
|
|
101
106
|
import "./chunk-UTL3MHPC.js";
|
|
102
107
|
import {
|
|
103
108
|
createVercelPluginTaskCallback,
|
|
104
109
|
queueCallback,
|
|
105
110
|
registerVercelPluginTaskDevConsumer
|
|
106
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-4S47VHJR.js";
|
|
107
112
|
import {
|
|
108
113
|
validatePluginEgressCredentialHooks,
|
|
109
114
|
validatePluginRegistrations
|
|
110
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-ZIF2S464.js";
|
|
111
116
|
import {
|
|
112
117
|
defineJuniorPlugins,
|
|
113
118
|
pluginCatalogConfigFromEnv,
|
|
@@ -155,36 +160,28 @@ import {
|
|
|
155
160
|
unpublishedArtifactHeaders,
|
|
156
161
|
verifySlackSignature,
|
|
157
162
|
withNormalizedThreadId
|
|
158
|
-
} from "./chunk-
|
|
163
|
+
} from "./chunk-6DOAUXLD.js";
|
|
159
164
|
import {
|
|
160
165
|
AuthorizationFlowDisabledError,
|
|
161
166
|
PluginCredentialFailureError
|
|
162
|
-
} from "./chunk-
|
|
167
|
+
} from "./chunk-EWEH2PJW.js";
|
|
163
168
|
import "./chunk-6Y5B5PEX.js";
|
|
164
169
|
import "./chunk-DIB4GX3G.js";
|
|
165
170
|
import {
|
|
166
|
-
createAgentRunner,
|
|
167
171
|
createUserTokenStore,
|
|
168
172
|
deleteMcpAuthSessionsForUserProvider,
|
|
169
173
|
deleteMcpServerSessionId,
|
|
170
174
|
deleteMcpStoredOAuthCredentials
|
|
171
|
-
} from "./chunk-
|
|
172
|
-
import {
|
|
173
|
-
RetryableDeliveryError
|
|
174
|
-
} from "./chunk-RF6J3DIO.js";
|
|
175
|
+
} from "./chunk-4HA36IWS.js";
|
|
175
176
|
import {
|
|
176
177
|
formatProviderLabel,
|
|
177
178
|
postSlackMessage,
|
|
178
179
|
startOAuthFlow
|
|
179
|
-
} from "./chunk-
|
|
180
|
-
import {
|
|
181
|
-
getExperimentalFeatures,
|
|
182
|
-
isExperimentalFeatureEnabled,
|
|
183
|
-
setExperimentalFeatures
|
|
184
|
-
} from "./chunk-2LYHWMR4.js";
|
|
180
|
+
} from "./chunk-ZKKGFUWM.js";
|
|
185
181
|
import {
|
|
186
182
|
collectAttachmentGarbage,
|
|
187
183
|
collectEventTaskMatchKeys,
|
|
184
|
+
createResourceEventTeamIdResolver,
|
|
188
185
|
findMatchingEventTasks,
|
|
189
186
|
getResourceEventCatalog,
|
|
190
187
|
listScheduledTasks,
|
|
@@ -197,10 +194,14 @@ import {
|
|
|
197
194
|
scheduledRunId,
|
|
198
195
|
scheduledRunSchema,
|
|
199
196
|
withScheduledTaskLock
|
|
200
|
-
} from "./chunk-
|
|
197
|
+
} from "./chunk-QZKHHG3B.js";
|
|
201
198
|
import {
|
|
202
199
|
parseSlackThreadId
|
|
203
|
-
} from "./chunk-
|
|
200
|
+
} from "./chunk-X7NCJQAA.js";
|
|
201
|
+
import {
|
|
202
|
+
RetryableDeliveryError,
|
|
203
|
+
createAgentRunner
|
|
204
|
+
} from "./chunk-INQHUE72.js";
|
|
204
205
|
import {
|
|
205
206
|
CONVERSATION_WORK_STALE_ENQUEUE_MS,
|
|
206
207
|
appendAndEnqueueInboundMessage,
|
|
@@ -215,7 +216,7 @@ import {
|
|
|
215
216
|
removeActiveConversation,
|
|
216
217
|
requestConversationWork,
|
|
217
218
|
stopConversationWork
|
|
218
|
-
} from "./chunk-
|
|
219
|
+
} from "./chunk-D63IUQQE.js";
|
|
219
220
|
import {
|
|
220
221
|
ConversationTurnBoundaryError,
|
|
221
222
|
CooperativeTurnYieldError,
|
|
@@ -233,7 +234,7 @@ import {
|
|
|
233
234
|
markTurnCompleted,
|
|
234
235
|
markTurnFailed,
|
|
235
236
|
startActiveTurn
|
|
236
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-U4CTAVWN.js";
|
|
237
238
|
import {
|
|
238
239
|
abandonTurnRecord,
|
|
239
240
|
appendThreadContextMessages,
|
|
@@ -269,11 +270,16 @@ import {
|
|
|
269
270
|
trimTrailingAssistantMessages,
|
|
270
271
|
turnHasReply,
|
|
271
272
|
upsertConversationMessage
|
|
272
|
-
} from "./chunk-
|
|
273
|
+
} from "./chunk-WLKWN4CD.js";
|
|
274
|
+
import {
|
|
275
|
+
getExperimentalFeatures,
|
|
276
|
+
isExperimentalFeatureEnabled,
|
|
277
|
+
setExperimentalFeatures
|
|
278
|
+
} from "./chunk-2LYHWMR4.js";
|
|
273
279
|
import {
|
|
274
280
|
getSandboxResourceConfig,
|
|
275
281
|
setSandboxResourceConfig
|
|
276
|
-
} from "./chunk-
|
|
282
|
+
} from "./chunk-QCT4226Q.js";
|
|
277
283
|
import "./chunk-PDO5BLNM.js";
|
|
278
284
|
import "./chunk-UIE3R5XU.js";
|
|
279
285
|
import {
|
|
@@ -292,7 +298,7 @@ import {
|
|
|
292
298
|
import "./chunk-7H2VZTWN.js";
|
|
293
299
|
import {
|
|
294
300
|
getTurnRequestDeadline
|
|
295
|
-
} from "./chunk-
|
|
301
|
+
} from "./chunk-FTVMXDNN.js";
|
|
296
302
|
import {
|
|
297
303
|
SlackActionError,
|
|
298
304
|
bindEventTaskCredentialSubject,
|
|
@@ -302,7 +308,6 @@ import {
|
|
|
302
308
|
collectResourceEventMatchKeys,
|
|
303
309
|
createPluginLogger,
|
|
304
310
|
createPluginState,
|
|
305
|
-
createResourceEventTeamIdResolver,
|
|
306
311
|
downloadPrivateSlackFile,
|
|
307
312
|
getPluginApiRoutes,
|
|
308
313
|
getPluginRoutes,
|
|
@@ -318,20 +323,18 @@ import {
|
|
|
318
323
|
verifyEventTaskCredentialSubject,
|
|
319
324
|
verifyScheduledTaskCredentialSubject,
|
|
320
325
|
verifySlackDirectCredentialSubject
|
|
321
|
-
} from "./chunk-
|
|
326
|
+
} from "./chunk-VH4IU3UC.js";
|
|
322
327
|
import "./chunk-3Y47VMHV.js";
|
|
323
328
|
import {
|
|
324
|
-
contextProvenance,
|
|
325
329
|
deleteProviderIdentityForSlackUser,
|
|
326
330
|
getConversationEventStore,
|
|
327
331
|
getConversationStore,
|
|
328
332
|
getDb,
|
|
329
333
|
getSqlExecutor,
|
|
330
|
-
instructionProvenanceFor,
|
|
331
334
|
requireSlackDestination,
|
|
332
335
|
withConversationEventLock,
|
|
333
336
|
withConversationMutationLock
|
|
334
|
-
} from "./chunk-
|
|
337
|
+
} from "./chunk-ABAX75GG.js";
|
|
335
338
|
import {
|
|
336
339
|
juniorAgentBindings,
|
|
337
340
|
juniorAgentInvocations,
|
|
@@ -339,13 +342,17 @@ import {
|
|
|
339
342
|
juniorConversations,
|
|
340
343
|
juniorSchedulerRuns,
|
|
341
344
|
juniorSchedulerTasks
|
|
342
|
-
} from "./chunk-
|
|
343
|
-
import "./chunk-
|
|
345
|
+
} from "./chunk-LVK4WJKM.js";
|
|
346
|
+
import "./chunk-EJYP3NQ4.js";
|
|
344
347
|
import {
|
|
345
348
|
acquireActiveLock,
|
|
346
349
|
getStateAdapter,
|
|
347
350
|
withActiveLock
|
|
348
|
-
} from "./chunk-
|
|
351
|
+
} from "./chunk-PNYJOEVV.js";
|
|
352
|
+
import {
|
|
353
|
+
contextProvenance,
|
|
354
|
+
instructionProvenanceFor
|
|
355
|
+
} from "./chunk-GFBR4S3Q.js";
|
|
349
356
|
import {
|
|
350
357
|
juniorDestinations
|
|
351
358
|
} from "./chunk-VFUK3X5B.js";
|
|
@@ -354,7 +361,6 @@ import {
|
|
|
354
361
|
completeObject,
|
|
355
362
|
completeText,
|
|
356
363
|
configureFunctionMaxDurationSeconds,
|
|
357
|
-
defaultModelId,
|
|
358
364
|
escapeXml,
|
|
359
365
|
getChatConfig,
|
|
360
366
|
getSlackBotToken,
|
|
@@ -363,16 +369,19 @@ import {
|
|
|
363
369
|
getSlackReactionConfig,
|
|
364
370
|
getSlackSigningSecret,
|
|
365
371
|
isProviderRetryError,
|
|
366
|
-
modelIdForProfile,
|
|
367
372
|
setProfiles,
|
|
368
373
|
setSlackReactionConfig
|
|
369
|
-
} from "./chunk-
|
|
370
|
-
import
|
|
371
|
-
|
|
374
|
+
} from "./chunk-LRI5IRGY.js";
|
|
375
|
+
import {
|
|
376
|
+
defaultModelId,
|
|
377
|
+
modelIdForProfile
|
|
378
|
+
} from "./chunk-D4YV5TDV.js";
|
|
379
|
+
import "./chunk-EIJCPJOY.js";
|
|
380
|
+
import "./chunk-ATUWN6Z3.js";
|
|
372
381
|
import {
|
|
373
382
|
pluginCatalogRuntime
|
|
374
|
-
} from "./chunk-
|
|
375
|
-
import "./chunk-
|
|
383
|
+
} from "./chunk-GJTIZQGJ.js";
|
|
384
|
+
import "./chunk-7MHNIGHP.js";
|
|
376
385
|
import {
|
|
377
386
|
credentialContextForActor
|
|
378
387
|
} from "./chunk-CEMMLV5L.js";
|
|
@@ -407,13 +416,13 @@ import {
|
|
|
407
416
|
withContext,
|
|
408
417
|
withLogContext,
|
|
409
418
|
withSpan
|
|
410
|
-
} from "./chunk-
|
|
419
|
+
} from "./chunk-OMQ2J6F6.js";
|
|
411
420
|
import {
|
|
412
421
|
continueTrace
|
|
413
422
|
} from "./chunk-PEQXYSSE.js";
|
|
414
423
|
import {
|
|
415
424
|
JUNIOR_VERSION
|
|
416
|
-
} from "./chunk-
|
|
425
|
+
} from "./chunk-7GAP2XG4.js";
|
|
417
426
|
|
|
418
427
|
// src/app.ts
|
|
419
428
|
import { createRequire } from "module";
|
|
@@ -698,10 +707,10 @@ async function readDispatchTurnResult(dispatch2) {
|
|
|
698
707
|
const session = storedSession ?? summary;
|
|
699
708
|
const dispatchOutcome = summary?.dispatchOutcome ?? storedSession?.dispatchOutcome;
|
|
700
709
|
const resultMessageTs = summary?.resultMessageId ?? storedSession?.resultMessageId;
|
|
701
|
-
const
|
|
710
|
+
const errorMessage3 = storedSession?.errorMessage;
|
|
702
711
|
if (dispatchOutcome) {
|
|
703
712
|
return {
|
|
704
|
-
...
|
|
713
|
+
...errorMessage3 ? { errorMessage: errorMessage3 } : void 0,
|
|
705
714
|
outcome: dispatchOutcome,
|
|
706
715
|
...resultMessageTs ? { resultMessageTs } : void 0
|
|
707
716
|
};
|
|
@@ -1274,13 +1283,13 @@ async function skipMissedRun(db, args) {
|
|
|
1274
1283
|
return void 0;
|
|
1275
1284
|
}
|
|
1276
1285
|
const duplicateOf = await findStaleRecoveryCanonicalTask(db, current);
|
|
1277
|
-
const
|
|
1286
|
+
const errorMessage3 = duplicateOf ? `Duplicate stale scheduled task was skipped without dispatch. Canonical task: ${duplicateOf.id}.` : "Scheduled occurrence was more than 24 hours late and was skipped without dispatch.";
|
|
1278
1287
|
const skipped = {
|
|
1279
1288
|
id: scheduledRunId(current.id, args.scheduledForMs),
|
|
1280
1289
|
attempt: 1,
|
|
1281
1290
|
claimedAtMs: args.nowMs,
|
|
1282
1291
|
completedAtMs: args.nowMs,
|
|
1283
|
-
errorMessage,
|
|
1292
|
+
errorMessage: errorMessage3,
|
|
1284
1293
|
scheduledForMs: args.scheduledForMs,
|
|
1285
1294
|
status: "skipped",
|
|
1286
1295
|
taskId: current.id
|
|
@@ -1299,12 +1308,12 @@ async function skipMissedRun(db, args) {
|
|
|
1299
1308
|
nextRunAtMs,
|
|
1300
1309
|
runNowAtMs: isRunNow ? void 0 : current.runNowAtMs,
|
|
1301
1310
|
status,
|
|
1302
|
-
statusReason: status === "deleted" ?
|
|
1311
|
+
statusReason: status === "deleted" ? errorMessage3 : void 0,
|
|
1303
1312
|
updatedAtMs: args.nowMs
|
|
1304
1313
|
},
|
|
1305
1314
|
current
|
|
1306
1315
|
);
|
|
1307
|
-
return { errorMessage, run: skipped, task: current };
|
|
1316
|
+
return { errorMessage: errorMessage3, run: skipped, task: current };
|
|
1308
1317
|
}
|
|
1309
1318
|
async function claimDueScheduledRun(db, args) {
|
|
1310
1319
|
const skippedRuns = [];
|
|
@@ -4218,9 +4227,10 @@ function shouldRethrowTurnControlError(error) {
|
|
|
4218
4227
|
function getQueuedMessages(context, options) {
|
|
4219
4228
|
return (context?.skipped ?? []).map((message) => {
|
|
4220
4229
|
const content = parseContent(message);
|
|
4221
|
-
const stripped =
|
|
4230
|
+
const stripped = stripLeadingBotMention(
|
|
4222
4231
|
stripLeadingSteeringOverride(content.topLevelText),
|
|
4223
4232
|
{
|
|
4233
|
+
botUserId: options.botUserId,
|
|
4224
4234
|
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
4225
4235
|
}
|
|
4226
4236
|
);
|
|
@@ -4295,7 +4305,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4295
4305
|
return true;
|
|
4296
4306
|
};
|
|
4297
4307
|
const failConversationTurn = async (args) => {
|
|
4298
|
-
const conversationId =
|
|
4308
|
+
const conversationId = getThreadId(args.thread, args.message) ?? getRunId(args.thread, args.message);
|
|
4299
4309
|
if (!conversationId) {
|
|
4300
4310
|
return;
|
|
4301
4311
|
}
|
|
@@ -4328,7 +4338,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4328
4338
|
const processingReactionByMessage = /* @__PURE__ */ new Map();
|
|
4329
4339
|
return {
|
|
4330
4340
|
start: async (context, targetMessage) => {
|
|
4331
|
-
const channelId =
|
|
4341
|
+
const channelId = getChannelId(thread, targetMessage);
|
|
4332
4342
|
const messageTs = getMessageTs(targetMessage);
|
|
4333
4343
|
const reactionKey = channelId && messageTs ? `${channelId}:${messageTs}` : void 0;
|
|
4334
4344
|
if (reactionKey) {
|
|
@@ -4337,10 +4347,9 @@ function createSlackTurnRuntime(deps) {
|
|
|
4337
4347
|
return existing;
|
|
4338
4348
|
}
|
|
4339
4349
|
}
|
|
4340
|
-
const started = await
|
|
4350
|
+
const started = await startProcessingReaction({
|
|
4341
4351
|
thread,
|
|
4342
|
-
message: targetMessage
|
|
4343
|
-
logException: deps.logException
|
|
4352
|
+
message: targetMessage
|
|
4344
4353
|
});
|
|
4345
4354
|
processingReactions.push(started);
|
|
4346
4355
|
if (reactionKey) {
|
|
@@ -4356,7 +4365,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4356
4365
|
try {
|
|
4357
4366
|
await args.thread.post(buildTurnFailureResponse(args.eventId));
|
|
4358
4367
|
} catch (postError) {
|
|
4359
|
-
|
|
4368
|
+
logException(postError, args.postFailureEventName, {
|
|
4360
4369
|
"app.slack.reply_stage": "error_fallback_post",
|
|
4361
4370
|
"app.error.original_event_id": args.eventId,
|
|
4362
4371
|
...getSlackErrorObservabilityAttributes(postError)
|
|
@@ -4367,15 +4376,16 @@ function createSlackTurnRuntime(deps) {
|
|
|
4367
4376
|
const decideSteeringMessage = async (thread, candidate, conversationContext) => {
|
|
4368
4377
|
const { message } = candidate;
|
|
4369
4378
|
const context = {
|
|
4370
|
-
threadId:
|
|
4379
|
+
threadId: getThreadId(thread, message),
|
|
4371
4380
|
actorId: message.author.userId,
|
|
4372
|
-
channelId:
|
|
4373
|
-
runId:
|
|
4381
|
+
channelId: getChannelId(thread, message),
|
|
4382
|
+
runId: getRunId(thread, message)
|
|
4374
4383
|
};
|
|
4375
4384
|
const content = parseContent(message);
|
|
4376
|
-
const strippedUserText =
|
|
4385
|
+
const strippedUserText = stripLeadingBotMention(
|
|
4377
4386
|
stripLeadingSteeringOverride(content.topLevelText),
|
|
4378
4387
|
{
|
|
4388
|
+
botUserId: deps.getBotUserId(),
|
|
4379
4389
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
4380
4390
|
}
|
|
4381
4391
|
);
|
|
@@ -4407,7 +4417,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4407
4417
|
runId: args.context.runId
|
|
4408
4418
|
}),
|
|
4409
4419
|
() => {
|
|
4410
|
-
|
|
4420
|
+
logWarn("subscribed_message.reply.skipped", {
|
|
4411
4421
|
"app.decision.reason": args.decision.reason
|
|
4412
4422
|
});
|
|
4413
4423
|
}
|
|
@@ -4503,7 +4513,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4503
4513
|
}
|
|
4504
4514
|
} catch (error) {
|
|
4505
4515
|
withLogContext(args.context, () => {
|
|
4506
|
-
|
|
4516
|
+
logException(error, "agent.turn.steering_flush.failed");
|
|
4507
4517
|
});
|
|
4508
4518
|
}
|
|
4509
4519
|
};
|
|
@@ -4520,18 +4530,18 @@ function createSlackTurnRuntime(deps) {
|
|
|
4520
4530
|
thread,
|
|
4521
4531
|
pending: skippedSteeringMessages,
|
|
4522
4532
|
context: logContext({
|
|
4523
|
-
threadId:
|
|
4533
|
+
threadId: getThreadId(thread, message),
|
|
4524
4534
|
actorId: message.author.userId,
|
|
4525
4535
|
actorUserName: actorUserName(message),
|
|
4526
|
-
channelId:
|
|
4527
|
-
runId:
|
|
4536
|
+
channelId: getChannelId(thread, message),
|
|
4537
|
+
runId: getRunId(thread, message)
|
|
4528
4538
|
})
|
|
4529
4539
|
});
|
|
4530
4540
|
};
|
|
4531
4541
|
try {
|
|
4532
|
-
const threadId =
|
|
4533
|
-
const channelId =
|
|
4534
|
-
const runId =
|
|
4542
|
+
const threadId = getThreadId(thread, message);
|
|
4543
|
+
const channelId = getChannelId(thread, message);
|
|
4544
|
+
const runId = getRunId(thread, message);
|
|
4535
4545
|
const context = logContext({
|
|
4536
4546
|
threadId,
|
|
4537
4547
|
channelId,
|
|
@@ -4544,11 +4554,11 @@ function createSlackTurnRuntime(deps) {
|
|
|
4544
4554
|
processingReaction,
|
|
4545
4555
|
hooks
|
|
4546
4556
|
);
|
|
4547
|
-
await
|
|
4557
|
+
await withSpan("chat.turn", "chat.turn", context, async () => {
|
|
4548
4558
|
await thread.subscribe();
|
|
4549
4559
|
const queuedMessages = getQueuedMessages(hooks.messageContext, {
|
|
4550
|
-
|
|
4551
|
-
|
|
4560
|
+
botUserId: deps.getBotUserId(),
|
|
4561
|
+
explicitMention: true
|
|
4552
4562
|
});
|
|
4553
4563
|
let queuedProcessingReactionsStarted = false;
|
|
4554
4564
|
const startQueuedProcessingReactions = async () => {
|
|
@@ -4568,6 +4578,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4568
4578
|
await startQueuedProcessingReactions();
|
|
4569
4579
|
};
|
|
4570
4580
|
const drainSteeringMessages = createAcceptedSteeringDrain(hooks, {
|
|
4581
|
+
botUserId: deps.getBotUserId(),
|
|
4571
4582
|
explicitMention: true,
|
|
4572
4583
|
onAcceptedForProcessing: async (messages) => {
|
|
4573
4584
|
await Promise.all(
|
|
@@ -4586,8 +4597,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4586
4597
|
conversationContext: drainContext?.conversationContext,
|
|
4587
4598
|
messages,
|
|
4588
4599
|
thread
|
|
4589
|
-
})
|
|
4590
|
-
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
4600
|
+
})
|
|
4591
4601
|
});
|
|
4592
4602
|
await deps.executeSlackTurn(thread, message, {
|
|
4593
4603
|
explicitMention: true,
|
|
@@ -4606,11 +4616,11 @@ function createSlackTurnRuntime(deps) {
|
|
|
4606
4616
|
});
|
|
4607
4617
|
} catch (error) {
|
|
4608
4618
|
const failureLogContext = logContext({
|
|
4609
|
-
threadId:
|
|
4619
|
+
threadId: getThreadId(thread, message),
|
|
4610
4620
|
actorId: message.author.userId,
|
|
4611
4621
|
actorUserName: actorUserName(message),
|
|
4612
|
-
channelId:
|
|
4613
|
-
runId:
|
|
4622
|
+
channelId: getChannelId(thread, message),
|
|
4623
|
+
runId: getRunId(thread, message)
|
|
4614
4624
|
});
|
|
4615
4625
|
const classifiedFailure = getConversationTurnBoundaryError(error);
|
|
4616
4626
|
const failureCause = classifiedFailure?.cause ?? error;
|
|
@@ -4623,7 +4633,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4623
4633
|
const failureCode = classifiedFailure?.failureCode ?? "agent_run_failed";
|
|
4624
4634
|
if (failureCause instanceof SlackActionError && failureCause.code === "read_only_channel") {
|
|
4625
4635
|
withLogContext(failureLogContext, () => {
|
|
4626
|
-
|
|
4636
|
+
logWarn("mention.handler.skipped", {
|
|
4627
4637
|
"app.decision.reason": "read_only_channel"
|
|
4628
4638
|
});
|
|
4629
4639
|
});
|
|
@@ -4631,7 +4641,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4631
4641
|
}
|
|
4632
4642
|
const eventId = classifiedFailure?.eventId ?? withLogContext(
|
|
4633
4643
|
failureLogContext,
|
|
4634
|
-
() =>
|
|
4644
|
+
() => logException(failureCause, "mention.handler.failed")
|
|
4635
4645
|
);
|
|
4636
4646
|
if (!acked && hooks.isFinalAttempt === false) {
|
|
4637
4647
|
return;
|
|
@@ -4669,11 +4679,11 @@ function createSlackTurnRuntime(deps) {
|
|
|
4669
4679
|
thread,
|
|
4670
4680
|
pending: skippedSteeringMessages,
|
|
4671
4681
|
context: logContext({
|
|
4672
|
-
threadId:
|
|
4682
|
+
threadId: getThreadId(thread, message),
|
|
4673
4683
|
actorId: message.author.userId,
|
|
4674
4684
|
actorUserName: actorUserName(message),
|
|
4675
|
-
channelId:
|
|
4676
|
-
runId:
|
|
4685
|
+
channelId: getChannelId(thread, message),
|
|
4686
|
+
runId: getRunId(thread, message)
|
|
4677
4687
|
})
|
|
4678
4688
|
});
|
|
4679
4689
|
await processingReactions.stopAll();
|
|
@@ -4692,18 +4702,18 @@ function createSlackTurnRuntime(deps) {
|
|
|
4692
4702
|
thread,
|
|
4693
4703
|
pending: skippedSteeringMessages,
|
|
4694
4704
|
context: logContext({
|
|
4695
|
-
threadId:
|
|
4705
|
+
threadId: getThreadId(thread, message),
|
|
4696
4706
|
actorId: message.author.userId,
|
|
4697
4707
|
actorUserName: actorUserName(message),
|
|
4698
|
-
channelId:
|
|
4699
|
-
runId:
|
|
4708
|
+
channelId: getChannelId(thread, message),
|
|
4709
|
+
runId: getRunId(thread, message)
|
|
4700
4710
|
})
|
|
4701
4711
|
});
|
|
4702
4712
|
};
|
|
4703
4713
|
try {
|
|
4704
|
-
const threadId =
|
|
4705
|
-
const channelId =
|
|
4706
|
-
const runId =
|
|
4714
|
+
const threadId = getThreadId(thread, message);
|
|
4715
|
+
const channelId = getChannelId(thread, message);
|
|
4716
|
+
const runId = getRunId(thread, message);
|
|
4707
4717
|
const isResourceEventNotification = isResourceEventSlackMessage(message);
|
|
4708
4718
|
const actorId = isResourceEventNotification ? void 0 : message.author.userId;
|
|
4709
4719
|
const turnContext = logContext({
|
|
@@ -4713,11 +4723,12 @@ function createSlackTurnRuntime(deps) {
|
|
|
4713
4723
|
channelId,
|
|
4714
4724
|
runId
|
|
4715
4725
|
});
|
|
4716
|
-
await
|
|
4726
|
+
await withSpan("chat.turn", "chat.turn", turnContext, async () => {
|
|
4717
4727
|
const content = parseContent(message);
|
|
4718
|
-
const strippedUserText =
|
|
4728
|
+
const strippedUserText = stripLeadingBotMention(
|
|
4719
4729
|
stripLeadingSteeringOverride(content.topLevelText),
|
|
4720
4730
|
{
|
|
4731
|
+
botUserId: deps.getBotUserId(),
|
|
4721
4732
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
4722
4733
|
}
|
|
4723
4734
|
);
|
|
@@ -4732,8 +4743,8 @@ function createSlackTurnRuntime(deps) {
|
|
|
4732
4743
|
runId
|
|
4733
4744
|
};
|
|
4734
4745
|
const queuedMessages = getQueuedMessages(hooks.messageContext, {
|
|
4735
|
-
|
|
4736
|
-
|
|
4746
|
+
botUserId: deps.getBotUserId(),
|
|
4747
|
+
explicitMention: Boolean(message.isMention)
|
|
4737
4748
|
});
|
|
4738
4749
|
const combinedText = combineTurnText(queuedMessages, currentText);
|
|
4739
4750
|
const turnIsExplicitMention = Boolean(message.isMention) || queuedMessages.some((queued) => queued.explicitMention);
|
|
@@ -4833,6 +4844,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4833
4844
|
}
|
|
4834
4845
|
const conversationContext = deps.getPreparedConversationContext(preparedState);
|
|
4835
4846
|
const drainSteeringMessages = createAcceptedSteeringDrain(hooks, {
|
|
4847
|
+
botUserId: deps.getBotUserId(),
|
|
4836
4848
|
explicitMention: Boolean(message.isMention),
|
|
4837
4849
|
onAcceptedForProcessing: async (messages) => {
|
|
4838
4850
|
await Promise.all(
|
|
@@ -4851,8 +4863,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4851
4863
|
conversationContext: drainContext?.conversationContext ?? conversationContext,
|
|
4852
4864
|
messages,
|
|
4853
4865
|
thread
|
|
4854
|
-
})
|
|
4855
|
-
stripLeadingBotMention: deps.stripLeadingBotMention
|
|
4866
|
+
})
|
|
4856
4867
|
});
|
|
4857
4868
|
processingReaction = await processingReactions.start(
|
|
4858
4869
|
turnContext,
|
|
@@ -4897,11 +4908,11 @@ function createSlackTurnRuntime(deps) {
|
|
|
4897
4908
|
});
|
|
4898
4909
|
} catch (error) {
|
|
4899
4910
|
const failureLogContext = logContext({
|
|
4900
|
-
threadId:
|
|
4911
|
+
threadId: getThreadId(thread, message),
|
|
4901
4912
|
actorId: message.author.userId,
|
|
4902
4913
|
actorUserName: actorUserName(message),
|
|
4903
|
-
channelId:
|
|
4904
|
-
runId:
|
|
4914
|
+
channelId: getChannelId(thread, message),
|
|
4915
|
+
runId: getRunId(thread, message)
|
|
4905
4916
|
});
|
|
4906
4917
|
const classifiedFailure = getConversationTurnBoundaryError(error);
|
|
4907
4918
|
const failureCause = classifiedFailure?.cause ?? error;
|
|
@@ -4914,7 +4925,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4914
4925
|
const failureCode = classifiedFailure?.failureCode ?? "agent_run_failed";
|
|
4915
4926
|
if (failureCause instanceof SlackActionError && failureCause.code === "read_only_channel") {
|
|
4916
4927
|
withLogContext(failureLogContext, () => {
|
|
4917
|
-
|
|
4928
|
+
logWarn("subscribed_message.handler.skipped", {
|
|
4918
4929
|
"app.decision.reason": "read_only_channel"
|
|
4919
4930
|
});
|
|
4920
4931
|
});
|
|
@@ -4922,10 +4933,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4922
4933
|
}
|
|
4923
4934
|
const eventId = classifiedFailure?.eventId ?? withLogContext(
|
|
4924
4935
|
failureLogContext,
|
|
4925
|
-
() =>
|
|
4926
|
-
failureCause,
|
|
4927
|
-
"subscribed_message.handler.failed"
|
|
4928
|
-
)
|
|
4936
|
+
() => logException(failureCause, "subscribed_message.handler.failed")
|
|
4929
4937
|
);
|
|
4930
4938
|
if (!acked && hooks.isFinalAttempt === false) {
|
|
4931
4939
|
return;
|
|
@@ -4963,11 +4971,11 @@ function createSlackTurnRuntime(deps) {
|
|
|
4963
4971
|
thread,
|
|
4964
4972
|
pending: skippedSteeringMessages,
|
|
4965
4973
|
context: logContext({
|
|
4966
|
-
threadId:
|
|
4974
|
+
threadId: getThreadId(thread, message),
|
|
4967
4975
|
actorId: message.author.userId,
|
|
4968
4976
|
actorUserName: actorUserName(message),
|
|
4969
|
-
channelId:
|
|
4970
|
-
runId:
|
|
4977
|
+
channelId: getChannelId(thread, message),
|
|
4978
|
+
runId: getRunId(thread, message)
|
|
4971
4979
|
})
|
|
4972
4980
|
});
|
|
4973
4981
|
await processingReactions.stopAll();
|
|
@@ -4990,7 +4998,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
4990
4998
|
sourceChannelId: event.context?.channelId
|
|
4991
4999
|
});
|
|
4992
5000
|
} catch (error) {
|
|
4993
|
-
|
|
5001
|
+
logException(error, "assistant.thread.initialization.failed");
|
|
4994
5002
|
}
|
|
4995
5003
|
}
|
|
4996
5004
|
);
|
|
@@ -5011,7 +5019,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
5011
5019
|
sourceChannelId: event.context?.channelId
|
|
5012
5020
|
});
|
|
5013
5021
|
} catch (error) {
|
|
5014
|
-
|
|
5022
|
+
logException(error, "assistant.context.refresh.failed");
|
|
5015
5023
|
}
|
|
5016
5024
|
}
|
|
5017
5025
|
);
|
|
@@ -5493,24 +5501,14 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
5493
5501
|
listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
|
|
5494
5502
|
downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
|
|
5495
5503
|
});
|
|
5496
|
-
const agentRunner = overrides.
|
|
5504
|
+
const agentRunner = overrides.agentRunner ?? createAgentRunner(executeAgentRun, {
|
|
5497
5505
|
bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: getVercelConversationWorkQueue }),
|
|
5498
5506
|
tracePropagation: overrides.sandbox?.tracePropagation
|
|
5499
5507
|
});
|
|
5500
5508
|
return {
|
|
5501
5509
|
conversationMemory,
|
|
5502
5510
|
contextCompactor,
|
|
5503
|
-
|
|
5504
|
-
contextCompactor: overrides.replyExecutor?.contextCompactor ?? contextCompactor,
|
|
5505
|
-
agentRunner,
|
|
5506
|
-
getPausedTurnRequest: overrides.replyExecutor?.getPausedTurnRequest ?? getPausedTurnRequest,
|
|
5507
|
-
lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
|
|
5508
|
-
wakePausedTurn: overrides.replyExecutor?.wakePausedTurn ?? wakePausedTurn,
|
|
5509
|
-
scheduleSessionCompletedPluginTasks: overrides.replyExecutor?.scheduleSessionCompletedPluginTasks ?? (async (params) => {
|
|
5510
|
-
await scheduleSessionCompletedPluginTasks(params);
|
|
5511
|
-
}),
|
|
5512
|
-
turnLifecycle: overrides.replyExecutor?.turnLifecycle ?? new ConversationTurnLifecycleService(getConversationEventStore())
|
|
5513
|
-
},
|
|
5511
|
+
executeTurn: async (run, saveResult, timeoutMs) => await executeTurn(agentRunner, run, saveResult, timeoutMs),
|
|
5514
5512
|
subscribedReplyPolicy: createSubscribedReplyPolicy({
|
|
5515
5513
|
completeObject: overrides.subscribedReplyPolicy?.completeObject ?? completeObject
|
|
5516
5514
|
}),
|
|
@@ -5616,11 +5614,8 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
|
|
|
5616
5614
|
};
|
|
5617
5615
|
}
|
|
5618
5616
|
|
|
5619
|
-
// src/chat/providers/slack/
|
|
5620
|
-
|
|
5621
|
-
await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
|
|
5622
|
-
}
|
|
5623
|
-
function parkedInputKey(message) {
|
|
5617
|
+
// src/chat/providers/slack/input.ts
|
|
5618
|
+
function steeringMessageKey(message) {
|
|
5624
5619
|
if (message.role !== "user") {
|
|
5625
5620
|
return void 0;
|
|
5626
5621
|
}
|
|
@@ -5629,19 +5624,19 @@ function parkedInputKey(message) {
|
|
|
5629
5624
|
return `${message.timestamp}:${text}`;
|
|
5630
5625
|
}
|
|
5631
5626
|
function renderRecentThreadMessageLines(conversationContext, messages) {
|
|
5632
|
-
const
|
|
5627
|
+
const messagesForContext = messages.filter((queued) => {
|
|
5633
5628
|
if (queued.explicitMention) {
|
|
5634
5629
|
return false;
|
|
5635
5630
|
}
|
|
5636
|
-
const slackTs =
|
|
5631
|
+
const slackTs = inboundMessageActor(queued)?.slackTs;
|
|
5637
5632
|
return !slackTs || !conversationContext?.includes(`slack_ts="${slackTs}"`);
|
|
5638
5633
|
});
|
|
5639
|
-
if (
|
|
5634
|
+
if (messagesForContext.length === 0) {
|
|
5640
5635
|
return [];
|
|
5641
5636
|
}
|
|
5642
5637
|
const lines = [];
|
|
5643
|
-
for (const queued of
|
|
5644
|
-
const actor =
|
|
5638
|
+
for (const queued of messagesForContext) {
|
|
5639
|
+
const actor = inboundMessageActor(queued);
|
|
5645
5640
|
const author = escapeXml(actor?.authorName ?? "user");
|
|
5646
5641
|
const attrs = [
|
|
5647
5642
|
`role="user"`,
|
|
@@ -5658,14 +5653,13 @@ function renderRecentThreadMessageLines(conversationContext, messages) {
|
|
|
5658
5653
|
}
|
|
5659
5654
|
return lines;
|
|
5660
5655
|
}
|
|
5661
|
-
function
|
|
5662
|
-
const baseContext = options?.includeConversationContext === false ? void 0 : conversationContext;
|
|
5656
|
+
function appendRecentMessagesToContext(conversationContext, messages) {
|
|
5663
5657
|
return appendThreadContextMessages(
|
|
5664
|
-
|
|
5658
|
+
conversationContext,
|
|
5665
5659
|
renderRecentThreadMessageLines(conversationContext, messages)
|
|
5666
5660
|
);
|
|
5667
5661
|
}
|
|
5668
|
-
function
|
|
5662
|
+
function inboundMessageActor(queued) {
|
|
5669
5663
|
const actor = getMessageActorIdentity(queued.message);
|
|
5670
5664
|
const authorId = actor?.userId ?? parseActorUserId(queued.message.author.userId);
|
|
5671
5665
|
const authorName = actor?.fullName ?? actor?.userName;
|
|
@@ -5676,7 +5670,7 @@ function queuedInstructionActor(queued) {
|
|
|
5676
5670
|
...slackTs ? { slackTs } : void 0
|
|
5677
5671
|
};
|
|
5678
5672
|
}
|
|
5679
|
-
function
|
|
5673
|
+
function inboundMessageProvenance(queued, teamId) {
|
|
5680
5674
|
if (isResourceEventSlackMessage(queued.message)) {
|
|
5681
5675
|
return contextProvenance;
|
|
5682
5676
|
}
|
|
@@ -5703,12 +5697,57 @@ async function resolveChannelName(thread) {
|
|
|
5703
5697
|
return void 0;
|
|
5704
5698
|
}
|
|
5705
5699
|
}
|
|
5706
|
-
function
|
|
5700
|
+
function collectAttachments(message, batchedMessages) {
|
|
5707
5701
|
return [
|
|
5708
|
-
...(
|
|
5702
|
+
...(batchedMessages ?? []).flatMap((queued) => queued.message.attachments),
|
|
5709
5703
|
...message.attachments
|
|
5710
5704
|
];
|
|
5711
5705
|
}
|
|
5706
|
+
async function saveSteeringMessages(args) {
|
|
5707
|
+
if (args.messages.length === 0) {
|
|
5708
|
+
return true;
|
|
5709
|
+
}
|
|
5710
|
+
const state = getStateAdapter();
|
|
5711
|
+
await state.connect();
|
|
5712
|
+
const lock = await acquireActiveLock(state, args.conversationId);
|
|
5713
|
+
if (!lock) {
|
|
5714
|
+
return false;
|
|
5715
|
+
}
|
|
5716
|
+
try {
|
|
5717
|
+
const projection = await loadConversationProjection({
|
|
5718
|
+
conversationId: args.conversationId
|
|
5719
|
+
});
|
|
5720
|
+
const savedKeys = new Set(
|
|
5721
|
+
projection.messages.map(steeringMessageKey).filter((key) => key !== void 0)
|
|
5722
|
+
);
|
|
5723
|
+
const missing = args.messages.filter((entry) => {
|
|
5724
|
+
const key = steeringMessageKey(entry.message);
|
|
5725
|
+
return key === void 0 || !savedKeys.has(key);
|
|
5726
|
+
});
|
|
5727
|
+
if (missing.length === 0) {
|
|
5728
|
+
return true;
|
|
5729
|
+
}
|
|
5730
|
+
await commitMessages({
|
|
5731
|
+
conversationId: args.conversationId,
|
|
5732
|
+
messages: [
|
|
5733
|
+
...projection.messages,
|
|
5734
|
+
...missing.map((entry) => entry.message)
|
|
5735
|
+
],
|
|
5736
|
+
provenance: [
|
|
5737
|
+
...projection.provenance,
|
|
5738
|
+
...missing.map((entry) => entry.provenance)
|
|
5739
|
+
]
|
|
5740
|
+
});
|
|
5741
|
+
return true;
|
|
5742
|
+
} finally {
|
|
5743
|
+
await state.releaseLock(lock);
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
|
|
5747
|
+
// src/chat/providers/slack/turn.ts
|
|
5748
|
+
async function persistThreadRuntimeStateWithRetry(thread, patch) {
|
|
5749
|
+
await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
|
|
5750
|
+
}
|
|
5712
5751
|
async function loadPiMessagesForTurn(args) {
|
|
5713
5752
|
if (!args.conversationId) {
|
|
5714
5753
|
return {};
|
|
@@ -5810,7 +5849,7 @@ function createSlackTurn(deps) {
|
|
|
5810
5849
|
(queued) => ensureSlackMessageActorIdentity(
|
|
5811
5850
|
queued.message,
|
|
5812
5851
|
teamId,
|
|
5813
|
-
|
|
5852
|
+
lookupSlackUser
|
|
5814
5853
|
)
|
|
5815
5854
|
)
|
|
5816
5855
|
);
|
|
@@ -5827,7 +5866,7 @@ function createSlackTurn(deps) {
|
|
|
5827
5866
|
executionActor = await ensureSlackMessageActorIdentity(
|
|
5828
5867
|
message,
|
|
5829
5868
|
teamId,
|
|
5830
|
-
|
|
5869
|
+
lookupSlackUser
|
|
5831
5870
|
);
|
|
5832
5871
|
if (executionActor) {
|
|
5833
5872
|
credentialContext = credentialContextForActor(executionActor);
|
|
@@ -5905,8 +5944,8 @@ function createSlackTurn(deps) {
|
|
|
5905
5944
|
queuedMessages.map(async (queued) => {
|
|
5906
5945
|
const attachments = queued.message.attachments;
|
|
5907
5946
|
return {
|
|
5908
|
-
actor:
|
|
5909
|
-
provenance:
|
|
5947
|
+
actor: inboundMessageActor(queued),
|
|
5948
|
+
provenance: inboundMessageProvenance(queued, teamId),
|
|
5910
5949
|
text: queued.userText,
|
|
5911
5950
|
timestampMs: queued.message.metadata.dateSent.getTime(),
|
|
5912
5951
|
omittedImageAttachmentCount: !isVisionEnabled() && hasPotentialImageAttachment(attachments) ? countPotentialImageAttachments(attachments) : 0,
|
|
@@ -5922,51 +5961,11 @@ function createSlackTurn(deps) {
|
|
|
5922
5961
|
})
|
|
5923
5962
|
);
|
|
5924
5963
|
};
|
|
5925
|
-
const
|
|
5926
|
-
if (!conversationId || pairs.length === 0) {
|
|
5927
|
-
return true;
|
|
5928
|
-
}
|
|
5929
|
-
const stateAdapter = getStateAdapter();
|
|
5930
|
-
await stateAdapter.connect();
|
|
5931
|
-
const lock = await acquireActiveLock(stateAdapter, conversationId);
|
|
5932
|
-
if (!lock) {
|
|
5933
|
-
return false;
|
|
5934
|
-
}
|
|
5935
|
-
try {
|
|
5936
|
-
const projection = await loadConversationProjection({
|
|
5937
|
-
conversationId
|
|
5938
|
-
});
|
|
5939
|
-
const appendedKeys = new Set(
|
|
5940
|
-
projection.messages.map(parkedInputKey).filter((key) => key !== void 0)
|
|
5941
|
-
);
|
|
5942
|
-
const missing = pairs.filter((pair) => {
|
|
5943
|
-
const key = parkedInputKey(pair.message);
|
|
5944
|
-
return key === void 0 || !appendedKeys.has(key);
|
|
5945
|
-
});
|
|
5946
|
-
if (missing.length === 0) {
|
|
5947
|
-
return true;
|
|
5948
|
-
}
|
|
5949
|
-
await commitMessages({
|
|
5950
|
-
conversationId,
|
|
5951
|
-
messages: [
|
|
5952
|
-
...projection.messages,
|
|
5953
|
-
...missing.map((pair) => pair.message)
|
|
5954
|
-
],
|
|
5955
|
-
provenance: [
|
|
5956
|
-
...projection.provenance,
|
|
5957
|
-
...missing.map((pair) => pair.provenance)
|
|
5958
|
-
]
|
|
5959
|
-
});
|
|
5960
|
-
return true;
|
|
5961
|
-
} finally {
|
|
5962
|
-
await stateAdapter.releaseLock(lock);
|
|
5963
|
-
}
|
|
5964
|
-
};
|
|
5965
|
-
const appendParkedTurnInput = async (parkedSessionId) => {
|
|
5964
|
+
const saveMessagesForPausedTurn = async (pausedTurnId) => {
|
|
5966
5965
|
if (!conversationId) {
|
|
5967
5966
|
return true;
|
|
5968
5967
|
}
|
|
5969
|
-
const
|
|
5968
|
+
const messagesForPausedTurn = [
|
|
5970
5969
|
...options.queuedMessages ?? [],
|
|
5971
5970
|
{
|
|
5972
5971
|
explicitMention: Boolean(
|
|
@@ -5977,18 +5976,21 @@ function createSlackTurn(deps) {
|
|
|
5977
5976
|
userText: currentText.userText
|
|
5978
5977
|
}
|
|
5979
5978
|
].filter(
|
|
5980
|
-
// Redelivery of the
|
|
5979
|
+
// Redelivery of the paused Turn's own message must not duplicate
|
|
5981
5980
|
// the prompt that already started the session.
|
|
5982
|
-
(queued) => buildDeterministicTurnId(queued.message.id) !==
|
|
5981
|
+
(queued) => buildDeterministicTurnId(queued.message.id) !== pausedTurnId
|
|
5983
5982
|
);
|
|
5984
|
-
if (
|
|
5983
|
+
if (messagesForPausedTurn.length === 0) {
|
|
5985
5984
|
return true;
|
|
5986
5985
|
}
|
|
5987
|
-
const
|
|
5986
|
+
const steeringMessages = (await resolveSteeringMessages(messagesForPausedTurn)).map((steering) => ({
|
|
5988
5987
|
message: buildSteeringPiMessage(steering),
|
|
5989
5988
|
provenance: steering.provenance
|
|
5990
5989
|
}));
|
|
5991
|
-
return
|
|
5990
|
+
return saveSteeringMessages({
|
|
5991
|
+
conversationId,
|
|
5992
|
+
messages: steeringMessages
|
|
5993
|
+
});
|
|
5992
5994
|
};
|
|
5993
5995
|
if (preparedState.userMessageAlreadyReplied) {
|
|
5994
5996
|
const deliveredMessage = [...preparedState.conversation.messages].reverse().find(
|
|
@@ -6005,16 +6007,16 @@ function createSlackTurn(deps) {
|
|
|
6005
6007
|
return;
|
|
6006
6008
|
}
|
|
6007
6009
|
if (conversationId && activeTurnId) {
|
|
6008
|
-
const pausedTurn = await deps.
|
|
6010
|
+
const pausedTurn = await deps.pausedTurns.get({
|
|
6009
6011
|
conversationId,
|
|
6010
6012
|
turnId: activeTurnId
|
|
6011
6013
|
});
|
|
6012
6014
|
if (pausedTurn) {
|
|
6013
|
-
if (!await
|
|
6015
|
+
if (!await saveMessagesForPausedTurn(pausedTurn.turnId)) {
|
|
6014
6016
|
throw new TurnInputDeferredError();
|
|
6015
6017
|
}
|
|
6016
6018
|
try {
|
|
6017
|
-
await deps.
|
|
6019
|
+
await deps.pausedTurns.wake(pausedTurn);
|
|
6018
6020
|
} catch (error) {
|
|
6019
6021
|
logException(error, "agent.continue.schedule.failed", {
|
|
6020
6022
|
"app.ai.resume_session_version": pausedTurn.expectedVersion,
|
|
@@ -6039,6 +6041,7 @@ function createSlackTurn(deps) {
|
|
|
6039
6041
|
await abandonTurnRecord({
|
|
6040
6042
|
conversationId,
|
|
6041
6043
|
turnId: activeTurnId,
|
|
6044
|
+
// TODO(dcramer): Rename this legacy text in Slack and Conversation API Turn records together.
|
|
6042
6045
|
errorMessage: "Auth-parked session superseded by a new user message"
|
|
6043
6046
|
});
|
|
6044
6047
|
markTurnClosed({
|
|
@@ -6108,7 +6111,7 @@ function createSlackTurn(deps) {
|
|
|
6108
6111
|
nextTurnId: turnId
|
|
6109
6112
|
});
|
|
6110
6113
|
if (conversationId && preparedState.userMessageId) {
|
|
6111
|
-
await deps.
|
|
6114
|
+
await deps.turnLifecycle.start({
|
|
6112
6115
|
conversationId,
|
|
6113
6116
|
createdAtMs: Date.now(),
|
|
6114
6117
|
inputMessageIds: [
|
|
@@ -6168,7 +6171,7 @@ function createSlackTurn(deps) {
|
|
|
6168
6171
|
if (actor?.userName) {
|
|
6169
6172
|
setTags({ userName: actor.userName });
|
|
6170
6173
|
}
|
|
6171
|
-
const turnAttachments =
|
|
6174
|
+
const turnAttachments = collectAttachments(
|
|
6172
6175
|
message,
|
|
6173
6176
|
options.queuedMessages
|
|
6174
6177
|
);
|
|
@@ -6197,7 +6200,6 @@ function createSlackTurn(deps) {
|
|
|
6197
6200
|
let runResultHandled = false;
|
|
6198
6201
|
let assistantMessageDelivered = false;
|
|
6199
6202
|
let acceptedDeliveryId;
|
|
6200
|
-
let lifecycleTerminalized = false;
|
|
6201
6203
|
let turnCompletionNotified = false;
|
|
6202
6204
|
const recordDispatchOutcome = async (dispatchOutcome, state) => {
|
|
6203
6205
|
const dispatchId = options.execution?.dispatch?.id;
|
|
@@ -6222,7 +6224,6 @@ function createSlackTurn(deps) {
|
|
|
6222
6224
|
};
|
|
6223
6225
|
let turnWakeError;
|
|
6224
6226
|
let boundaryFailureCode = "agent_run_failed";
|
|
6225
|
-
let finalizedFailureEventId;
|
|
6226
6227
|
let terminalDispatchFailureOutcome;
|
|
6227
6228
|
const notifyTurnCompleted = async () => {
|
|
6228
6229
|
if (turnCompletionNotified) {
|
|
@@ -6354,7 +6355,7 @@ function createSlackTurn(deps) {
|
|
|
6354
6355
|
});
|
|
6355
6356
|
let piMessages = loadedPiMessages.piMessages;
|
|
6356
6357
|
if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
|
|
6357
|
-
const compaction = await deps.
|
|
6358
|
+
const compaction = await deps.contextCompactor.maybeCompact({
|
|
6358
6359
|
conversation: preparedState.conversation,
|
|
6359
6360
|
conversationContext: preparedState.conversationContext,
|
|
6360
6361
|
conversationId,
|
|
@@ -6379,7 +6380,7 @@ function createSlackTurn(deps) {
|
|
|
6379
6380
|
}
|
|
6380
6381
|
}
|
|
6381
6382
|
const hasDurablePromptHistory = Boolean(piMessages?.length);
|
|
6382
|
-
const
|
|
6383
|
+
const batchedSteeringMessages = (await resolveSteeringMessages(
|
|
6383
6384
|
(options.queuedMessages ?? []).filter(
|
|
6384
6385
|
(queued) => queued.explicitMention
|
|
6385
6386
|
)
|
|
@@ -6387,20 +6388,23 @@ function createSlackTurn(deps) {
|
|
|
6387
6388
|
message: buildSteeringPiMessage(steering),
|
|
6388
6389
|
provenance: steering.provenance
|
|
6389
6390
|
}));
|
|
6390
|
-
if (!await
|
|
6391
|
+
if (!await saveSteeringMessages({
|
|
6392
|
+
conversationId,
|
|
6393
|
+
messages: batchedSteeringMessages
|
|
6394
|
+
})) {
|
|
6391
6395
|
shouldPersistFailureState = false;
|
|
6392
6396
|
throw new TurnInputDeferredError();
|
|
6393
6397
|
}
|
|
6394
|
-
if (
|
|
6398
|
+
if (batchedSteeringMessages.length > 0) {
|
|
6395
6399
|
const presentKeys = new Set(
|
|
6396
|
-
(piMessages ?? []).map(
|
|
6400
|
+
(piMessages ?? []).map(steeringMessageKey).filter((key) => key !== void 0)
|
|
6397
6401
|
);
|
|
6398
|
-
const
|
|
6399
|
-
const key =
|
|
6402
|
+
const newSteeringMessages = batchedSteeringMessages.map((entry) => entry.message).filter((batchedMessage) => {
|
|
6403
|
+
const key = steeringMessageKey(batchedMessage);
|
|
6400
6404
|
return key === void 0 || !presentKeys.has(key);
|
|
6401
6405
|
});
|
|
6402
|
-
if (
|
|
6403
|
-
piMessages = [...piMessages ?? [], ...
|
|
6406
|
+
if (newSteeringMessages.length > 0) {
|
|
6407
|
+
piMessages = [...piMessages ?? [], ...newSteeringMessages];
|
|
6404
6408
|
}
|
|
6405
6409
|
}
|
|
6406
6410
|
status.update();
|
|
@@ -6420,7 +6424,7 @@ function createSlackTurn(deps) {
|
|
|
6420
6424
|
const toolChannelId = channelId;
|
|
6421
6425
|
const activeInstructionAuthorId = actor?.userId ?? parseActorUserId(message.author.userId);
|
|
6422
6426
|
const activeInstructionAuthorName = actor?.fullName ?? actor?.userName;
|
|
6423
|
-
const promptConversationContext =
|
|
6427
|
+
const promptConversationContext = appendRecentMessagesToContext(
|
|
6424
6428
|
preparedState.conversationContext,
|
|
6425
6429
|
options.queuedMessages ?? []
|
|
6426
6430
|
);
|
|
@@ -6435,7 +6439,7 @@ function createSlackTurn(deps) {
|
|
|
6435
6439
|
);
|
|
6436
6440
|
return acceptedMessages ?? await resolveSteeringMessages(drained);
|
|
6437
6441
|
} : void 0;
|
|
6438
|
-
const
|
|
6442
|
+
const run = {
|
|
6439
6443
|
conversationId,
|
|
6440
6444
|
turnId,
|
|
6441
6445
|
...runId ? { runId } : void 0,
|
|
@@ -6507,7 +6511,121 @@ function createSlackTurn(deps) {
|
|
|
6507
6511
|
});
|
|
6508
6512
|
}
|
|
6509
6513
|
}
|
|
6510
|
-
}
|
|
6514
|
+
};
|
|
6515
|
+
let completedResult;
|
|
6516
|
+
const saveResult = async (result) => {
|
|
6517
|
+
let finalResult = result;
|
|
6518
|
+
setTags({ modelId: finalResult.diagnostics.modelId });
|
|
6519
|
+
const diagnosticsAttributes = getAgentTurnDiagnosticsAttributes(finalResult);
|
|
6520
|
+
setSpanAttributes(diagnosticsAttributes);
|
|
6521
|
+
let failureEventId;
|
|
6522
|
+
let failureReason;
|
|
6523
|
+
if (finalResult.diagnostics.outcome !== "success") {
|
|
6524
|
+
const finalized = finalizeFailedTurnReplyWithEvent({
|
|
6525
|
+
reply: finalResult,
|
|
6526
|
+
logException
|
|
6527
|
+
});
|
|
6528
|
+
finalResult = finalized.reply;
|
|
6529
|
+
failureEventId = finalized.eventId;
|
|
6530
|
+
failureReason = finalized.failureReason;
|
|
6531
|
+
await deliverAssistantMessage(finalResult.text);
|
|
6532
|
+
}
|
|
6533
|
+
const turnResult = finalResult.diagnostics.outcome === "success" ? { outcome: "completed" } : {
|
|
6534
|
+
errorMessage: finalResult.diagnostics.errorMessage ?? `Agent turn ended with ${finalResult.diagnostics.outcome}.`,
|
|
6535
|
+
outcome: "failed"
|
|
6536
|
+
};
|
|
6537
|
+
runResultHandled = true;
|
|
6538
|
+
shouldPersistFailureState = false;
|
|
6539
|
+
boundaryFailureCode = "agent_run_failed";
|
|
6540
|
+
const completedState = buildDeliveredTurnStatePatch({
|
|
6541
|
+
conversation: preparedState.conversation,
|
|
6542
|
+
reply: finalResult,
|
|
6543
|
+
sessionId: turnId,
|
|
6544
|
+
userMessageId: preparedState.userMessageId
|
|
6545
|
+
});
|
|
6546
|
+
let saveFailed = false;
|
|
6547
|
+
let saveFailureEventId;
|
|
6548
|
+
try {
|
|
6549
|
+
if (conversationId && finalResult.piMessages?.length) {
|
|
6550
|
+
await saveTurnCheckpoint({
|
|
6551
|
+
mode: "completed",
|
|
6552
|
+
channelName,
|
|
6553
|
+
conversationId,
|
|
6554
|
+
turnId,
|
|
6555
|
+
durationMs: finalResult.diagnostics.durationMs,
|
|
6556
|
+
usage: finalResult.diagnostics.usage,
|
|
6557
|
+
destination,
|
|
6558
|
+
destinationVisibility,
|
|
6559
|
+
source,
|
|
6560
|
+
sliceId: 1,
|
|
6561
|
+
dispatchOutcome: finalResult.diagnostics.outcome === "success" ? "completed" : "failed",
|
|
6562
|
+
...options.execution?.dispatch && turnResult.errorMessage ? { errorMessage: turnResult.errorMessage } : void 0,
|
|
6563
|
+
...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
|
|
6564
|
+
messages: finalResult.piMessages,
|
|
6565
|
+
actor: executionActor,
|
|
6566
|
+
surface: options.execution?.surface ?? "slack",
|
|
6567
|
+
dispatchId: options.execution?.dispatch?.id
|
|
6568
|
+
});
|
|
6569
|
+
} else if (conversationId) {
|
|
6570
|
+
await recordTurnSummary({
|
|
6571
|
+
channelName,
|
|
6572
|
+
conversationId,
|
|
6573
|
+
cumulativeDurationMs: finalResult.diagnostics.durationMs,
|
|
6574
|
+
cumulativeUsage: finalResult.diagnostics.usage,
|
|
6575
|
+
turnId,
|
|
6576
|
+
sliceId: 1,
|
|
6577
|
+
startedAtMs: message.metadata.dateSent.getTime(),
|
|
6578
|
+
state: "completed",
|
|
6579
|
+
actor: executionActor,
|
|
6580
|
+
destination,
|
|
6581
|
+
destinationVisibility,
|
|
6582
|
+
source,
|
|
6583
|
+
surface: options.execution?.surface ?? "slack",
|
|
6584
|
+
dispatchId: options.execution?.dispatch?.id,
|
|
6585
|
+
dispatchOutcome: finalResult.diagnostics.outcome === "success" ? "completed" : "failed",
|
|
6586
|
+
...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
|
|
6587
|
+
traceId: getActiveTraceId()
|
|
6588
|
+
});
|
|
6589
|
+
}
|
|
6590
|
+
await persistWithRetry(
|
|
6591
|
+
() => persistConversationMessages({
|
|
6592
|
+
conversation: completedState.conversation,
|
|
6593
|
+
conversationId
|
|
6594
|
+
})
|
|
6595
|
+
);
|
|
6596
|
+
await persistThreadRuntimeStateWithRetry(thread, completedState);
|
|
6597
|
+
} catch (saveError) {
|
|
6598
|
+
saveFailed = true;
|
|
6599
|
+
saveFailureEventId = logException(
|
|
6600
|
+
saveError,
|
|
6601
|
+
"slack.reply.post_delivery_commit.failed",
|
|
6602
|
+
messageTs ? { "messaging.message.id": messageTs } : {}
|
|
6603
|
+
);
|
|
6604
|
+
}
|
|
6605
|
+
preparedState.conversation = completedState.conversation;
|
|
6606
|
+
persistedAtLeastOnce = true;
|
|
6607
|
+
options.onTurnOutcome?.(turnResult);
|
|
6608
|
+
completedResult = finalResult;
|
|
6609
|
+
if (saveFailed) {
|
|
6610
|
+
return {
|
|
6611
|
+
...saveFailureEventId ? { eventId: saveFailureEventId } : void 0,
|
|
6612
|
+
failureCode: "persistence_failed",
|
|
6613
|
+
outcome: "failed"
|
|
6614
|
+
};
|
|
6615
|
+
}
|
|
6616
|
+
if (finalResult.diagnostics.outcome === "success") {
|
|
6617
|
+
return {
|
|
6618
|
+
outcome: assistantMessageDelivered ? "success" : "no_reply"
|
|
6619
|
+
};
|
|
6620
|
+
}
|
|
6621
|
+
return {
|
|
6622
|
+
...failureEventId ? { eventId: failureEventId } : void 0,
|
|
6623
|
+
failureCode: "model_execution_failed",
|
|
6624
|
+
...failureReason ? { failureReason } : void 0,
|
|
6625
|
+
outcome: "failed"
|
|
6626
|
+
};
|
|
6627
|
+
};
|
|
6628
|
+
const outcome = await deps.executeTurn(run, saveResult);
|
|
6511
6629
|
if (outcome.status === "awaiting_auth") {
|
|
6512
6630
|
await recordDispatchOutcome("blocked", "failed");
|
|
6513
6631
|
options.onTurnOutcome?.({ outcome: "blocked" });
|
|
@@ -6530,14 +6648,13 @@ function createSlackTurn(deps) {
|
|
|
6530
6648
|
conversation: preparedState.conversation
|
|
6531
6649
|
});
|
|
6532
6650
|
if (conversationId) {
|
|
6533
|
-
await deps.
|
|
6651
|
+
await deps.turnLifecycle.fail({
|
|
6534
6652
|
conversationId,
|
|
6535
6653
|
createdAtMs: Date.now(),
|
|
6536
6654
|
...authFailureEventId ? { eventId: authFailureEventId } : void 0,
|
|
6537
6655
|
failureCode: "agent_run_failed",
|
|
6538
6656
|
turnId
|
|
6539
6657
|
});
|
|
6540
|
-
lifecycleTerminalized = true;
|
|
6541
6658
|
}
|
|
6542
6659
|
persistedAtLeastOnce = true;
|
|
6543
6660
|
shouldPersistFailureState = false;
|
|
@@ -6570,7 +6687,7 @@ function createSlackTurn(deps) {
|
|
|
6570
6687
|
);
|
|
6571
6688
|
}
|
|
6572
6689
|
try {
|
|
6573
|
-
await deps.
|
|
6690
|
+
await deps.pausedTurns.wake({
|
|
6574
6691
|
conversationId,
|
|
6575
6692
|
destination,
|
|
6576
6693
|
turnId,
|
|
@@ -6588,134 +6705,26 @@ function createSlackTurn(deps) {
|
|
|
6588
6705
|
}
|
|
6589
6706
|
return;
|
|
6590
6707
|
}
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
const diagnosticsAttributes = getAgentTurnDiagnosticsAttributes(reply);
|
|
6594
|
-
setSpanAttributes(diagnosticsAttributes);
|
|
6595
|
-
if (reply.diagnostics.outcome !== "success") {
|
|
6596
|
-
const finalized = finalizeFailedTurnReplyWithEvent({
|
|
6597
|
-
reply,
|
|
6598
|
-
logException
|
|
6599
|
-
});
|
|
6600
|
-
reply = finalized.reply;
|
|
6601
|
-
finalizedFailureEventId = finalized.eventId;
|
|
6602
|
-
await deliverAssistantMessage(reply.text);
|
|
6603
|
-
}
|
|
6604
|
-
const turnResult = reply.diagnostics.outcome === "success" ? { outcome: "completed" } : {
|
|
6605
|
-
errorMessage: reply.diagnostics.errorMessage ?? `Agent turn ended with ${reply.diagnostics.outcome}.`,
|
|
6606
|
-
outcome: "failed"
|
|
6607
|
-
};
|
|
6608
|
-
runResultHandled = true;
|
|
6609
|
-
shouldPersistFailureState = false;
|
|
6610
|
-
boundaryFailureCode = "agent_run_failed";
|
|
6611
|
-
const completedState = buildDeliveredTurnStatePatch({
|
|
6612
|
-
conversation: preparedState.conversation,
|
|
6613
|
-
reply,
|
|
6614
|
-
sessionId: turnId,
|
|
6615
|
-
userMessageId: preparedState.userMessageId
|
|
6616
|
-
});
|
|
6617
|
-
try {
|
|
6618
|
-
if (conversationId && reply.piMessages?.length) {
|
|
6619
|
-
await saveTurnCheckpoint({
|
|
6620
|
-
mode: "completed",
|
|
6621
|
-
channelName,
|
|
6622
|
-
conversationId,
|
|
6623
|
-
turnId,
|
|
6624
|
-
durationMs: reply.diagnostics.durationMs,
|
|
6625
|
-
usage: reply.diagnostics.usage,
|
|
6626
|
-
destination,
|
|
6627
|
-
destinationVisibility,
|
|
6628
|
-
source,
|
|
6629
|
-
sliceId: 1,
|
|
6630
|
-
dispatchOutcome: reply.diagnostics.outcome === "success" ? "completed" : "failed",
|
|
6631
|
-
...options.execution?.dispatch && turnResult.errorMessage ? { errorMessage: turnResult.errorMessage } : void 0,
|
|
6632
|
-
...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
|
|
6633
|
-
messages: reply.piMessages,
|
|
6634
|
-
actor: executionActor,
|
|
6635
|
-
surface: options.execution?.surface ?? "slack",
|
|
6636
|
-
dispatchId: options.execution?.dispatch?.id
|
|
6637
|
-
});
|
|
6638
|
-
} else if (conversationId) {
|
|
6639
|
-
await recordTurnSummary({
|
|
6640
|
-
channelName,
|
|
6641
|
-
conversationId,
|
|
6642
|
-
cumulativeDurationMs: reply.diagnostics.durationMs,
|
|
6643
|
-
cumulativeUsage: reply.diagnostics.usage,
|
|
6644
|
-
turnId,
|
|
6645
|
-
sliceId: 1,
|
|
6646
|
-
startedAtMs: message.metadata.dateSent.getTime(),
|
|
6647
|
-
state: "completed",
|
|
6648
|
-
actor: executionActor,
|
|
6649
|
-
destination,
|
|
6650
|
-
destinationVisibility,
|
|
6651
|
-
source,
|
|
6652
|
-
surface: options.execution?.surface ?? "slack",
|
|
6653
|
-
dispatchId: options.execution?.dispatch?.id,
|
|
6654
|
-
dispatchOutcome: reply.diagnostics.outcome === "success" ? "completed" : "failed",
|
|
6655
|
-
...acceptedDeliveryId ? { resultMessageId: acceptedDeliveryId } : void 0,
|
|
6656
|
-
traceId: getActiveTraceId()
|
|
6657
|
-
});
|
|
6658
|
-
}
|
|
6659
|
-
await persistWithRetry(
|
|
6660
|
-
() => persistConversationMessages({
|
|
6661
|
-
conversation: completedState.conversation,
|
|
6662
|
-
conversationId
|
|
6663
|
-
})
|
|
6664
|
-
);
|
|
6665
|
-
await persistThreadRuntimeStateWithRetry(thread, completedState);
|
|
6666
|
-
} catch (commitError) {
|
|
6667
|
-
const persistenceEventId = logException(
|
|
6668
|
-
commitError,
|
|
6669
|
-
"slack.reply.post_delivery_commit.failed",
|
|
6670
|
-
messageTs ? { "messaging.message.id": messageTs } : {}
|
|
6671
|
-
);
|
|
6672
|
-
if (conversationId && !lifecycleTerminalized) {
|
|
6673
|
-
await deps.services.turnLifecycle.fail({
|
|
6674
|
-
conversationId,
|
|
6675
|
-
createdAtMs: Date.now(),
|
|
6676
|
-
...persistenceEventId ? { eventId: persistenceEventId } : void 0,
|
|
6677
|
-
failureCode: "persistence_failed",
|
|
6678
|
-
turnId
|
|
6679
|
-
});
|
|
6680
|
-
lifecycleTerminalized = true;
|
|
6681
|
-
}
|
|
6682
|
-
}
|
|
6683
|
-
preparedState.conversation = completedState.conversation;
|
|
6684
|
-
persistedAtLeastOnce = true;
|
|
6685
|
-
options.onTurnOutcome?.(turnResult);
|
|
6686
|
-
if (!lifecycleTerminalized && conversationId) {
|
|
6687
|
-
if (reply.diagnostics.outcome === "success") {
|
|
6688
|
-
await deps.services.turnLifecycle.complete({
|
|
6689
|
-
conversationId,
|
|
6690
|
-
createdAtMs: Date.now(),
|
|
6691
|
-
outcome: assistantMessageDelivered ? "success" : "no_reply",
|
|
6692
|
-
turnId
|
|
6693
|
-
});
|
|
6694
|
-
} else {
|
|
6695
|
-
await deps.services.turnLifecycle.fail({
|
|
6696
|
-
conversationId,
|
|
6697
|
-
createdAtMs: Date.now(),
|
|
6698
|
-
...finalizedFailureEventId ? { eventId: finalizedFailureEventId } : void 0,
|
|
6699
|
-
failureCode: "model_execution_failed",
|
|
6700
|
-
turnId
|
|
6701
|
-
});
|
|
6702
|
-
}
|
|
6703
|
-
lifecycleTerminalized = true;
|
|
6708
|
+
if (!completedResult) {
|
|
6709
|
+
throw new Error("Completed Turn did not save a result");
|
|
6704
6710
|
}
|
|
6705
6711
|
if (shouldEmitDevAgentTrace()) {
|
|
6706
6712
|
logInfo("agent.turn.completed", {
|
|
6707
|
-
"app.ai.outcome":
|
|
6708
|
-
"app.ai.tool_call_count":
|
|
6709
|
-
"app.ai.tool_error_results":
|
|
6713
|
+
"app.ai.outcome": completedResult.diagnostics.outcome,
|
|
6714
|
+
"app.ai.tool_call_count": completedResult.diagnostics.toolCalls.length,
|
|
6715
|
+
"app.ai.tool_error_results": completedResult.diagnostics.toolErrorCount
|
|
6710
6716
|
});
|
|
6711
6717
|
}
|
|
6712
6718
|
await notifyTurnCompleted();
|
|
6713
|
-
if (
|
|
6719
|
+
if (completedResult.diagnostics.outcome === "success" && conversationId) {
|
|
6714
6720
|
try {
|
|
6715
|
-
await
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6721
|
+
await scheduleSessionCompletedPluginTasks(
|
|
6722
|
+
{
|
|
6723
|
+
conversationId,
|
|
6724
|
+
sessionId: turnId
|
|
6725
|
+
},
|
|
6726
|
+
deps.sendPluginTask ? { send: deps.sendPluginTask } : void 0
|
|
6727
|
+
);
|
|
6719
6728
|
} catch (error) {
|
|
6720
6729
|
logException(
|
|
6721
6730
|
error,
|
|
@@ -6754,8 +6763,9 @@ function createSlackTurn(deps) {
|
|
|
6754
6763
|
shouldPersistFailureState = true;
|
|
6755
6764
|
}
|
|
6756
6765
|
shouldPersistFailureState = true;
|
|
6757
|
-
const
|
|
6758
|
-
const
|
|
6766
|
+
const runFailure = error instanceof AgentRunError ? error.cause : error;
|
|
6767
|
+
const classifiedFailure = getConversationTurnBoundaryError(runFailure);
|
|
6768
|
+
const failureCause = classifiedFailure?.cause ?? runFailure;
|
|
6759
6769
|
if (failureCause instanceof AuthorizationFlowDisabledError || failureCause instanceof PluginCredentialFailureError) {
|
|
6760
6770
|
terminalDispatchFailureOutcome = "blocked";
|
|
6761
6771
|
}
|
|
@@ -7147,7 +7157,7 @@ function createSlackDispatchTurnRunner(options) {
|
|
|
7147
7157
|
isSubscribedContext: true
|
|
7148
7158
|
});
|
|
7149
7159
|
let outcome;
|
|
7150
|
-
let
|
|
7160
|
+
let errorMessage3;
|
|
7151
7161
|
let resultMessageTs;
|
|
7152
7162
|
const routing = buildDispatchRoutingContext(dispatch2);
|
|
7153
7163
|
await options.executeSlackTurn(thread, message, {
|
|
@@ -7173,13 +7183,13 @@ function createSlackDispatchTurnRunner(options) {
|
|
|
7173
7183
|
},
|
|
7174
7184
|
onTurnOutcome: (result) => {
|
|
7175
7185
|
outcome = result.outcome;
|
|
7176
|
-
|
|
7186
|
+
errorMessage3 = result.errorMessage;
|
|
7177
7187
|
},
|
|
7178
7188
|
shouldYield: hooks.shouldYield,
|
|
7179
7189
|
skipBackfill: true
|
|
7180
7190
|
});
|
|
7181
7191
|
return {
|
|
7182
|
-
...
|
|
7192
|
+
...errorMessage3 ? { errorMessage: errorMessage3 } : void 0,
|
|
7183
7193
|
...outcome ? { outcome } : void 0,
|
|
7184
7194
|
...resultMessageTs ? { resultMessageTs } : void 0
|
|
7185
7195
|
};
|
|
@@ -7209,6 +7219,9 @@ function upsertSkippedConversationMessage(conversation, args) {
|
|
|
7209
7219
|
}
|
|
7210
7220
|
function createSlackRuntime(options) {
|
|
7211
7221
|
const services = createJuniorRuntimeServices(options.services);
|
|
7222
|
+
const turnLifecycle = new ConversationTurnLifecycleService(
|
|
7223
|
+
getConversationEventStore()
|
|
7224
|
+
);
|
|
7212
7225
|
const prepareTurnState = createPrepareTurnState({
|
|
7213
7226
|
compactConversationIfNeeded: services.conversationMemory.compactConversationIfNeeded,
|
|
7214
7227
|
hydrateConversationVisionContext: services.visionContext.hydrateConversationVisionContext,
|
|
@@ -7224,34 +7237,29 @@ function createSlackRuntime(options) {
|
|
|
7224
7237
|
(message) => ensureSlackMessageActorIdentity(
|
|
7225
7238
|
message,
|
|
7226
7239
|
destination.teamId,
|
|
7227
|
-
|
|
7240
|
+
lookupSlackUser
|
|
7228
7241
|
)
|
|
7229
7242
|
)
|
|
7230
7243
|
);
|
|
7231
7244
|
}
|
|
7232
7245
|
});
|
|
7233
7246
|
const executeSlackTurn = createSlackTurn({
|
|
7247
|
+
contextCompactor: services.contextCompactor,
|
|
7248
|
+
executeTurn: services.executeTurn,
|
|
7234
7249
|
getSlackAdapter: options.getSlackAdapter,
|
|
7250
|
+
pausedTurns: options.pausedTurns ?? createPausedTurns(),
|
|
7235
7251
|
prepareTurnState,
|
|
7236
7252
|
resolveUserAttachments: services.visionContext.resolveUserAttachments,
|
|
7237
|
-
|
|
7253
|
+
sendPluginTask: options.sendPluginTask,
|
|
7254
|
+
turnLifecycle
|
|
7238
7255
|
});
|
|
7239
7256
|
const runtime = createSlackTurnRuntime({
|
|
7240
7257
|
assistantUserName: botConfig.userName,
|
|
7241
7258
|
cancelEventSubscriptions: cancelSubscriptions,
|
|
7259
|
+
getBotUserId: () => options.getSlackAdapter().botUserId,
|
|
7242
7260
|
modelId: defaultModelId(botConfig),
|
|
7243
7261
|
now: options.now ?? (() => Date.now()),
|
|
7244
|
-
|
|
7245
|
-
getChannelId,
|
|
7246
|
-
getRunId,
|
|
7247
|
-
stripLeadingBotMention: (text, stripOptions) => stripLeadingBotMention(text, {
|
|
7248
|
-
...stripOptions,
|
|
7249
|
-
botUserId: options.getSlackAdapter().botUserId
|
|
7250
|
-
}),
|
|
7251
|
-
withSpan,
|
|
7252
|
-
logWarn,
|
|
7253
|
-
logException,
|
|
7254
|
-
failConversationTurn: (input) => services.replyExecutor.turnLifecycle.fail(input),
|
|
7262
|
+
failConversationTurn: (input) => turnLifecycle.fail(input),
|
|
7255
7263
|
prepareTurnState,
|
|
7256
7264
|
persistPreparedState: async ({ thread, preparedState }) => {
|
|
7257
7265
|
await persistThreadState(thread, {
|
|
@@ -7411,7 +7419,7 @@ async function failTurnBestEffort(args) {
|
|
|
7411
7419
|
);
|
|
7412
7420
|
}
|
|
7413
7421
|
}
|
|
7414
|
-
async function persistFailedReplyState(turn,
|
|
7422
|
+
async function persistFailedReplyState(turn, errorMessage3 = "Paused agent run failed while continuing") {
|
|
7415
7423
|
const currentState = await getPersistedThreadState(turn.conversationId);
|
|
7416
7424
|
const conversation = coerceThreadConversationState(currentState);
|
|
7417
7425
|
await hydrateConversationMessages({
|
|
@@ -7428,7 +7436,7 @@ async function persistFailedReplyState(turn, errorMessage = "Paused agent run fa
|
|
|
7428
7436
|
});
|
|
7429
7437
|
await failTurnBestEffort({
|
|
7430
7438
|
turn,
|
|
7431
|
-
errorMessage
|
|
7439
|
+
errorMessage: errorMessage3
|
|
7432
7440
|
});
|
|
7433
7441
|
await persistThreadStateById(turn.conversationId, {
|
|
7434
7442
|
conversation
|
|
@@ -7552,8 +7560,7 @@ async function runPausedTurnInContext(payload, options, runOptions) {
|
|
|
7552
7560
|
"Paused turn"
|
|
7553
7561
|
);
|
|
7554
7562
|
const wakePausedTurn2 = options.wakePausedTurn ?? wakePausedTurn;
|
|
7555
|
-
|
|
7556
|
-
return await resumeTurn({
|
|
7563
|
+
return await resumeSlackTurn({
|
|
7557
7564
|
messageText: "",
|
|
7558
7565
|
conversationId: payload.conversationId,
|
|
7559
7566
|
turnId: payload.turnId,
|
|
@@ -7562,7 +7569,7 @@ async function runPausedTurnInContext(payload, options, runOptions) {
|
|
|
7562
7569
|
lockKey: payload.conversationId,
|
|
7563
7570
|
// Queue continue runs under the conversation work lease already.
|
|
7564
7571
|
ownsConversationLease: true,
|
|
7565
|
-
|
|
7572
|
+
executeTurn: async (run, saveResult, timeoutMs) => await executeTurn(options.agentRunner, run, saveResult, timeoutMs),
|
|
7566
7573
|
scheduleSessionCompletedPluginTasks: options.scheduleSessionCompletedPluginTasks,
|
|
7567
7574
|
beforeStart: async () => {
|
|
7568
7575
|
let turn;
|
|
@@ -7650,7 +7657,7 @@ async function runPausedTurnInContext(payload, options, runOptions) {
|
|
|
7650
7657
|
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
7651
7658
|
inputMessageIds: [userMessage.id],
|
|
7652
7659
|
initialStatus: latestReportedProgress(turnMessages),
|
|
7653
|
-
|
|
7660
|
+
run: {
|
|
7654
7661
|
instruction: {
|
|
7655
7662
|
...conversationContext ? { context: conversationContext } : void 0,
|
|
7656
7663
|
// Attachment fields come from the turn user message context helper.
|
|
@@ -7888,24 +7895,18 @@ async function runNextPausedTurnInContext(conversationId, options, runOptions) {
|
|
|
7888
7895
|
// src/chat/app/conversation-work.ts
|
|
7889
7896
|
function createConversationWork(options) {
|
|
7890
7897
|
const apiTurnCancellation = createApiTurnCancellation();
|
|
7891
|
-
const
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
getPausedTurnRequest: options.services?.replyExecutor?.getPausedTurnRequest ?? (async (request) => await getPausedTurnRequest({
|
|
7897
|
-
...request,
|
|
7898
|
-
conversationStore: options.conversationStore
|
|
7899
|
-
})),
|
|
7900
|
-
wakePausedTurn: options.services?.replyExecutor?.wakePausedTurn ?? (async (request) => await wakePausedTurn(request, {
|
|
7901
|
-
queue: options.queue,
|
|
7902
|
-
state: options.state
|
|
7903
|
-
}))
|
|
7904
|
-
}
|
|
7905
|
-
};
|
|
7898
|
+
const pausedTurns = createPausedTurns({
|
|
7899
|
+
conversationStore: options.conversationStore,
|
|
7900
|
+
queue: options.queue,
|
|
7901
|
+
...options.state ? { state: options.state } : void 0
|
|
7902
|
+
});
|
|
7906
7903
|
const runtime = createSlackRuntime({
|
|
7907
7904
|
getSlackAdapter: options.getSlackAdapter,
|
|
7908
|
-
|
|
7905
|
+
pausedTurns,
|
|
7906
|
+
services: {
|
|
7907
|
+
...options.services,
|
|
7908
|
+
agentRunner: options.agentRunner
|
|
7909
|
+
}
|
|
7909
7910
|
});
|
|
7910
7911
|
const slackWorker = createSlackConversationWorker({
|
|
7911
7912
|
getSlackAdapter: options.getSlackAdapter,
|
|
@@ -7914,8 +7915,7 @@ function createConversationWork(options) {
|
|
|
7914
7915
|
conversationId,
|
|
7915
7916
|
{
|
|
7916
7917
|
agentRunner: options.agentRunner,
|
|
7917
|
-
|
|
7918
|
-
wakePausedTurn: services.replyExecutor?.wakePausedTurn
|
|
7918
|
+
wakePausedTurn: pausedTurns.wake
|
|
7919
7919
|
},
|
|
7920
7920
|
runOptions
|
|
7921
7921
|
),
|
|
@@ -7930,8 +7930,7 @@ function createConversationWork(options) {
|
|
|
7930
7930
|
agentRunner: options.agentRunner,
|
|
7931
7931
|
inputMessageIds: [getDispatchInputMessageId(dispatch2.id)],
|
|
7932
7932
|
routingContext: buildDispatchRoutingContext(dispatch2),
|
|
7933
|
-
|
|
7934
|
-
wakePausedTurn: services.replyExecutor?.wakePausedTurn
|
|
7933
|
+
wakePausedTurn: pausedTurns.wake
|
|
7935
7934
|
},
|
|
7936
7935
|
{ shouldYield: hooks.shouldYield }
|
|
7937
7936
|
);
|
|
@@ -7947,11 +7946,10 @@ function createConversationWork(options) {
|
|
|
7947
7946
|
conversationStore: options.conversationStore,
|
|
7948
7947
|
queue: options.queue,
|
|
7949
7948
|
run: routeApiTurnWork({
|
|
7950
|
-
apiTurnWorker: createApiTurnWorker(
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
}),
|
|
7949
|
+
apiTurnWorker: createApiTurnWorker(
|
|
7950
|
+
options.agentRunner,
|
|
7951
|
+
apiTurnCancellation
|
|
7952
|
+
),
|
|
7955
7953
|
fallbackWorker: routeAgentInvocationWork({
|
|
7956
7954
|
invocationWorker: createAgentInvocationWorker(options.agentRunner),
|
|
7957
7955
|
fallbackWorker: providerWorker
|
|
@@ -8189,7 +8187,7 @@ async function stopApiConversationTurn(args) {
|
|
|
8189
8187
|
return result;
|
|
8190
8188
|
}
|
|
8191
8189
|
|
|
8192
|
-
// src/
|
|
8190
|
+
// src/api/acp/conversations.ts
|
|
8193
8191
|
var EVENT_PAGE_SIZE = 50;
|
|
8194
8192
|
function actorFromUser(user) {
|
|
8195
8193
|
return webActorFromEmail(
|
|
@@ -8227,7 +8225,7 @@ async function latestEventCursor(eventStore, conversationId) {
|
|
|
8227
8225
|
const page = await eventStore.query(conversationId, { limit: 1 });
|
|
8228
8226
|
return page.events.at(-1)?.seq ?? 0;
|
|
8229
8227
|
}
|
|
8230
|
-
function
|
|
8228
|
+
function createAcpConversations(options) {
|
|
8231
8229
|
return {
|
|
8232
8230
|
async cancel({ conversationId, user }) {
|
|
8233
8231
|
if (!await hasConversationAccess(conversationId, user)) {
|
|
@@ -8340,83 +8338,2034 @@ function createAdapterConversations(options) {
|
|
|
8340
8338
|
};
|
|
8341
8339
|
}
|
|
8342
8340
|
|
|
8343
|
-
// src/
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
createDashboardApp: mod.createDashboardApp,
|
|
8371
|
-
functionMaxDurationSeconds: mod.functionMaxDurationSeconds,
|
|
8372
|
-
dashboard: mod.dashboard,
|
|
8373
|
-
pluginSet: mod.pluginSet,
|
|
8374
|
-
plugins: mod.plugins,
|
|
8375
|
-
pluginRuntimeRegistrations: mod.pluginRuntimeRegistrations ?? []
|
|
8376
|
-
};
|
|
8377
|
-
} catch (error) {
|
|
8378
|
-
if (!isMissingVirtualConfig(error)) {
|
|
8379
|
-
throw error;
|
|
8341
|
+
// src/api/acp/route.ts
|
|
8342
|
+
import { createHash as createHash4 } from "crypto";
|
|
8343
|
+
import * as acp4 from "@agentclientprotocol/sdk";
|
|
8344
|
+
|
|
8345
|
+
// src/api/acp/auth.ts
|
|
8346
|
+
import {
|
|
8347
|
+
createHash as createHash3,
|
|
8348
|
+
randomBytes,
|
|
8349
|
+
randomUUID as randomUUID2,
|
|
8350
|
+
timingSafeEqual as timingSafeEqual3
|
|
8351
|
+
} from "crypto";
|
|
8352
|
+
import * as acp3 from "@agentclientprotocol/sdk";
|
|
8353
|
+
import { userSchema as userSchema2 } from "@sentry/junior-plugin-api";
|
|
8354
|
+
import { z as z7 } from "zod";
|
|
8355
|
+
|
|
8356
|
+
// src/api/acp/state.ts
|
|
8357
|
+
import { setTimeout as wait } from "timers/promises";
|
|
8358
|
+
var MUTATION_LOCK_TTL_MS = 1e4;
|
|
8359
|
+
async function withLock(state, key, run, options = {}) {
|
|
8360
|
+
const ttlMs = options.ttlMs ?? MUTATION_LOCK_TTL_MS;
|
|
8361
|
+
const startedAtMs = Date.now();
|
|
8362
|
+
let lock;
|
|
8363
|
+
while (true) {
|
|
8364
|
+
lock = await state.acquireLock(key, ttlMs);
|
|
8365
|
+
if (lock) break;
|
|
8366
|
+
if (Date.now() - startedAtMs >= (options.waitMs ?? 0)) {
|
|
8367
|
+
return { acquired: false };
|
|
8380
8368
|
}
|
|
8381
|
-
|
|
8382
|
-
}
|
|
8383
|
-
}
|
|
8384
|
-
function isMissingVirtualConfig(error) {
|
|
8385
|
-
if (!(error instanceof Error)) {
|
|
8386
|
-
return false;
|
|
8369
|
+
await wait(options.retryMs ?? 25, void 0, { ref: false });
|
|
8387
8370
|
}
|
|
8388
|
-
const
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8371
|
+
const heartbeat = options.keepAlive ? setInterval(
|
|
8372
|
+
() => void state.extendLock(lock, ttlMs).catch(() => void 0),
|
|
8373
|
+
Math.floor(ttlMs / 3)
|
|
8374
|
+
) : void 0;
|
|
8375
|
+
heartbeat?.unref();
|
|
8376
|
+
try {
|
|
8377
|
+
return { acquired: true, value: await run(lock) };
|
|
8378
|
+
} finally {
|
|
8379
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
8380
|
+
await state.releaseLock(lock);
|
|
8394
8381
|
}
|
|
8395
|
-
return Boolean(
|
|
8396
|
-
config.inlineManifests?.length || config.packages?.length || Object.keys(config.manifests ?? {}).length
|
|
8397
|
-
);
|
|
8398
8382
|
}
|
|
8399
|
-
function
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8383
|
+
async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
|
|
8384
|
+
if (!await state.extendLock(lock, ttlMs)) {
|
|
8385
|
+
throw new Error(
|
|
8386
|
+
`ACP lock ownership was lost before write for ${lock.threadId}`
|
|
8387
|
+
);
|
|
8404
8388
|
}
|
|
8405
8389
|
}
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
}
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8390
|
+
|
|
8391
|
+
// src/api/acp/transport.ts
|
|
8392
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
8393
|
+
import * as acp2 from "@agentclientprotocol/sdk";
|
|
8394
|
+
import { userSchema } from "@sentry/junior-plugin-api";
|
|
8395
|
+
import { z as z6 } from "zod";
|
|
8396
|
+
|
|
8397
|
+
// src/api/acp/schema.ts
|
|
8398
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
8399
|
+
import { z as z5 } from "zod";
|
|
8400
|
+
var ACP_SESSION_ID_PATTERN = /^local:acp:[a-f0-9]{32}$/;
|
|
8401
|
+
var metaSchema = z5.record(z5.string(), z5.unknown()).nullable();
|
|
8402
|
+
var jsonRpcIdSchema = z5.union([z5.string(), z5.number().finite(), z5.null()]);
|
|
8403
|
+
var SESSION_HEADER_METHODS = /* @__PURE__ */ new Set([
|
|
8404
|
+
acp.methods.agent.session.cancel,
|
|
8405
|
+
acp.methods.agent.session.close,
|
|
8406
|
+
acp.methods.agent.session.load,
|
|
8407
|
+
acp.methods.agent.session.prompt,
|
|
8408
|
+
acp.methods.agent.session.resume,
|
|
8409
|
+
acp.methods.agent.session.setConfigOption,
|
|
8410
|
+
acp.methods.agent.session.setMode
|
|
8411
|
+
]);
|
|
8412
|
+
var acpConnectionIdSchema = z5.string().uuid();
|
|
8413
|
+
var acpSessionIdSchema = z5.string().regex(ACP_SESSION_ID_PATTERN);
|
|
8414
|
+
var acpCallSchema = z5.object({
|
|
8415
|
+
id: jsonRpcIdSchema.optional(),
|
|
8416
|
+
jsonrpc: z5.literal("2.0"),
|
|
8417
|
+
method: z5.string().min(1),
|
|
8418
|
+
params: z5.unknown().optional()
|
|
8419
|
+
}).strict();
|
|
8420
|
+
var jsonRpcErrorSchema = z5.object({
|
|
8421
|
+
code: z5.number().int(),
|
|
8422
|
+
data: z5.unknown().optional(),
|
|
8423
|
+
message: z5.string()
|
|
8424
|
+
}).strict();
|
|
8425
|
+
var acpResponseSchema = z5.union([
|
|
8426
|
+
z5.object({
|
|
8427
|
+
id: jsonRpcIdSchema,
|
|
8428
|
+
jsonrpc: z5.literal("2.0"),
|
|
8429
|
+
result: z5.unknown()
|
|
8430
|
+
}).strict(),
|
|
8431
|
+
z5.object({
|
|
8432
|
+
error: jsonRpcErrorSchema,
|
|
8433
|
+
id: jsonRpcIdSchema,
|
|
8434
|
+
jsonrpc: z5.literal("2.0")
|
|
8435
|
+
}).strict()
|
|
8436
|
+
]);
|
|
8437
|
+
var acpInboundMessageSchema = z5.union([
|
|
8438
|
+
acpCallSchema,
|
|
8439
|
+
acpResponseSchema
|
|
8440
|
+
]);
|
|
8441
|
+
var implementationSchema = z5.object({
|
|
8442
|
+
_meta: metaSchema.optional(),
|
|
8443
|
+
name: z5.string().min(1),
|
|
8444
|
+
title: z5.string().nullable().optional(),
|
|
8445
|
+
version: z5.string()
|
|
8446
|
+
}).strict();
|
|
8447
|
+
var initializeParamsSchema = z5.object({
|
|
8448
|
+
_meta: metaSchema.optional(),
|
|
8449
|
+
clientCapabilities: z5.record(z5.string(), z5.unknown()).optional(),
|
|
8450
|
+
clientInfo: implementationSchema.nullable().optional(),
|
|
8451
|
+
protocolVersion: z5.number().int().min(0).max(65535)
|
|
8452
|
+
}).strict();
|
|
8453
|
+
var authenticateParamsSchema = z5.object({
|
|
8454
|
+
_meta: metaSchema.optional(),
|
|
8455
|
+
methodId: z5.string().min(1)
|
|
8456
|
+
}).strict();
|
|
8457
|
+
var newSessionParamsSchema = z5.object({
|
|
8458
|
+
_meta: metaSchema.optional(),
|
|
8459
|
+
additionalDirectories: z5.array(z5.string()).optional(),
|
|
8460
|
+
cwd: z5.string().min(1),
|
|
8461
|
+
mcpServers: z5.array(z5.unknown())
|
|
8462
|
+
}).strict();
|
|
8463
|
+
var loadSessionParamsSchema = newSessionParamsSchema.extend({
|
|
8464
|
+
sessionId: acpSessionIdSchema
|
|
8465
|
+
});
|
|
8466
|
+
var annotationsSchema = z5.object({
|
|
8467
|
+
_meta: metaSchema.optional(),
|
|
8468
|
+
audience: z5.array(z5.enum(["assistant", "user"])).nullable().optional(),
|
|
8469
|
+
lastModified: z5.string().nullable().optional(),
|
|
8470
|
+
priority: z5.number().finite().nullable().optional()
|
|
8471
|
+
}).strict();
|
|
8472
|
+
var textContentBlockSchema = z5.object({
|
|
8473
|
+
_meta: metaSchema.optional(),
|
|
8474
|
+
annotations: annotationsSchema.nullable().optional(),
|
|
8475
|
+
text: z5.string(),
|
|
8476
|
+
type: z5.literal("text")
|
|
8477
|
+
}).strict();
|
|
8478
|
+
var resourceLinkContentBlockSchema = z5.object({
|
|
8479
|
+
_meta: metaSchema.optional(),
|
|
8480
|
+
annotations: annotationsSchema.nullable().optional(),
|
|
8481
|
+
description: z5.string().nullable().optional(),
|
|
8482
|
+
mimeType: z5.string().nullable().optional(),
|
|
8483
|
+
name: z5.string(),
|
|
8484
|
+
size: z5.number().finite().nullable().optional(),
|
|
8485
|
+
title: z5.string().nullable().optional(),
|
|
8486
|
+
type: z5.literal("resource_link"),
|
|
8487
|
+
uri: z5.string()
|
|
8488
|
+
}).strict();
|
|
8489
|
+
var promptParamsSchema = z5.object({
|
|
8490
|
+
_meta: metaSchema.optional(),
|
|
8491
|
+
prompt: z5.array(
|
|
8492
|
+
z5.discriminatedUnion("type", [
|
|
8493
|
+
textContentBlockSchema,
|
|
8494
|
+
resourceLinkContentBlockSchema
|
|
8495
|
+
])
|
|
8496
|
+
),
|
|
8497
|
+
sessionId: acpSessionIdSchema
|
|
8498
|
+
}).strict();
|
|
8499
|
+
var cancelParamsSchema = z5.object({
|
|
8500
|
+
_meta: metaSchema.optional(),
|
|
8501
|
+
sessionId: acpSessionIdSchema
|
|
8502
|
+
}).strict();
|
|
8503
|
+
|
|
8504
|
+
// src/api/acp/sleep.ts
|
|
8505
|
+
function sleep(ms, signal) {
|
|
8506
|
+
return new Promise((resolve, reject) => {
|
|
8507
|
+
if (signal.aborted) {
|
|
8508
|
+
reject(signal.reason);
|
|
8509
|
+
return;
|
|
8510
|
+
}
|
|
8511
|
+
const timeoutId = setTimeout(() => {
|
|
8512
|
+
signal.removeEventListener("abort", onAbort);
|
|
8513
|
+
resolve();
|
|
8514
|
+
}, ms);
|
|
8515
|
+
const onAbort = () => {
|
|
8516
|
+
clearTimeout(timeoutId);
|
|
8517
|
+
reject(signal.reason);
|
|
8518
|
+
};
|
|
8519
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
8520
|
+
});
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8523
|
+
// src/api/acp/transport.ts
|
|
8524
|
+
var ACP_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8525
|
+
var ACP_SSE_LEASE_TTL_MS = 6e4;
|
|
8526
|
+
var ACP_SSE_LEASE_RENEW_INTERVAL_MS = 2e4;
|
|
8527
|
+
var EVENT_POLL_INTERVAL_MS = 100;
|
|
8528
|
+
var LOCK_WAIT_MS = 5e3;
|
|
8529
|
+
var MAX_STREAM_ITEMS = 1024;
|
|
8530
|
+
var MUTATION_LOCK_OPTIONS = {
|
|
8531
|
+
keepAlive: true,
|
|
8532
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
8533
|
+
waitMs: LOCK_WAIT_MS
|
|
8534
|
+
};
|
|
8535
|
+
var SSE_KEEP_ALIVE_MS = 15e3;
|
|
8536
|
+
var SSE_MAINTENANCE_INTERVAL_MS = 1e3;
|
|
8537
|
+
var jsonRpcIdSchema2 = z6.union([z6.string(), z6.number().finite(), z6.null()]);
|
|
8538
|
+
var acpConnectionSchema = z6.object({
|
|
8539
|
+
credentialHash: z6.string().length(64),
|
|
8540
|
+
nonce: z6.string().min(1),
|
|
8541
|
+
user: userSchema.optional()
|
|
8542
|
+
}).strict();
|
|
8543
|
+
var acpStreamRouteSchema = z6.object({
|
|
8544
|
+
connectionId: z6.string().min(1),
|
|
8545
|
+
sessionId: z6.string().min(1).optional()
|
|
8546
|
+
}).strict();
|
|
8547
|
+
var acpPromptStreamOutputSchema = z6.object({
|
|
8548
|
+
afterSeq: z6.number().int().nonnegative(),
|
|
8549
|
+
kind: z6.literal("prompt"),
|
|
8550
|
+
messageId: z6.string().min(1),
|
|
8551
|
+
requestId: jsonRpcIdSchema2,
|
|
8552
|
+
turnId: z6.string().min(1)
|
|
8553
|
+
}).strict();
|
|
8554
|
+
var acpStreamOutputSchema = z6.discriminatedUnion("kind", [
|
|
8555
|
+
z6.object({
|
|
8556
|
+
kind: z6.literal("message"),
|
|
8557
|
+
message: acpInboundMessageSchema
|
|
8558
|
+
}).strict(),
|
|
8559
|
+
z6.object({ kind: z6.literal("replay") }).strict(),
|
|
8560
|
+
acpPromptStreamOutputSchema
|
|
8561
|
+
]);
|
|
8562
|
+
var storedAcpStreamItemSchema = z6.object({
|
|
8563
|
+
id: z6.string().min(1),
|
|
8564
|
+
output: acpStreamOutputSchema
|
|
8565
|
+
}).strict();
|
|
8566
|
+
var acpRequestReceiptSchema = z6.object({
|
|
8567
|
+
deliveryKey: z6.string().min(1).optional(),
|
|
8568
|
+
outputs: z6.array(acpStreamOutputSchema),
|
|
8569
|
+
sessionId: z6.string().min(1).optional()
|
|
8570
|
+
}).strict();
|
|
8571
|
+
var acpStreamCursorSchema = z6.object({ itemId: z6.string().min(1) }).strict();
|
|
8572
|
+
function stableHex(value) {
|
|
8573
|
+
return createHash2("sha256").update(value).digest("hex").slice(0, 32);
|
|
8574
|
+
}
|
|
8575
|
+
function connectionKey(connectionId) {
|
|
8576
|
+
return `junior:acp:v1:connection:${connectionId}`;
|
|
8577
|
+
}
|
|
8578
|
+
function receiptKey(connectionId, requestKey2) {
|
|
8579
|
+
return `${connectionKey(connectionId)}:request:${requestKey2}`;
|
|
8580
|
+
}
|
|
8581
|
+
function receiptLockKey(connectionId, requestKey2) {
|
|
8582
|
+
return `${receiptKey(connectionId, requestKey2)}:lock`;
|
|
8583
|
+
}
|
|
8584
|
+
function connectionLockKey(connectionId) {
|
|
8585
|
+
return `${connectionKey(connectionId)}:lock`;
|
|
8586
|
+
}
|
|
8587
|
+
function streamKey(route) {
|
|
8588
|
+
const suffix = route.sessionId ? `session:${stableHex(route.sessionId)}` : "connection";
|
|
8589
|
+
return `${connectionKey(route.connectionId)}:stream:${suffix}`;
|
|
8590
|
+
}
|
|
8591
|
+
function streamListKey(route) {
|
|
8592
|
+
return `${streamKey(route)}:items`;
|
|
8593
|
+
}
|
|
8594
|
+
function streamCursorKey(route) {
|
|
8595
|
+
return `${streamKey(route)}:cursor`;
|
|
8596
|
+
}
|
|
8597
|
+
function streamLockKey(route) {
|
|
8598
|
+
return `${streamKey(route)}:subscriber`;
|
|
8599
|
+
}
|
|
8600
|
+
function streamAppendLockKey(route) {
|
|
8601
|
+
return `${streamKey(route)}:append`;
|
|
8602
|
+
}
|
|
8603
|
+
function requestStreamItemId(requestKey2, index) {
|
|
8604
|
+
return `acp-item:${stableHex(`${requestKey2}:${index}`)}`;
|
|
8605
|
+
}
|
|
8606
|
+
function parseStreamItem(value) {
|
|
8607
|
+
return storedAcpStreamItemSchema.parse(value);
|
|
8608
|
+
}
|
|
8609
|
+
function parseReceipt(value) {
|
|
8610
|
+
return acpRequestReceiptSchema.parse(value);
|
|
8611
|
+
}
|
|
8612
|
+
async function readStreamItems(state, route) {
|
|
8613
|
+
return (await state.getList(streamListKey(route))).map(parseStreamItem);
|
|
8614
|
+
}
|
|
8615
|
+
async function readStreamCursor(state, route) {
|
|
8616
|
+
const value = await state.get(streamCursorKey(route));
|
|
8617
|
+
return value === null || value === void 0 ? void 0 : acpStreamCursorSchema.parse(value).itemId;
|
|
8618
|
+
}
|
|
8619
|
+
async function connectionIsLive(state, connectionId) {
|
|
8620
|
+
return Boolean(await readAcpConnection(state, connectionId));
|
|
8621
|
+
}
|
|
8622
|
+
async function createAcpConnection(state, credentialHash2) {
|
|
8623
|
+
const connectionId = randomUUID();
|
|
8624
|
+
const connection = acpConnectionSchema.parse({
|
|
8625
|
+
credentialHash: credentialHash2,
|
|
8626
|
+
nonce: randomUUID()
|
|
8627
|
+
});
|
|
8628
|
+
await state.set(connectionKey(connectionId), connection, ACP_STATE_TTL_MS);
|
|
8629
|
+
return { connection, connectionId };
|
|
8630
|
+
}
|
|
8631
|
+
async function readAcpConnection(state, connectionId) {
|
|
8632
|
+
const value = await state.get(connectionKey(connectionId));
|
|
8633
|
+
return value === null || value === void 0 ? void 0 : acpConnectionSchema.parse(value);
|
|
8634
|
+
}
|
|
8635
|
+
async function deleteAcpConnection(state, connectionId) {
|
|
8636
|
+
const result = await withLock(
|
|
8637
|
+
state,
|
|
8638
|
+
connectionLockKey(connectionId),
|
|
8639
|
+
async (lock) => {
|
|
8640
|
+
await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
|
|
8641
|
+
await state.delete(connectionKey(connectionId));
|
|
8642
|
+
await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
|
|
8643
|
+
await state.delete(streamCursorKey({ connectionId }));
|
|
8644
|
+
},
|
|
8645
|
+
MUTATION_LOCK_OPTIONS
|
|
8646
|
+
);
|
|
8647
|
+
if (!result.acquired) {
|
|
8648
|
+
throw new Error("Timed out acquiring ACP connection control");
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
async function bindAcpConnectionUser(args) {
|
|
8652
|
+
const result = await withLock(
|
|
8653
|
+
args.state,
|
|
8654
|
+
connectionLockKey(args.connectionId),
|
|
8655
|
+
async (lock) => {
|
|
8656
|
+
const connection = await readAcpConnection(args.state, args.connectionId);
|
|
8657
|
+
if (!connection || connection.credentialHash !== args.credentialHash) {
|
|
8658
|
+
return "expired";
|
|
8659
|
+
}
|
|
8660
|
+
if (connection.user && connection.user.id !== args.user.id) {
|
|
8661
|
+
return "conflict";
|
|
8662
|
+
}
|
|
8663
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8664
|
+
await args.state.set(
|
|
8665
|
+
connectionKey(args.connectionId),
|
|
8666
|
+
acpConnectionSchema.parse({ ...connection, user: args.user }),
|
|
8667
|
+
ACP_STATE_TTL_MS
|
|
8668
|
+
);
|
|
8669
|
+
return "completed";
|
|
8670
|
+
},
|
|
8671
|
+
MUTATION_LOCK_OPTIONS
|
|
8672
|
+
);
|
|
8673
|
+
if (!result.acquired) {
|
|
8674
|
+
throw new Error("Timed out acquiring ACP connection control");
|
|
8675
|
+
}
|
|
8676
|
+
return result.value;
|
|
8677
|
+
}
|
|
8678
|
+
async function prepareStreamAppend(args) {
|
|
8679
|
+
const existing = await readStreamItems(args.state, args.route);
|
|
8680
|
+
const cursor = await readStreamCursor(args.state, args.route);
|
|
8681
|
+
const cursorIndex = cursor ? existing.findIndex((item) => item.id === cursor) : -1;
|
|
8682
|
+
const pending = existing.slice(cursorIndex >= 0 ? cursorIndex + 1 : 0);
|
|
8683
|
+
const presentIds = /* @__PURE__ */ new Set();
|
|
8684
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
8685
|
+
for (const id of args.ids) {
|
|
8686
|
+
const matches = (item) => item.id === id || item.id.startsWith(`${id}:delivery:`);
|
|
8687
|
+
if (existing.some(matches)) presentIds.add(id);
|
|
8688
|
+
if (pending.some(matches)) pendingIds.add(id);
|
|
8689
|
+
}
|
|
8690
|
+
const retainedIds = pendingIds.size === 0 ? /* @__PURE__ */ new Set() : presentIds;
|
|
8691
|
+
const missingCount = args.ids.length - retainedIds.size;
|
|
8692
|
+
if (missingCount > MAX_STREAM_ITEMS) throw new AcpStreamFullError();
|
|
8693
|
+
if (missingCount === 0 || existing.length + missingCount <= MAX_STREAM_ITEMS) {
|
|
8694
|
+
return {
|
|
8695
|
+
existingIds: new Set(existing.map((item) => item.id)),
|
|
8696
|
+
retainedIds
|
|
8697
|
+
};
|
|
8698
|
+
}
|
|
8699
|
+
if (existing.length === 0 || cursorIndex !== existing.length - 1) {
|
|
8700
|
+
throw new AcpStreamFullError();
|
|
8701
|
+
}
|
|
8702
|
+
await fenceLock(args.state, args.lock, MUTATION_LOCK_TTL_MS);
|
|
8703
|
+
await args.state.delete(streamListKey(args.route));
|
|
8704
|
+
await args.state.delete(streamCursorKey(args.route));
|
|
8705
|
+
return { existingIds: /* @__PURE__ */ new Set(), retainedIds: /* @__PURE__ */ new Set() };
|
|
8706
|
+
}
|
|
8707
|
+
async function appendStreamOutputWithLock(args) {
|
|
8708
|
+
const route = acpStreamRouteSchema.parse(args.route);
|
|
8709
|
+
const output = acpStreamOutputSchema.parse(args.output);
|
|
8710
|
+
let id = args.id;
|
|
8711
|
+
for (let delivery = 1; args.existingIds.has(id); delivery += 1) {
|
|
8712
|
+
id = `${args.id}:delivery:${delivery}`;
|
|
8713
|
+
}
|
|
8714
|
+
await fenceLock(args.state, args.lock, MUTATION_LOCK_TTL_MS);
|
|
8715
|
+
if (output.kind !== "message" && !route.sessionId) {
|
|
8716
|
+
throw new Error(`ACP ${output.kind} output requires a session stream`);
|
|
8717
|
+
}
|
|
8718
|
+
await args.state.appendToList(
|
|
8719
|
+
streamListKey(route),
|
|
8720
|
+
{ id, output },
|
|
8721
|
+
{ ttlMs: ACP_STATE_TTL_MS }
|
|
8722
|
+
);
|
|
8723
|
+
args.existingIds.add(id);
|
|
8724
|
+
}
|
|
8725
|
+
var AcpStreamFullError = class extends Error {
|
|
8726
|
+
constructor() {
|
|
8727
|
+
super("ACP stream has too much undelivered output");
|
|
8728
|
+
this.name = "AcpStreamFullError";
|
|
8729
|
+
}
|
|
8730
|
+
};
|
|
8731
|
+
async function streamSessionReplay(args) {
|
|
8732
|
+
const messages = await args.conversations.readMessages(args.sessionId);
|
|
8733
|
+
for (const message of messages) {
|
|
8734
|
+
if (args.signal.aborted) return false;
|
|
8735
|
+
const emitted = await args.emit({
|
|
8736
|
+
jsonrpc: "2.0",
|
|
8737
|
+
method: acp2.methods.client.session.update,
|
|
8738
|
+
params: {
|
|
8739
|
+
sessionId: args.sessionId,
|
|
8740
|
+
update: {
|
|
8741
|
+
sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
|
|
8742
|
+
content: { type: "text", text: message.text },
|
|
8743
|
+
messageId: message.id
|
|
8744
|
+
}
|
|
8745
|
+
}
|
|
8746
|
+
});
|
|
8747
|
+
if (!emitted) return false;
|
|
8748
|
+
}
|
|
8749
|
+
return true;
|
|
8750
|
+
}
|
|
8751
|
+
async function queueReceipt(args) {
|
|
8752
|
+
const receipt = parseReceipt(args.receipt);
|
|
8753
|
+
const deliveryKey = receipt.deliveryKey ?? args.requestKey;
|
|
8754
|
+
const route = { connectionId: args.connectionId };
|
|
8755
|
+
if (receipt.sessionId) route.sessionId = receipt.sessionId;
|
|
8756
|
+
const ids = receipt.outputs.map(
|
|
8757
|
+
(_, index) => requestStreamItemId(deliveryKey, index)
|
|
8758
|
+
);
|
|
8759
|
+
const appendAll = async (lock) => {
|
|
8760
|
+
const prepared = await prepareStreamAppend({
|
|
8761
|
+
ids,
|
|
8762
|
+
lock,
|
|
8763
|
+
route,
|
|
8764
|
+
state: args.state
|
|
8765
|
+
});
|
|
8766
|
+
for (const [index, output] of receipt.outputs.entries()) {
|
|
8767
|
+
const id = ids[index];
|
|
8768
|
+
if (!id || prepared.retainedIds.has(id)) continue;
|
|
8769
|
+
await appendStreamOutputWithLock({
|
|
8770
|
+
existingIds: prepared.existingIds,
|
|
8771
|
+
id,
|
|
8772
|
+
lock,
|
|
8773
|
+
output,
|
|
8774
|
+
route,
|
|
8775
|
+
state: args.state
|
|
8776
|
+
});
|
|
8777
|
+
}
|
|
8778
|
+
};
|
|
8779
|
+
if (args.stream) {
|
|
8780
|
+
if (streamAppendLockKey(args.stream.route) !== streamAppendLockKey(route)) {
|
|
8781
|
+
throw new Error("Reserved ACP output changed streams");
|
|
8782
|
+
}
|
|
8783
|
+
await appendAll(args.stream.lock);
|
|
8784
|
+
return;
|
|
8785
|
+
}
|
|
8786
|
+
const result = await withLock(
|
|
8787
|
+
args.state,
|
|
8788
|
+
streamAppendLockKey(route),
|
|
8789
|
+
appendAll,
|
|
8790
|
+
MUTATION_LOCK_OPTIONS
|
|
8791
|
+
);
|
|
8792
|
+
if (!result.acquired) {
|
|
8793
|
+
throw new Error("Timed out acquiring ACP stream append control");
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
async function completeAcpRequest(args) {
|
|
8797
|
+
try {
|
|
8798
|
+
const result = await withLock(
|
|
8799
|
+
args.state,
|
|
8800
|
+
receiptLockKey(args.connectionId, args.requestKey),
|
|
8801
|
+
async (lock) => {
|
|
8802
|
+
if (!await readAcpConnection(args.state, args.connectionId)) {
|
|
8803
|
+
return "expired";
|
|
8804
|
+
}
|
|
8805
|
+
const receipt = parseReceipt(args.receipt);
|
|
8806
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8807
|
+
await args.state.set(
|
|
8808
|
+
receiptKey(args.connectionId, args.requestKey),
|
|
8809
|
+
receipt,
|
|
8810
|
+
ACP_STATE_TTL_MS
|
|
8811
|
+
);
|
|
8812
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8813
|
+
await queueReceipt({
|
|
8814
|
+
connectionId: args.connectionId,
|
|
8815
|
+
receipt,
|
|
8816
|
+
requestKey: args.requestKey,
|
|
8817
|
+
state: args.state
|
|
8818
|
+
});
|
|
8819
|
+
return "completed";
|
|
8820
|
+
},
|
|
8821
|
+
MUTATION_LOCK_OPTIONS
|
|
8822
|
+
);
|
|
8823
|
+
if (!result.acquired) {
|
|
8824
|
+
throw new Error("Timed out acquiring ACP request completion control");
|
|
8825
|
+
}
|
|
8826
|
+
return result.value;
|
|
8827
|
+
} catch (error) {
|
|
8828
|
+
if (error instanceof AcpStreamFullError) return "full";
|
|
8829
|
+
throw error;
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
async function acceptAcpRequest(args) {
|
|
8833
|
+
try {
|
|
8834
|
+
const result = await withLock(
|
|
8835
|
+
args.state,
|
|
8836
|
+
receiptLockKey(args.connectionId, args.requestKey),
|
|
8837
|
+
async (lock) => {
|
|
8838
|
+
if (!await readAcpConnection(args.state, args.connectionId)) {
|
|
8839
|
+
return "expired";
|
|
8840
|
+
}
|
|
8841
|
+
const stored = await args.state.get(
|
|
8842
|
+
receiptKey(args.connectionId, args.requestKey)
|
|
8843
|
+
);
|
|
8844
|
+
const receipt = stored === null || stored === void 0 ? void 0 : parseReceipt(stored);
|
|
8845
|
+
if (!receipt) {
|
|
8846
|
+
const createAndQueue = async (stream2) => {
|
|
8847
|
+
const created = parseReceipt(await args.createReceipt());
|
|
8848
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8849
|
+
await args.state.set(
|
|
8850
|
+
receiptKey(args.connectionId, args.requestKey),
|
|
8851
|
+
created,
|
|
8852
|
+
ACP_STATE_TTL_MS
|
|
8853
|
+
);
|
|
8854
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8855
|
+
const queue = {
|
|
8856
|
+
connectionId: args.connectionId,
|
|
8857
|
+
receipt: created,
|
|
8858
|
+
requestKey: args.requestKey,
|
|
8859
|
+
state: args.state
|
|
8860
|
+
};
|
|
8861
|
+
if (stream2) queue.stream = stream2;
|
|
8862
|
+
await queueReceipt(queue);
|
|
8863
|
+
};
|
|
8864
|
+
if (!args.reserveRoute) {
|
|
8865
|
+
if (!await readAcpConnection(args.state, args.connectionId)) {
|
|
8866
|
+
return "expired";
|
|
8867
|
+
}
|
|
8868
|
+
await createAndQueue();
|
|
8869
|
+
return "accepted";
|
|
8870
|
+
}
|
|
8871
|
+
const route = acpStreamRouteSchema.parse(args.reserveRoute);
|
|
8872
|
+
const stream = await withLock(
|
|
8873
|
+
args.state,
|
|
8874
|
+
streamAppendLockKey(route),
|
|
8875
|
+
async (streamLock) => {
|
|
8876
|
+
await prepareStreamAppend({
|
|
8877
|
+
ids: [requestStreamItemId(args.requestKey, 0)],
|
|
8878
|
+
lock: streamLock,
|
|
8879
|
+
route,
|
|
8880
|
+
state: args.state
|
|
8881
|
+
});
|
|
8882
|
+
if (!await readAcpConnection(args.state, args.connectionId)) {
|
|
8883
|
+
return "expired";
|
|
8884
|
+
}
|
|
8885
|
+
await createAndQueue({ lock: streamLock, route });
|
|
8886
|
+
return "accepted";
|
|
8887
|
+
},
|
|
8888
|
+
MUTATION_LOCK_OPTIONS
|
|
8889
|
+
);
|
|
8890
|
+
if (!stream.acquired) {
|
|
8891
|
+
throw new Error("Timed out acquiring ACP stream append control");
|
|
8892
|
+
}
|
|
8893
|
+
return stream.value;
|
|
8894
|
+
}
|
|
8895
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
8896
|
+
await queueReceipt({
|
|
8897
|
+
connectionId: args.connectionId,
|
|
8898
|
+
receipt,
|
|
8899
|
+
requestKey: args.requestKey,
|
|
8900
|
+
state: args.state
|
|
8901
|
+
});
|
|
8902
|
+
return "accepted";
|
|
8903
|
+
},
|
|
8904
|
+
MUTATION_LOCK_OPTIONS
|
|
8905
|
+
);
|
|
8906
|
+
return result.acquired ? result.value : "busy";
|
|
8907
|
+
} catch (error) {
|
|
8908
|
+
if (error instanceof AcpStreamFullError) return "full";
|
|
8909
|
+
throw error;
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
function resultMessage(requestId, result) {
|
|
8913
|
+
return { jsonrpc: "2.0", id: requestId, result };
|
|
8914
|
+
}
|
|
8915
|
+
function errorMessage(requestId, error) {
|
|
8916
|
+
return {
|
|
8917
|
+
jsonrpc: "2.0",
|
|
8918
|
+
id: requestId,
|
|
8919
|
+
error: error.toErrorResponse()
|
|
8920
|
+
};
|
|
8921
|
+
}
|
|
8922
|
+
async function streamPrompt(args) {
|
|
8923
|
+
let cursor = args.output.afterSeq;
|
|
8924
|
+
const sentMessageIds = /* @__PURE__ */ new Set();
|
|
8925
|
+
while (!args.signal.aborted) {
|
|
8926
|
+
if (!await connectionIsLive(args.state, args.connectionId)) return false;
|
|
8927
|
+
const page = await args.conversations.readTurn({
|
|
8928
|
+
afterCursor: cursor,
|
|
8929
|
+
conversationId: args.sessionId,
|
|
8930
|
+
messageId: args.output.messageId,
|
|
8931
|
+
turnId: args.output.turnId
|
|
8932
|
+
});
|
|
8933
|
+
cursor = page.cursor;
|
|
8934
|
+
if (page.messages.length === 0 && !page.terminal) {
|
|
8935
|
+
await sleep(EVENT_POLL_INTERVAL_MS, args.signal);
|
|
8936
|
+
continue;
|
|
8937
|
+
}
|
|
8938
|
+
for (const message of page.messages) {
|
|
8939
|
+
if (sentMessageIds.has(message.id)) continue;
|
|
8940
|
+
sentMessageIds.add(message.id);
|
|
8941
|
+
if (!await args.emit({
|
|
8942
|
+
jsonrpc: "2.0",
|
|
8943
|
+
method: acp2.methods.client.session.update,
|
|
8944
|
+
params: {
|
|
8945
|
+
sessionId: args.sessionId,
|
|
8946
|
+
update: {
|
|
8947
|
+
sessionUpdate: "agent_message_chunk",
|
|
8948
|
+
content: { type: "text", text: message.text },
|
|
8949
|
+
messageId: message.id
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8952
|
+
})) {
|
|
8953
|
+
return false;
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
if (page.terminal?.status === "completed") {
|
|
8957
|
+
return await args.emit(
|
|
8958
|
+
resultMessage(args.output.requestId, {
|
|
8959
|
+
stopReason: page.terminal.outcome === "cancelled" ? "cancelled" : "end_turn"
|
|
8960
|
+
})
|
|
8961
|
+
);
|
|
8962
|
+
}
|
|
8963
|
+
if (page.terminal?.status === "failed") {
|
|
8964
|
+
return await args.emit(
|
|
8965
|
+
errorMessage(
|
|
8966
|
+
args.output.requestId,
|
|
8967
|
+
acp2.RequestError.internalError(
|
|
8968
|
+
{ failureCode: page.terminal.failureCode },
|
|
8969
|
+
"Junior Turn failed"
|
|
8970
|
+
)
|
|
8971
|
+
)
|
|
8972
|
+
);
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8975
|
+
return false;
|
|
8976
|
+
}
|
|
8977
|
+
function serializeSseMessage(message) {
|
|
8978
|
+
return new TextEncoder().encode(`data: ${JSON.stringify(message)}
|
|
8979
|
+
|
|
8980
|
+
`);
|
|
8981
|
+
}
|
|
8982
|
+
function serializeSseKeepAlive() {
|
|
8983
|
+
return new TextEncoder().encode(":\n\n");
|
|
8984
|
+
}
|
|
8985
|
+
async function openAcpSse(args) {
|
|
8986
|
+
const route = acpStreamRouteSchema.parse(args.route);
|
|
8987
|
+
const lock = await args.state.acquireLock(
|
|
8988
|
+
streamLockKey(route),
|
|
8989
|
+
ACP_SSE_LEASE_TTL_MS
|
|
8990
|
+
);
|
|
8991
|
+
if (!lock) {
|
|
8992
|
+
return new Response("ACP SSE stream already connected", { status: 409 });
|
|
8993
|
+
}
|
|
8994
|
+
return new Response(createSseBody({ ...args, lock, route }), {
|
|
8995
|
+
status: 200,
|
|
8996
|
+
headers: {
|
|
8997
|
+
"Cache-Control": "no-cache",
|
|
8998
|
+
Connection: "keep-alive",
|
|
8999
|
+
"Content-Type": "text/event-stream"
|
|
9000
|
+
}
|
|
9001
|
+
});
|
|
9002
|
+
}
|
|
9003
|
+
function createSseBody(args) {
|
|
9004
|
+
const abort = new AbortController();
|
|
9005
|
+
let cleaned = false;
|
|
9006
|
+
let heartbeat;
|
|
9007
|
+
let keepAlive;
|
|
9008
|
+
let leaseExpiresAt = args.lock.expiresAt;
|
|
9009
|
+
let nextMaintenanceAtMs = 0;
|
|
9010
|
+
let retainingLease;
|
|
9011
|
+
let closeBody;
|
|
9012
|
+
const correlation = {
|
|
9013
|
+
connectionId: args.route.connectionId
|
|
9014
|
+
};
|
|
9015
|
+
if (args.route.sessionId) correlation.conversationId = args.route.sessionId;
|
|
9016
|
+
if (args.userId) correlation.userId = args.userId;
|
|
9017
|
+
const cleanup = async () => {
|
|
9018
|
+
if (cleaned) return;
|
|
9019
|
+
cleaned = true;
|
|
9020
|
+
abort.abort();
|
|
9021
|
+
closeBody?.();
|
|
9022
|
+
if (keepAlive) clearInterval(keepAlive);
|
|
9023
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
9024
|
+
args.requestSignal.removeEventListener("abort", onRequestAbort);
|
|
9025
|
+
try {
|
|
9026
|
+
await args.state.releaseLock(args.lock);
|
|
9027
|
+
} catch (error) {
|
|
9028
|
+
args.onError?.(error, "acp.sse.lock_cleanup.exception", correlation);
|
|
9029
|
+
}
|
|
9030
|
+
};
|
|
9031
|
+
const onRequestAbort = () => void cleanup();
|
|
9032
|
+
const retainLease = async () => {
|
|
9033
|
+
if (retainingLease) return await retainingLease;
|
|
9034
|
+
retainingLease = (async () => {
|
|
9035
|
+
let extended;
|
|
9036
|
+
try {
|
|
9037
|
+
extended = await args.state.extendLock(args.lock, ACP_SSE_LEASE_TTL_MS);
|
|
9038
|
+
} catch (error) {
|
|
9039
|
+
if (Date.now() < leaseExpiresAt) return;
|
|
9040
|
+
throw new Error("ACP SSE stream lease could not be renewed", {
|
|
9041
|
+
cause: error
|
|
9042
|
+
});
|
|
9043
|
+
}
|
|
9044
|
+
if (!extended) {
|
|
9045
|
+
throw new Error("ACP SSE stream lease expired");
|
|
9046
|
+
}
|
|
9047
|
+
leaseExpiresAt = Date.now() + ACP_SSE_LEASE_TTL_MS;
|
|
9048
|
+
})();
|
|
9049
|
+
try {
|
|
9050
|
+
await retainingLease;
|
|
9051
|
+
} finally {
|
|
9052
|
+
retainingLease = void 0;
|
|
9053
|
+
}
|
|
9054
|
+
};
|
|
9055
|
+
return new ReadableStream({
|
|
9056
|
+
start(controller) {
|
|
9057
|
+
let failureCaptured = false;
|
|
9058
|
+
closeBody = () => {
|
|
9059
|
+
try {
|
|
9060
|
+
controller.close();
|
|
9061
|
+
} catch {
|
|
9062
|
+
}
|
|
9063
|
+
};
|
|
9064
|
+
const fail = (error) => {
|
|
9065
|
+
if (cleaned) return;
|
|
9066
|
+
const failure = error instanceof Error ? error : new Error("ACP SSE stream failed", { cause: error });
|
|
9067
|
+
if (!failureCaptured) {
|
|
9068
|
+
failureCaptured = true;
|
|
9069
|
+
args.onError?.(failure, "acp.sse.exception", correlation);
|
|
9070
|
+
}
|
|
9071
|
+
abort.abort(failure);
|
|
9072
|
+
try {
|
|
9073
|
+
controller.error(failure);
|
|
9074
|
+
} catch {
|
|
9075
|
+
}
|
|
9076
|
+
void cleanup();
|
|
9077
|
+
};
|
|
9078
|
+
args.requestSignal.addEventListener("abort", onRequestAbort, {
|
|
9079
|
+
once: true
|
|
9080
|
+
});
|
|
9081
|
+
if (args.requestSignal.aborted) {
|
|
9082
|
+
void cleanup();
|
|
9083
|
+
return;
|
|
9084
|
+
}
|
|
9085
|
+
keepAlive = setInterval(() => {
|
|
9086
|
+
if (cleaned || (controller.desiredSize ?? 0) <= 0) return;
|
|
9087
|
+
try {
|
|
9088
|
+
controller.enqueue(serializeSseKeepAlive());
|
|
9089
|
+
} catch (error) {
|
|
9090
|
+
fail(error);
|
|
9091
|
+
}
|
|
9092
|
+
}, SSE_KEEP_ALIVE_MS);
|
|
9093
|
+
keepAlive.unref?.();
|
|
9094
|
+
heartbeat = setInterval(() => {
|
|
9095
|
+
if (cleaned) return;
|
|
9096
|
+
void retainLease().catch(fail);
|
|
9097
|
+
}, ACP_SSE_LEASE_RENEW_INTERVAL_MS);
|
|
9098
|
+
heartbeat.unref?.();
|
|
9099
|
+
const emit = async (message) => {
|
|
9100
|
+
while (!abort.signal.aborted && (controller.desiredSize ?? 0) <= 0) {
|
|
9101
|
+
await sleep(EVENT_POLL_INTERVAL_MS, abort.signal);
|
|
9102
|
+
}
|
|
9103
|
+
if (abort.signal.aborted || !await connectionIsLive(args.state, args.route.connectionId)) {
|
|
9104
|
+
abort.abort();
|
|
9105
|
+
return false;
|
|
9106
|
+
}
|
|
9107
|
+
await retainLease();
|
|
9108
|
+
controller.enqueue(serializeSseMessage(message));
|
|
9109
|
+
return true;
|
|
9110
|
+
};
|
|
9111
|
+
const pump = async () => {
|
|
9112
|
+
let cursor = await readStreamCursor(args.state, args.route);
|
|
9113
|
+
while (!abort.signal.aborted) {
|
|
9114
|
+
if (!await connectionIsLive(args.state, args.route.connectionId)) {
|
|
9115
|
+
return;
|
|
9116
|
+
}
|
|
9117
|
+
if (args.maintain && Date.now() >= nextMaintenanceAtMs) {
|
|
9118
|
+
nextMaintenanceAtMs = Date.now() + SSE_MAINTENANCE_INTERVAL_MS;
|
|
9119
|
+
await args.maintain();
|
|
9120
|
+
}
|
|
9121
|
+
const items = await readStreamItems(args.state, args.route);
|
|
9122
|
+
const cursorIndex = cursor ? items.findIndex((item) => item.id === cursor) : -1;
|
|
9123
|
+
const pending = items.slice(cursorIndex >= 0 ? cursorIndex + 1 : 0);
|
|
9124
|
+
if (pending.length === 0) {
|
|
9125
|
+
await sleep(EVENT_POLL_INTERVAL_MS, abort.signal);
|
|
9126
|
+
continue;
|
|
9127
|
+
}
|
|
9128
|
+
for (const item of pending) {
|
|
9129
|
+
if (abort.signal.aborted) return;
|
|
9130
|
+
await retainLease();
|
|
9131
|
+
await args.state.set(
|
|
9132
|
+
streamCursorKey(args.route),
|
|
9133
|
+
acpStreamCursorSchema.parse({ itemId: item.id }),
|
|
9134
|
+
ACP_STATE_TTL_MS
|
|
9135
|
+
);
|
|
9136
|
+
cursor = item.id;
|
|
9137
|
+
let delivered;
|
|
9138
|
+
if (item.output.kind === "message") {
|
|
9139
|
+
delivered = await emit(item.output.message);
|
|
9140
|
+
} else {
|
|
9141
|
+
const sessionId = args.route.sessionId;
|
|
9142
|
+
if (!sessionId) {
|
|
9143
|
+
throw new Error(
|
|
9144
|
+
`ACP ${item.output.kind} output requires a session stream`
|
|
9145
|
+
);
|
|
9146
|
+
}
|
|
9147
|
+
delivered = item.output.kind === "replay" ? await streamSessionReplay({
|
|
9148
|
+
conversations: args.conversations,
|
|
9149
|
+
emit,
|
|
9150
|
+
sessionId,
|
|
9151
|
+
signal: abort.signal
|
|
9152
|
+
}) : await streamPrompt({
|
|
9153
|
+
conversations: args.conversations,
|
|
9154
|
+
connectionId: args.route.connectionId,
|
|
9155
|
+
emit,
|
|
9156
|
+
output: item.output,
|
|
9157
|
+
sessionId,
|
|
9158
|
+
signal: abort.signal,
|
|
9159
|
+
state: args.state
|
|
9160
|
+
});
|
|
9161
|
+
}
|
|
9162
|
+
if (!delivered || abort.signal.aborted) return;
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
9165
|
+
};
|
|
9166
|
+
void pump().catch((error) => {
|
|
9167
|
+
if (!cleaned && !args.requestSignal.aborted) fail(error);
|
|
9168
|
+
}).finally(cleanup);
|
|
9169
|
+
},
|
|
9170
|
+
async cancel() {
|
|
9171
|
+
await cleanup();
|
|
9172
|
+
}
|
|
9173
|
+
});
|
|
9174
|
+
}
|
|
9175
|
+
|
|
9176
|
+
// src/api/acp/auth.ts
|
|
9177
|
+
var ACP_AUTHORIZATION_TTL_MS = 10 * 60 * 1e3;
|
|
9178
|
+
var ACP_CONNECTION_COOKIE = "junior_acp_connection";
|
|
9179
|
+
var LOCK_WAIT_MS2 = 5e3;
|
|
9180
|
+
var jsonRpcIdSchema3 = z7.union([z7.string(), z7.number().finite(), z7.null()]);
|
|
9181
|
+
var acpAuthorizationSchema = z7.object({
|
|
9182
|
+
connectionId: z7.string().uuid(),
|
|
9183
|
+
credentialHash: z7.string().length(64),
|
|
9184
|
+
elicitationId: z7.string().uuid(),
|
|
9185
|
+
expiresAtMs: z7.number().int().positive(),
|
|
9186
|
+
requestId: jsonRpcIdSchema3,
|
|
9187
|
+
requestKey: z7.string().min(1),
|
|
9188
|
+
userCodeHash: z7.string().length(64)
|
|
9189
|
+
}).strict();
|
|
9190
|
+
var acpAuthorizationPointerSchema = z7.object({
|
|
9191
|
+
expiresAtMs: z7.number().int().positive(),
|
|
9192
|
+
transactionId: z7.string().uuid()
|
|
9193
|
+
}).strict();
|
|
9194
|
+
var ACP_AUTH_METHOD_ID = "junior";
|
|
9195
|
+
var ACP_AUTH_METHOD = {
|
|
9196
|
+
id: ACP_AUTH_METHOD_ID,
|
|
9197
|
+
name: "Sign in to Junior",
|
|
9198
|
+
description: "Sign in with the Google account allowed by this Junior app."
|
|
9199
|
+
};
|
|
9200
|
+
function authorizationKey(transactionId) {
|
|
9201
|
+
return `junior:acp:v1:authorization:${transactionId}`;
|
|
9202
|
+
}
|
|
9203
|
+
function authorizationLockKey(transactionId) {
|
|
9204
|
+
return `${authorizationKey(transactionId)}:lock`;
|
|
9205
|
+
}
|
|
9206
|
+
function authorizationPointerKey(connectionId) {
|
|
9207
|
+
return `junior:acp:v1:connection:${connectionId}:authorization`;
|
|
9208
|
+
}
|
|
9209
|
+
function authorizationPointerLockKey(connectionId) {
|
|
9210
|
+
return `${authorizationPointerKey(connectionId)}:lock`;
|
|
9211
|
+
}
|
|
9212
|
+
function credentialHash(value) {
|
|
9213
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
9214
|
+
}
|
|
9215
|
+
function createUserCode() {
|
|
9216
|
+
const value = randomBytes(6).toString("hex").toUpperCase();
|
|
9217
|
+
return `${value.slice(0, 4)}-${value.slice(4, 8)}-${value.slice(8, 12)}`;
|
|
9218
|
+
}
|
|
9219
|
+
function userCodeHash(value) {
|
|
9220
|
+
return credentialHash(value.trim().replaceAll("-", "").toUpperCase());
|
|
9221
|
+
}
|
|
9222
|
+
function userCodeMatches(value, expectedHash) {
|
|
9223
|
+
const actual = Buffer.from(userCodeHash(value), "hex");
|
|
9224
|
+
const expected = Buffer.from(expectedHash, "hex");
|
|
9225
|
+
return actual.length === expected.length && timingSafeEqual3(actual, expected);
|
|
9226
|
+
}
|
|
9227
|
+
function cookieValue(request) {
|
|
9228
|
+
const header = request.headers.get("cookie");
|
|
9229
|
+
if (!header) return void 0;
|
|
9230
|
+
for (const part of header.split(";")) {
|
|
9231
|
+
const separator = part.indexOf("=");
|
|
9232
|
+
if (separator <= 0) continue;
|
|
9233
|
+
if (part.slice(0, separator).trim() !== ACP_CONNECTION_COOKIE) continue;
|
|
9234
|
+
const value = part.slice(separator + 1).trim();
|
|
9235
|
+
if (value) return value;
|
|
9236
|
+
}
|
|
9237
|
+
return void 0;
|
|
9238
|
+
}
|
|
9239
|
+
function cookieAttributes(request) {
|
|
9240
|
+
return [
|
|
9241
|
+
"Path=/api/acp",
|
|
9242
|
+
"HttpOnly",
|
|
9243
|
+
"SameSite=Strict",
|
|
9244
|
+
...new URL(request.url).protocol === "https:" ? ["Secure"] : []
|
|
9245
|
+
].join("; ");
|
|
9246
|
+
}
|
|
9247
|
+
function createAcpConnectionCredential(request) {
|
|
9248
|
+
const credential = randomBytes(32).toString("base64url");
|
|
9249
|
+
return {
|
|
9250
|
+
cookie: `${ACP_CONNECTION_COOKIE}=${credential}; ${cookieAttributes(request)}; Max-Age=86400`,
|
|
9251
|
+
credentialHash: credentialHash(credential)
|
|
9252
|
+
};
|
|
9253
|
+
}
|
|
9254
|
+
function hasAcpConnectionCredential(request, expectedHash) {
|
|
9255
|
+
const credential = cookieValue(request);
|
|
9256
|
+
if (!credential) return false;
|
|
9257
|
+
const actual = Buffer.from(credentialHash(credential), "hex");
|
|
9258
|
+
const expected = Buffer.from(expectedHash, "hex");
|
|
9259
|
+
return actual.length === expected.length && timingSafeEqual3(actual, expected);
|
|
9260
|
+
}
|
|
9261
|
+
function authorizationBaseURL(request, configured) {
|
|
9262
|
+
const value = configured?.trim();
|
|
9263
|
+
let origin;
|
|
9264
|
+
if (value) {
|
|
9265
|
+
origin = /^https?:\/\//.test(value) ? value : `https://${value}`;
|
|
9266
|
+
} else {
|
|
9267
|
+
const requestURL = new URL(request.url);
|
|
9268
|
+
const hostname = requestURL.hostname.replace(/^\[|\]$/g, "");
|
|
9269
|
+
if (!["localhost", "127.0.0.1", "::1"].includes(hostname)) {
|
|
9270
|
+
throw acp3.RequestError.internalError(
|
|
9271
|
+
void 0,
|
|
9272
|
+
"Junior ACP sign-in requires a configured public base URL"
|
|
9273
|
+
);
|
|
9274
|
+
}
|
|
9275
|
+
origin = requestURL.origin;
|
|
9276
|
+
}
|
|
9277
|
+
const url = new URL(origin);
|
|
9278
|
+
url.pathname = "/";
|
|
9279
|
+
url.search = "";
|
|
9280
|
+
url.hash = "";
|
|
9281
|
+
return url;
|
|
9282
|
+
}
|
|
9283
|
+
async function readAuthorization(state, transactionId) {
|
|
9284
|
+
const value = await state.get(authorizationKey(transactionId));
|
|
9285
|
+
return value === null || value === void 0 ? void 0 : acpAuthorizationSchema.parse(value);
|
|
9286
|
+
}
|
|
9287
|
+
async function readAuthorizationPointer(state, connectionId) {
|
|
9288
|
+
const value = await state.get(authorizationPointerKey(connectionId));
|
|
9289
|
+
return value === null || value === void 0 ? void 0 : acpAuthorizationPointerSchema.parse(value);
|
|
9290
|
+
}
|
|
9291
|
+
async function deleteAuthorizationPointer(state, connectionId, transactionId) {
|
|
9292
|
+
const result = await withLock(
|
|
9293
|
+
state,
|
|
9294
|
+
authorizationPointerLockKey(connectionId),
|
|
9295
|
+
async (lock) => {
|
|
9296
|
+
const pointer = await readAuthorizationPointer(state, connectionId);
|
|
9297
|
+
if (pointer?.transactionId !== transactionId) return;
|
|
9298
|
+
await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
|
|
9299
|
+
await state.delete(authorizationPointerKey(connectionId));
|
|
9300
|
+
},
|
|
9301
|
+
{
|
|
9302
|
+
keepAlive: true,
|
|
9303
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
9304
|
+
waitMs: LOCK_WAIT_MS2
|
|
9305
|
+
}
|
|
9306
|
+
);
|
|
9307
|
+
if (!result.acquired) {
|
|
9308
|
+
throw new Error("Timed out acquiring ACP authorization pointer control");
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
async function deleteAuthorization(state, authorization, transactionId) {
|
|
9312
|
+
await deleteAuthorizationPointer(
|
|
9313
|
+
state,
|
|
9314
|
+
authorization.connectionId,
|
|
9315
|
+
transactionId
|
|
9316
|
+
);
|
|
9317
|
+
await state.delete(authorizationKey(transactionId));
|
|
9318
|
+
}
|
|
9319
|
+
function completionReceipt(authorization, response) {
|
|
9320
|
+
return {
|
|
9321
|
+
deliveryKey: `${authorization.requestKey}:authorization-complete`,
|
|
9322
|
+
outputs: [
|
|
9323
|
+
{
|
|
9324
|
+
kind: "message",
|
|
9325
|
+
message: {
|
|
9326
|
+
jsonrpc: "2.0",
|
|
9327
|
+
method: acp3.methods.client.elicitation.complete,
|
|
9328
|
+
params: { elicitationId: authorization.elicitationId }
|
|
9329
|
+
}
|
|
9330
|
+
},
|
|
9331
|
+
{ kind: "message", message: response }
|
|
9332
|
+
]
|
|
9333
|
+
};
|
|
9334
|
+
}
|
|
9335
|
+
function resultMessage2(requestId) {
|
|
9336
|
+
return { jsonrpc: "2.0", id: requestId, result: {} };
|
|
9337
|
+
}
|
|
9338
|
+
function cancelledMessage(requestId, message = "Junior sign-in was cancelled") {
|
|
9339
|
+
return {
|
|
9340
|
+
jsonrpc: "2.0",
|
|
9341
|
+
id: requestId,
|
|
9342
|
+
error: acp3.RequestError.requestCancelled(
|
|
9343
|
+
void 0,
|
|
9344
|
+
message
|
|
9345
|
+
).toErrorResponse()
|
|
9346
|
+
};
|
|
9347
|
+
}
|
|
9348
|
+
async function beginAcpAuthorization(args) {
|
|
9349
|
+
const url = authorizationBaseURL(args.request, args.baseURL);
|
|
9350
|
+
const result = await withLock(
|
|
9351
|
+
args.state,
|
|
9352
|
+
authorizationPointerLockKey(args.connectionId),
|
|
9353
|
+
async (lock) => {
|
|
9354
|
+
const pointer = await readAuthorizationPointer(
|
|
9355
|
+
args.state,
|
|
9356
|
+
args.connectionId
|
|
9357
|
+
);
|
|
9358
|
+
if (pointer) {
|
|
9359
|
+
const pending = await readAuthorization(
|
|
9360
|
+
args.state,
|
|
9361
|
+
pointer.transactionId
|
|
9362
|
+
);
|
|
9363
|
+
if (pending?.connectionId === args.connectionId) {
|
|
9364
|
+
throw acp3.RequestError.invalidRequest(
|
|
9365
|
+
void 0,
|
|
9366
|
+
"Junior sign-in is already in progress"
|
|
9367
|
+
);
|
|
9368
|
+
}
|
|
9369
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9370
|
+
await args.state.delete(authorizationPointerKey(args.connectionId));
|
|
9371
|
+
}
|
|
9372
|
+
const transactionId2 = randomUUID2();
|
|
9373
|
+
const userCode2 = createUserCode();
|
|
9374
|
+
const authorization = acpAuthorizationSchema.parse({
|
|
9375
|
+
connectionId: args.connectionId,
|
|
9376
|
+
credentialHash: args.credentialHash,
|
|
9377
|
+
elicitationId: transactionId2,
|
|
9378
|
+
expiresAtMs: Date.now() + ACP_AUTHORIZATION_TTL_MS,
|
|
9379
|
+
requestId: args.requestId,
|
|
9380
|
+
requestKey: args.requestKey,
|
|
9381
|
+
userCodeHash: userCodeHash(userCode2)
|
|
9382
|
+
});
|
|
9383
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9384
|
+
await args.state.set(
|
|
9385
|
+
authorizationKey(transactionId2),
|
|
9386
|
+
authorization,
|
|
9387
|
+
ACP_STATE_TTL_MS
|
|
9388
|
+
);
|
|
9389
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9390
|
+
await args.state.set(
|
|
9391
|
+
authorizationPointerKey(args.connectionId),
|
|
9392
|
+
{ expiresAtMs: authorization.expiresAtMs, transactionId: transactionId2 },
|
|
9393
|
+
ACP_STATE_TTL_MS
|
|
9394
|
+
);
|
|
9395
|
+
return { transactionId: transactionId2, userCode: userCode2 };
|
|
9396
|
+
},
|
|
9397
|
+
{
|
|
9398
|
+
keepAlive: true,
|
|
9399
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
9400
|
+
waitMs: LOCK_WAIT_MS2
|
|
9401
|
+
}
|
|
9402
|
+
);
|
|
9403
|
+
if (!result.acquired) {
|
|
9404
|
+
throw new Error("Timed out acquiring ACP authorization pointer control");
|
|
9405
|
+
}
|
|
9406
|
+
const { transactionId, userCode } = result.value;
|
|
9407
|
+
url.pathname = `/_junior/acp/auth/${transactionId}`;
|
|
9408
|
+
const params = {
|
|
9409
|
+
mode: "url",
|
|
9410
|
+
message: `Sign in to Junior in your browser. Enter verification code ${userCode}.`,
|
|
9411
|
+
elicitationId: transactionId,
|
|
9412
|
+
requestId: args.requestId,
|
|
9413
|
+
url: url.toString()
|
|
9414
|
+
};
|
|
9415
|
+
return {
|
|
9416
|
+
deliveryKey: `${args.requestKey}:authorization-start`,
|
|
9417
|
+
outputs: [
|
|
9418
|
+
{
|
|
9419
|
+
kind: "message",
|
|
9420
|
+
message: {
|
|
9421
|
+
jsonrpc: "2.0",
|
|
9422
|
+
id: transactionId,
|
|
9423
|
+
method: acp3.methods.client.elicitation.create,
|
|
9424
|
+
params
|
|
9425
|
+
}
|
|
9426
|
+
}
|
|
9427
|
+
]
|
|
9428
|
+
};
|
|
9429
|
+
}
|
|
9430
|
+
async function finishAuthorization(args) {
|
|
9431
|
+
const result = await completeAcpRequest({
|
|
9432
|
+
connectionId: args.authorization.connectionId,
|
|
9433
|
+
receipt: completionReceipt(args.authorization, args.response),
|
|
9434
|
+
requestKey: args.authorization.requestKey,
|
|
9435
|
+
state: args.state
|
|
9436
|
+
});
|
|
9437
|
+
return result === "full" ? "busy" : result;
|
|
9438
|
+
}
|
|
9439
|
+
async function completeAcpAuthorization(args) {
|
|
9440
|
+
const result = await withLock(
|
|
9441
|
+
args.state,
|
|
9442
|
+
authorizationLockKey(args.transactionId),
|
|
9443
|
+
async (lock) => {
|
|
9444
|
+
const authorization = await readAuthorization(
|
|
9445
|
+
args.state,
|
|
9446
|
+
args.transactionId
|
|
9447
|
+
);
|
|
9448
|
+
if (!authorization) {
|
|
9449
|
+
return "expired";
|
|
9450
|
+
}
|
|
9451
|
+
if (authorization.expiresAtMs <= Date.now()) {
|
|
9452
|
+
const completion2 = await finishAuthorization({
|
|
9453
|
+
authorization,
|
|
9454
|
+
response: cancelledMessage(
|
|
9455
|
+
authorization.requestId,
|
|
9456
|
+
"Junior sign-in request expired"
|
|
9457
|
+
),
|
|
9458
|
+
state: args.state
|
|
9459
|
+
});
|
|
9460
|
+
if (completion2 === "busy") return completion2;
|
|
9461
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9462
|
+
await deleteAuthorization(
|
|
9463
|
+
args.state,
|
|
9464
|
+
authorization,
|
|
9465
|
+
args.transactionId
|
|
9466
|
+
);
|
|
9467
|
+
return "expired";
|
|
9468
|
+
}
|
|
9469
|
+
if (!userCodeMatches(args.userCode, authorization.userCodeHash)) {
|
|
9470
|
+
return "invalid";
|
|
9471
|
+
}
|
|
9472
|
+
const binding = await bindAcpConnectionUser({
|
|
9473
|
+
connectionId: authorization.connectionId,
|
|
9474
|
+
credentialHash: authorization.credentialHash,
|
|
9475
|
+
state: args.state,
|
|
9476
|
+
user: userSchema2.parse(args.user)
|
|
9477
|
+
});
|
|
9478
|
+
if (binding !== "completed") {
|
|
9479
|
+
const completion2 = await finishAuthorization({
|
|
9480
|
+
authorization,
|
|
9481
|
+
response: cancelledMessage(
|
|
9482
|
+
authorization.requestId,
|
|
9483
|
+
"Junior sign-in could not bind this connection"
|
|
9484
|
+
),
|
|
9485
|
+
state: args.state
|
|
9486
|
+
});
|
|
9487
|
+
if (completion2 !== "completed") return completion2;
|
|
9488
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9489
|
+
await deleteAuthorization(
|
|
9490
|
+
args.state,
|
|
9491
|
+
authorization,
|
|
9492
|
+
args.transactionId
|
|
9493
|
+
);
|
|
9494
|
+
return binding;
|
|
9495
|
+
}
|
|
9496
|
+
const completion = await finishAuthorization({
|
|
9497
|
+
authorization,
|
|
9498
|
+
response: resultMessage2(authorization.requestId),
|
|
9499
|
+
state: args.state
|
|
9500
|
+
});
|
|
9501
|
+
if (completion !== "completed") return completion;
|
|
9502
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9503
|
+
await deleteAuthorization(args.state, authorization, args.transactionId);
|
|
9504
|
+
return "completed";
|
|
9505
|
+
},
|
|
9506
|
+
{
|
|
9507
|
+
keepAlive: true,
|
|
9508
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
9509
|
+
waitMs: LOCK_WAIT_MS2
|
|
9510
|
+
}
|
|
9511
|
+
);
|
|
9512
|
+
if (!result.acquired) {
|
|
9513
|
+
throw new Error("Timed out acquiring ACP authorization control");
|
|
9514
|
+
}
|
|
9515
|
+
return result.value;
|
|
9516
|
+
}
|
|
9517
|
+
async function handleAcpAuthorizationResponse(args) {
|
|
9518
|
+
if (typeof args.response.id !== "string") return "ignored";
|
|
9519
|
+
const parsedId = z7.string().uuid().safeParse(args.response.id);
|
|
9520
|
+
if (!parsedId.success) return "ignored";
|
|
9521
|
+
const authorization = await readAuthorization(args.state, parsedId.data);
|
|
9522
|
+
if (!authorization || authorization.connectionId !== args.connectionId) {
|
|
9523
|
+
return "ignored";
|
|
9524
|
+
}
|
|
9525
|
+
const action = "result" in args.response && typeof args.response.result === "object" && args.response.result !== null && "action" in args.response.result && typeof args.response.result.action === "string" ? args.response.result.action : void 0;
|
|
9526
|
+
if (action === "accept") return "accepted";
|
|
9527
|
+
const result = await withLock(
|
|
9528
|
+
args.state,
|
|
9529
|
+
authorizationLockKey(parsedId.data),
|
|
9530
|
+
async (lock) => {
|
|
9531
|
+
const current = await readAuthorization(args.state, parsedId.data);
|
|
9532
|
+
if (!current || current.connectionId !== args.connectionId) return false;
|
|
9533
|
+
const completion = await finishAuthorization({
|
|
9534
|
+
authorization: current,
|
|
9535
|
+
response: cancelledMessage(current.requestId),
|
|
9536
|
+
state: args.state
|
|
9537
|
+
});
|
|
9538
|
+
if (completion === "busy") return false;
|
|
9539
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9540
|
+
await deleteAuthorization(args.state, current, parsedId.data);
|
|
9541
|
+
return true;
|
|
9542
|
+
},
|
|
9543
|
+
{
|
|
9544
|
+
keepAlive: true,
|
|
9545
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
9546
|
+
waitMs: LOCK_WAIT_MS2
|
|
9547
|
+
}
|
|
9548
|
+
);
|
|
9549
|
+
if (!result.acquired) {
|
|
9550
|
+
throw new Error("Timed out acquiring ACP authorization control");
|
|
9551
|
+
}
|
|
9552
|
+
return result.value ? "cancelled" : "ignored";
|
|
9553
|
+
}
|
|
9554
|
+
async function expireAcpAuthorization(args) {
|
|
9555
|
+
const pointer = await readAuthorizationPointer(args.state, args.connectionId);
|
|
9556
|
+
if (!pointer || pointer.expiresAtMs > Date.now()) return false;
|
|
9557
|
+
const result = await withLock(
|
|
9558
|
+
args.state,
|
|
9559
|
+
authorizationLockKey(pointer.transactionId),
|
|
9560
|
+
async (lock) => {
|
|
9561
|
+
const authorization = await readAuthorization(
|
|
9562
|
+
args.state,
|
|
9563
|
+
pointer.transactionId
|
|
9564
|
+
);
|
|
9565
|
+
if (!authorization || authorization.connectionId !== args.connectionId) {
|
|
9566
|
+
await deleteAuthorizationPointer(
|
|
9567
|
+
args.state,
|
|
9568
|
+
args.connectionId,
|
|
9569
|
+
pointer.transactionId
|
|
9570
|
+
);
|
|
9571
|
+
return false;
|
|
9572
|
+
}
|
|
9573
|
+
if (authorization.expiresAtMs > Date.now()) return false;
|
|
9574
|
+
const completion = await finishAuthorization({
|
|
9575
|
+
authorization,
|
|
9576
|
+
response: cancelledMessage(
|
|
9577
|
+
authorization.requestId,
|
|
9578
|
+
"Junior sign-in request expired"
|
|
9579
|
+
),
|
|
9580
|
+
state: args.state
|
|
9581
|
+
});
|
|
9582
|
+
if (completion === "busy") return false;
|
|
9583
|
+
await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
|
|
9584
|
+
await deleteAuthorization(
|
|
9585
|
+
args.state,
|
|
9586
|
+
authorization,
|
|
9587
|
+
pointer.transactionId
|
|
9588
|
+
);
|
|
9589
|
+
return true;
|
|
9590
|
+
},
|
|
9591
|
+
{
|
|
9592
|
+
keepAlive: true,
|
|
9593
|
+
ttlMs: MUTATION_LOCK_TTL_MS,
|
|
9594
|
+
waitMs: LOCK_WAIT_MS2
|
|
9595
|
+
}
|
|
9596
|
+
);
|
|
9597
|
+
if (!result.acquired) return false;
|
|
9598
|
+
return result.value;
|
|
9599
|
+
}
|
|
9600
|
+
|
|
9601
|
+
// src/api/acp/route.ts
|
|
9602
|
+
var ACP_CONNECTION_ID_HEADER = "Acp-Connection-Id";
|
|
9603
|
+
var ACP_SESSION_ID_HEADER = "Acp-Session-Id";
|
|
9604
|
+
var ACP_CONVERSATION_PREFIX = "local:acp:";
|
|
9605
|
+
var ACP_EVENT_STREAM_MIME_TYPE = "text/event-stream";
|
|
9606
|
+
var ACP_JSON_MIME_TYPE = "application/json";
|
|
9607
|
+
var MAX_PROMPT_TEXT_LENGTH = 32e3;
|
|
9608
|
+
function isRecord2(value) {
|
|
9609
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9610
|
+
}
|
|
9611
|
+
function authenticatedAcpUser(connection) {
|
|
9612
|
+
return connection.user;
|
|
9613
|
+
}
|
|
9614
|
+
function supportsUrlElicitation(params) {
|
|
9615
|
+
const parsed = initializeParamsSchema.safeParse(params);
|
|
9616
|
+
if (!parsed.success) return false;
|
|
9617
|
+
const elicitation = parsed.data.clientCapabilities?.elicitation;
|
|
9618
|
+
return isRecord2(elicitation) && isRecord2(elicitation.url);
|
|
9619
|
+
}
|
|
9620
|
+
function parseParams(schema, params) {
|
|
9621
|
+
const parsed = schema.safeParse(params);
|
|
9622
|
+
if (parsed.success) return parsed.data;
|
|
9623
|
+
const firstPath = parsed.error.issues[0]?.path[0];
|
|
9624
|
+
throw acp4.RequestError.invalidParams({
|
|
9625
|
+
field: typeof firstPath === "string" || typeof firstPath === "number" ? String(firstPath) : "params"
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
function rejectUnsupportedMcpServers(mcpServers) {
|
|
9629
|
+
if (mcpServers.length > 0) {
|
|
9630
|
+
throw acp4.RequestError.invalidParams(
|
|
9631
|
+
{ field: "mcpServers" },
|
|
9632
|
+
"Junior does not accept client MCP servers"
|
|
9633
|
+
);
|
|
9634
|
+
}
|
|
9635
|
+
}
|
|
9636
|
+
function resourceLinkText(block) {
|
|
9637
|
+
return [
|
|
9638
|
+
"Resource link:",
|
|
9639
|
+
`Name: ${block.name}`,
|
|
9640
|
+
`URI: ${block.uri}`,
|
|
9641
|
+
...block.title ? [`Title: ${block.title}`] : [],
|
|
9642
|
+
...block.description ? [`Description: ${block.description}`] : [],
|
|
9643
|
+
...block.mimeType ? [`MIME type: ${block.mimeType}`] : [],
|
|
9644
|
+
...block.size !== void 0 && block.size !== null ? [`Size: ${block.size} bytes`] : []
|
|
9645
|
+
].join("\n");
|
|
9646
|
+
}
|
|
9647
|
+
function promptText(prompt) {
|
|
9648
|
+
if (prompt.length === 0) {
|
|
9649
|
+
throw acp4.RequestError.invalidParams(
|
|
9650
|
+
{ field: "prompt" },
|
|
9651
|
+
"Junior accepts one or more text or resource link blocks"
|
|
9652
|
+
);
|
|
9653
|
+
}
|
|
9654
|
+
const blocks = [];
|
|
9655
|
+
for (const block of prompt) {
|
|
9656
|
+
if (block.type === "resource_link") {
|
|
9657
|
+
blocks.push(resourceLinkText(block));
|
|
9658
|
+
continue;
|
|
9659
|
+
}
|
|
9660
|
+
if (!block.text.trim()) {
|
|
9661
|
+
throw acp4.RequestError.invalidParams(
|
|
9662
|
+
{ field: "prompt" },
|
|
9663
|
+
"Junior accepts non-empty text blocks only"
|
|
9664
|
+
);
|
|
9665
|
+
}
|
|
9666
|
+
blocks.push(block.text);
|
|
9667
|
+
}
|
|
9668
|
+
const text = blocks.join("\n");
|
|
9669
|
+
if (text.length > MAX_PROMPT_TEXT_LENGTH) {
|
|
9670
|
+
throw acp4.RequestError.invalidParams(
|
|
9671
|
+
{ field: "prompt" },
|
|
9672
|
+
`Junior accepts at most ${MAX_PROMPT_TEXT_LENGTH} prompt characters`
|
|
9673
|
+
);
|
|
9674
|
+
}
|
|
9675
|
+
return text;
|
|
9676
|
+
}
|
|
9677
|
+
function stableHex2(value, length = 32) {
|
|
9678
|
+
return createHash4("sha256").update(value).digest("hex").slice(0, length);
|
|
9679
|
+
}
|
|
9680
|
+
function canonicalJson(value) {
|
|
9681
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
9682
|
+
if (!isRecord2(value)) return value;
|
|
9683
|
+
return Object.fromEntries(
|
|
9684
|
+
Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, canonicalJson(child)])
|
|
9685
|
+
);
|
|
9686
|
+
}
|
|
9687
|
+
function requestKey(call) {
|
|
9688
|
+
const id = call.id === null ? "null" : `${typeof call.id}:${stableHex2(String(call.id))}`;
|
|
9689
|
+
const payload = JSON.stringify(
|
|
9690
|
+
canonicalJson({ method: call.method, params: call.params ?? null })
|
|
9691
|
+
);
|
|
9692
|
+
return `${id}:${stableHex2(payload)}`;
|
|
9693
|
+
}
|
|
9694
|
+
function sessionIdFromCall(call) {
|
|
9695
|
+
if (!call) return void 0;
|
|
9696
|
+
const schema = call.method === acp4.methods.agent.session.load ? loadSessionParamsSchema : call.method === acp4.methods.agent.session.prompt ? promptParamsSchema : call.method === acp4.methods.agent.session.cancel ? cancelParamsSchema : void 0;
|
|
9697
|
+
if (!schema) return void 0;
|
|
9698
|
+
const parsed = schema.safeParse(call.params);
|
|
9699
|
+
return parsed.success ? parsed.data.sessionId : void 0;
|
|
9700
|
+
}
|
|
9701
|
+
function determineRoute(connectionId, call, headerSessionId) {
|
|
9702
|
+
const paramsSessionId = sessionIdFromCall(call);
|
|
9703
|
+
if (SESSION_HEADER_METHODS.has(call.method) && !headerSessionId) {
|
|
9704
|
+
return textResponse("Missing Acp-Session-Id", 400);
|
|
9705
|
+
}
|
|
9706
|
+
if (headerSessionId !== void 0 && paramsSessionId !== void 0 && headerSessionId !== paramsSessionId) {
|
|
9707
|
+
return textResponse("Mismatched Acp-Session-Id", 400);
|
|
9708
|
+
}
|
|
9709
|
+
return {
|
|
9710
|
+
connectionId,
|
|
9711
|
+
...headerSessionId ? { sessionId: headerSessionId } : paramsSessionId ? { sessionId: paramsSessionId } : {}
|
|
9712
|
+
};
|
|
9713
|
+
}
|
|
9714
|
+
function resultMessage3(requestId, result) {
|
|
9715
|
+
return { jsonrpc: "2.0", id: requestId, result };
|
|
9716
|
+
}
|
|
9717
|
+
function errorMessage2(requestId, error) {
|
|
9718
|
+
return {
|
|
9719
|
+
jsonrpc: "2.0",
|
|
9720
|
+
id: requestId,
|
|
9721
|
+
error: error.toErrorResponse()
|
|
9722
|
+
};
|
|
9723
|
+
}
|
|
9724
|
+
function textResponse(body, status) {
|
|
9725
|
+
return new Response(body, {
|
|
9726
|
+
status,
|
|
9727
|
+
headers: { "Content-Type": "text/plain" }
|
|
9728
|
+
});
|
|
9729
|
+
}
|
|
9730
|
+
function jsonResponse(value, status, headers) {
|
|
9731
|
+
return new Response(JSON.stringify(value), {
|
|
9732
|
+
status,
|
|
9733
|
+
headers: { "Content-Type": ACP_JSON_MIME_TYPE, ...headers }
|
|
9734
|
+
});
|
|
9735
|
+
}
|
|
9736
|
+
function emptyResponse(status) {
|
|
9737
|
+
return new Response(null, { status });
|
|
9738
|
+
}
|
|
9739
|
+
async function createSession(args) {
|
|
9740
|
+
const params = parseParams(
|
|
9741
|
+
newSessionParamsSchema,
|
|
9742
|
+
args.call.params
|
|
9743
|
+
);
|
|
9744
|
+
rejectUnsupportedMcpServers(params.mcpServers);
|
|
9745
|
+
const sessionId = `${ACP_CONVERSATION_PREFIX}${stableHex2(
|
|
9746
|
+
`${args.connection.nonce}:${args.requestKey}`
|
|
9747
|
+
)}`;
|
|
9748
|
+
await args.conversations.create({
|
|
9749
|
+
conversationId: sessionId,
|
|
9750
|
+
user: args.user
|
|
9751
|
+
});
|
|
9752
|
+
return sessionId;
|
|
9753
|
+
}
|
|
9754
|
+
async function acceptPrompt(args) {
|
|
9755
|
+
const params = parseParams(
|
|
9756
|
+
promptParamsSchema,
|
|
9757
|
+
args.call.params
|
|
9758
|
+
);
|
|
9759
|
+
const admission = await args.conversations.prompt({
|
|
9760
|
+
conversationId: params.sessionId,
|
|
9761
|
+
idempotencyKey: `${args.connection.nonce}:${args.requestKey}`,
|
|
9762
|
+
text: promptText(params.prompt),
|
|
9763
|
+
user: args.user
|
|
9764
|
+
});
|
|
9765
|
+
if (admission.status === "not_found") {
|
|
9766
|
+
throw acp4.RequestError.resourceNotFound(params.sessionId);
|
|
9767
|
+
}
|
|
9768
|
+
if (admission.status === "active") {
|
|
9769
|
+
throw acp4.RequestError.invalidParams(
|
|
9770
|
+
{ field: "sessionId" },
|
|
9771
|
+
"This ACP session already has an active prompt"
|
|
9772
|
+
);
|
|
9773
|
+
}
|
|
9774
|
+
return {
|
|
9775
|
+
output: {
|
|
9776
|
+
afterSeq: admission.afterCursor,
|
|
9777
|
+
kind: "prompt",
|
|
9778
|
+
messageId: admission.messageId,
|
|
9779
|
+
requestId: args.requestId,
|
|
9780
|
+
turnId: admission.turnId
|
|
9781
|
+
},
|
|
9782
|
+
sessionId: params.sessionId
|
|
9783
|
+
};
|
|
9784
|
+
}
|
|
9785
|
+
async function createRequestReceipt(args) {
|
|
9786
|
+
if (args.call.method === acp4.methods.agent.session.new) {
|
|
9787
|
+
const sessionId = await createSession(args);
|
|
9788
|
+
return {
|
|
9789
|
+
outputs: [
|
|
9790
|
+
{
|
|
9791
|
+
kind: "message",
|
|
9792
|
+
message: resultMessage3(args.call.id, { sessionId })
|
|
9793
|
+
}
|
|
9794
|
+
]
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
9797
|
+
if (args.call.method === acp4.methods.agent.session.load) {
|
|
9798
|
+
const params = parseParams(
|
|
9799
|
+
loadSessionParamsSchema,
|
|
9800
|
+
args.call.params
|
|
9801
|
+
);
|
|
9802
|
+
rejectUnsupportedMcpServers(params.mcpServers);
|
|
9803
|
+
if (!await args.conversations.hasAccess({
|
|
9804
|
+
conversationId: params.sessionId,
|
|
9805
|
+
user: args.user
|
|
9806
|
+
})) {
|
|
9807
|
+
throw acp4.RequestError.resourceNotFound(params.sessionId);
|
|
9808
|
+
}
|
|
9809
|
+
return {
|
|
9810
|
+
sessionId: params.sessionId,
|
|
9811
|
+
outputs: [
|
|
9812
|
+
{ kind: "replay" },
|
|
9813
|
+
{ kind: "message", message: resultMessage3(args.call.id, {}) }
|
|
9814
|
+
]
|
|
9815
|
+
};
|
|
9816
|
+
}
|
|
9817
|
+
if (args.call.method === acp4.methods.agent.session.prompt) {
|
|
9818
|
+
const prompt = await acceptPrompt({
|
|
9819
|
+
...args,
|
|
9820
|
+
requestId: args.call.id
|
|
9821
|
+
});
|
|
9822
|
+
return {
|
|
9823
|
+
outputs: [prompt.output],
|
|
9824
|
+
sessionId: prompt.sessionId
|
|
9825
|
+
};
|
|
9826
|
+
}
|
|
9827
|
+
throw acp4.RequestError.methodNotFound(args.call.method);
|
|
9828
|
+
}
|
|
9829
|
+
async function handleCancelNotification(args) {
|
|
9830
|
+
const params = parseParams(
|
|
9831
|
+
cancelParamsSchema,
|
|
9832
|
+
args.call.params
|
|
9833
|
+
);
|
|
9834
|
+
const result = await args.conversations.cancel({
|
|
9835
|
+
conversationId: params.sessionId,
|
|
9836
|
+
user: args.user
|
|
9837
|
+
});
|
|
9838
|
+
if (result === "not_found") {
|
|
9839
|
+
throw acp4.RequestError.resourceNotFound(params.sessionId);
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
async function requireConnection(args) {
|
|
9843
|
+
const value = await readAcpConnection(args.state, args.connectionId);
|
|
9844
|
+
if (!value || !hasAcpConnectionCredential(args.request, value.credentialHash)) {
|
|
9845
|
+
return void 0;
|
|
9846
|
+
}
|
|
9847
|
+
return value;
|
|
9848
|
+
}
|
|
9849
|
+
function isJsonContentType(contentType) {
|
|
9850
|
+
return contentType?.split(";", 1)[0]?.trim().toLowerCase() === ACP_JSON_MIME_TYPE;
|
|
9851
|
+
}
|
|
9852
|
+
async function handleInitialize(args) {
|
|
9853
|
+
const requestId = args.call.id;
|
|
9854
|
+
if (requestId === void 0) {
|
|
9855
|
+
return textResponse("Initialize request must include an ID", 400);
|
|
9856
|
+
}
|
|
9857
|
+
try {
|
|
9858
|
+
parseParams(initializeParamsSchema, args.call.params);
|
|
9859
|
+
if (!supportsUrlElicitation(args.call.params)) {
|
|
9860
|
+
throw acp4.RequestError.invalidParams(
|
|
9861
|
+
{ field: "clientCapabilities.elicitation.url" },
|
|
9862
|
+
"Remote Junior authentication requires URL elicitation support"
|
|
9863
|
+
);
|
|
9864
|
+
}
|
|
9865
|
+
const credential = createAcpConnectionCredential(args.request);
|
|
9866
|
+
const result = {
|
|
9867
|
+
protocolVersion: acp4.PROTOCOL_VERSION,
|
|
9868
|
+
agentCapabilities: {
|
|
9869
|
+
loadSession: true,
|
|
9870
|
+
promptCapabilities: {
|
|
9871
|
+
image: false,
|
|
9872
|
+
audio: false,
|
|
9873
|
+
embeddedContext: false
|
|
9874
|
+
}
|
|
9875
|
+
},
|
|
9876
|
+
authMethods: args.browserAuthAvailable ? [ACP_AUTH_METHOD] : [],
|
|
9877
|
+
agentInfo: { name: "junior", version: args.version }
|
|
9878
|
+
};
|
|
9879
|
+
const connection = await createAcpConnection(
|
|
9880
|
+
args.state,
|
|
9881
|
+
credential.credentialHash
|
|
9882
|
+
);
|
|
9883
|
+
return jsonResponse(resultMessage3(requestId, result), 200, {
|
|
9884
|
+
[ACP_CONNECTION_ID_HEADER]: connection.connectionId,
|
|
9885
|
+
"Set-Cookie": credential.cookie
|
|
9886
|
+
});
|
|
9887
|
+
} catch (error) {
|
|
9888
|
+
if (!(error instanceof acp4.RequestError)) throw error;
|
|
9889
|
+
return jsonResponse(errorMessage2(requestId, error), 200);
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
async function handleConnectedPost(args) {
|
|
9893
|
+
const requestId = args.call.id;
|
|
9894
|
+
if (requestId === void 0) {
|
|
9895
|
+
if (args.authenticated && args.call.method === acp4.methods.agent.session.cancel) {
|
|
9896
|
+
try {
|
|
9897
|
+
await handleCancelNotification({
|
|
9898
|
+
call: args.call,
|
|
9899
|
+
conversations: args.options.conversations,
|
|
9900
|
+
user: args.authenticated
|
|
9901
|
+
});
|
|
9902
|
+
} catch (error) {
|
|
9903
|
+
if (!(error instanceof acp4.RequestError)) throw error;
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
return emptyResponse(202);
|
|
9907
|
+
}
|
|
9908
|
+
const identifiedCall = { ...args.call, id: requestId };
|
|
9909
|
+
const identifiedRequestKey = requestKey(identifiedCall);
|
|
9910
|
+
const acceptance = {
|
|
9911
|
+
connectionId: args.connectionId,
|
|
9912
|
+
requestKey: identifiedRequestKey,
|
|
9913
|
+
state: args.state,
|
|
9914
|
+
createReceipt: async () => {
|
|
9915
|
+
try {
|
|
9916
|
+
if (identifiedCall.method === acp4.methods.agent.authenticate) {
|
|
9917
|
+
const params = parseParams(
|
|
9918
|
+
authenticateParamsSchema,
|
|
9919
|
+
identifiedCall.params
|
|
9920
|
+
);
|
|
9921
|
+
if (params.methodId !== ACP_AUTH_METHOD_ID) {
|
|
9922
|
+
throw acp4.RequestError.invalidParams(
|
|
9923
|
+
{ field: "methodId" },
|
|
9924
|
+
"Junior did not advertise this authentication method"
|
|
9925
|
+
);
|
|
9926
|
+
}
|
|
9927
|
+
if (args.authenticated) {
|
|
9928
|
+
return {
|
|
9929
|
+
outputs: [
|
|
9930
|
+
{
|
|
9931
|
+
kind: "message",
|
|
9932
|
+
message: resultMessage3(requestId, {})
|
|
9933
|
+
}
|
|
9934
|
+
]
|
|
9935
|
+
};
|
|
9936
|
+
}
|
|
9937
|
+
if (!args.options.browserAuth) {
|
|
9938
|
+
throw acp4.RequestError.internalError(
|
|
9939
|
+
void 0,
|
|
9940
|
+
"Junior ACP authentication requires an enabled dashboard"
|
|
9941
|
+
);
|
|
9942
|
+
}
|
|
9943
|
+
return await beginAcpAuthorization({
|
|
9944
|
+
baseURL: args.options.browserAuth.baseURL,
|
|
9945
|
+
connectionId: args.connectionId,
|
|
9946
|
+
credentialHash: args.connection.credentialHash,
|
|
9947
|
+
request: args.request,
|
|
9948
|
+
requestId,
|
|
9949
|
+
requestKey: identifiedRequestKey,
|
|
9950
|
+
state: args.state
|
|
9951
|
+
});
|
|
9952
|
+
}
|
|
9953
|
+
if (!args.authenticated) {
|
|
9954
|
+
throw acp4.RequestError.authRequired();
|
|
9955
|
+
}
|
|
9956
|
+
return await createRequestReceipt({
|
|
9957
|
+
call: identifiedCall,
|
|
9958
|
+
connection: args.connection,
|
|
9959
|
+
conversations: args.options.conversations,
|
|
9960
|
+
requestKey: identifiedRequestKey,
|
|
9961
|
+
user: args.authenticated
|
|
9962
|
+
});
|
|
9963
|
+
} catch (error) {
|
|
9964
|
+
if (!(error instanceof acp4.RequestError)) throw error;
|
|
9965
|
+
const receipt = {
|
|
9966
|
+
outputs: [
|
|
9967
|
+
{
|
|
9968
|
+
kind: "message",
|
|
9969
|
+
message: errorMessage2(requestId, error)
|
|
9970
|
+
}
|
|
9971
|
+
]
|
|
9972
|
+
};
|
|
9973
|
+
if (args.route.sessionId) receipt.sessionId = args.route.sessionId;
|
|
9974
|
+
return receipt;
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
9977
|
+
};
|
|
9978
|
+
if (identifiedCall.method === acp4.methods.agent.session.prompt) {
|
|
9979
|
+
acceptance.reserveRoute = args.route;
|
|
9980
|
+
}
|
|
9981
|
+
const status = await acceptAcpRequest(acceptance);
|
|
9982
|
+
if (status === "busy") {
|
|
9983
|
+
return textResponse("ACP request is already being accepted", 503);
|
|
9984
|
+
}
|
|
9985
|
+
if (status === "expired") {
|
|
9986
|
+
return textResponse("Unknown Acp-Connection-Id", 404);
|
|
9987
|
+
}
|
|
9988
|
+
if (status === "full") {
|
|
9989
|
+
return textResponse("ACP stream has too much undelivered output", 503);
|
|
9990
|
+
}
|
|
9991
|
+
return emptyResponse(202);
|
|
9992
|
+
}
|
|
9993
|
+
async function handleConnectedResponse(args) {
|
|
9994
|
+
await handleAcpAuthorizationResponse({
|
|
9995
|
+
connectionId: args.connectionId,
|
|
9996
|
+
response: args.response,
|
|
9997
|
+
state: args.state
|
|
9998
|
+
});
|
|
9999
|
+
return emptyResponse(202);
|
|
10000
|
+
}
|
|
10001
|
+
function createAcpHttpHandler(options) {
|
|
10002
|
+
const state = options.state;
|
|
10003
|
+
return async (request) => {
|
|
10004
|
+
let authenticated;
|
|
10005
|
+
let call;
|
|
10006
|
+
let connectionId;
|
|
10007
|
+
let sessionId;
|
|
10008
|
+
try {
|
|
10009
|
+
if (request.headers.has("Authorization")) {
|
|
10010
|
+
return new Response("Unauthorized", { status: 401 });
|
|
10011
|
+
}
|
|
10012
|
+
const connectionHeader = request.headers.get(ACP_CONNECTION_ID_HEADER);
|
|
10013
|
+
if (connectionHeader !== null) {
|
|
10014
|
+
const parsed = acpConnectionIdSchema.safeParse(connectionHeader);
|
|
10015
|
+
if (!parsed.success) {
|
|
10016
|
+
return textResponse("Invalid Acp-Connection-Id", 400);
|
|
10017
|
+
}
|
|
10018
|
+
connectionId = parsed.data;
|
|
10019
|
+
}
|
|
10020
|
+
const sessionHeader = request.headers.get(ACP_SESSION_ID_HEADER);
|
|
10021
|
+
if (sessionHeader !== null) {
|
|
10022
|
+
const parsed = acpSessionIdSchema.safeParse(sessionHeader);
|
|
10023
|
+
if (!parsed.success) {
|
|
10024
|
+
return textResponse("Invalid Acp-Session-Id", 400);
|
|
10025
|
+
}
|
|
10026
|
+
sessionId = parsed.data;
|
|
10027
|
+
}
|
|
10028
|
+
if (request.method === "POST") {
|
|
10029
|
+
if (!isJsonContentType(request.headers.get("Content-Type"))) {
|
|
10030
|
+
return textResponse("Unsupported Media Type", 415);
|
|
10031
|
+
}
|
|
10032
|
+
let value;
|
|
10033
|
+
try {
|
|
10034
|
+
value = await request.json();
|
|
10035
|
+
} catch {
|
|
10036
|
+
return textResponse("Invalid JSON", 400);
|
|
10037
|
+
}
|
|
10038
|
+
if (Array.isArray(value)) {
|
|
10039
|
+
return textResponse(
|
|
10040
|
+
"Batch JSON-RPC requests are not implemented",
|
|
10041
|
+
501
|
|
10042
|
+
);
|
|
10043
|
+
}
|
|
10044
|
+
const parsedMessage = acpInboundMessageSchema.safeParse(value);
|
|
10045
|
+
if (!parsedMessage.success) {
|
|
10046
|
+
return textResponse("Invalid JSON-RPC message", 400);
|
|
10047
|
+
}
|
|
10048
|
+
const message = parsedMessage.data;
|
|
10049
|
+
if ("method" in message && message.method === acp4.methods.agent.initialize) {
|
|
10050
|
+
call = message;
|
|
10051
|
+
if (connectionId) {
|
|
10052
|
+
return textResponse(
|
|
10053
|
+
"Initialize not allowed on existing connection",
|
|
10054
|
+
400
|
|
10055
|
+
);
|
|
10056
|
+
}
|
|
10057
|
+
return await handleInitialize({
|
|
10058
|
+
browserAuthAvailable: options.browserAuth !== void 0,
|
|
10059
|
+
call,
|
|
10060
|
+
request,
|
|
10061
|
+
state,
|
|
10062
|
+
version: options.version
|
|
10063
|
+
});
|
|
10064
|
+
}
|
|
10065
|
+
if (!connectionId) {
|
|
10066
|
+
return textResponse("Missing Acp-Connection-Id", 400);
|
|
10067
|
+
}
|
|
10068
|
+
const connection2 = await requireConnection({
|
|
10069
|
+
connectionId,
|
|
10070
|
+
request,
|
|
10071
|
+
state
|
|
10072
|
+
});
|
|
10073
|
+
if (!connection2) {
|
|
10074
|
+
return textResponse("Unauthorized", 401);
|
|
10075
|
+
}
|
|
10076
|
+
authenticated = authenticatedAcpUser(connection2);
|
|
10077
|
+
if (!("method" in message)) {
|
|
10078
|
+
return await handleConnectedResponse({
|
|
10079
|
+
connectionId,
|
|
10080
|
+
response: message,
|
|
10081
|
+
state
|
|
10082
|
+
});
|
|
10083
|
+
}
|
|
10084
|
+
call = message;
|
|
10085
|
+
const route = determineRoute(connectionId, call, sessionId);
|
|
10086
|
+
if (route instanceof Response) return route;
|
|
10087
|
+
return await handleConnectedPost({
|
|
10088
|
+
authenticated,
|
|
10089
|
+
call,
|
|
10090
|
+
connection: connection2,
|
|
10091
|
+
connectionId,
|
|
10092
|
+
options,
|
|
10093
|
+
request,
|
|
10094
|
+
route,
|
|
10095
|
+
state
|
|
10096
|
+
});
|
|
10097
|
+
}
|
|
10098
|
+
if (!connectionId) {
|
|
10099
|
+
return textResponse("Missing Acp-Connection-Id", 400);
|
|
10100
|
+
}
|
|
10101
|
+
const connection = await requireConnection({
|
|
10102
|
+
connectionId,
|
|
10103
|
+
request,
|
|
10104
|
+
state
|
|
10105
|
+
});
|
|
10106
|
+
if (!connection) {
|
|
10107
|
+
return textResponse("Unauthorized", 401);
|
|
10108
|
+
}
|
|
10109
|
+
authenticated = authenticatedAcpUser(connection);
|
|
10110
|
+
if (request.method === "GET") {
|
|
10111
|
+
if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") {
|
|
10112
|
+
return textResponse("WebSocket upgrade is not implemented", 426);
|
|
10113
|
+
}
|
|
10114
|
+
const accept = request.headers.get("Accept")?.toLowerCase();
|
|
10115
|
+
if (!accept?.includes(ACP_EVENT_STREAM_MIME_TYPE)) {
|
|
10116
|
+
return textResponse("Not Acceptable", 406);
|
|
10117
|
+
}
|
|
10118
|
+
const route = { connectionId };
|
|
10119
|
+
if (sessionId) route.sessionId = sessionId;
|
|
10120
|
+
const stream = {
|
|
10121
|
+
conversations: options.conversations,
|
|
10122
|
+
onError: options.onError,
|
|
10123
|
+
requestSignal: request.signal,
|
|
10124
|
+
route,
|
|
10125
|
+
state
|
|
10126
|
+
};
|
|
10127
|
+
if (!sessionId) {
|
|
10128
|
+
stream.maintain = async () => {
|
|
10129
|
+
await expireAcpAuthorization({
|
|
10130
|
+
connectionId: route.connectionId,
|
|
10131
|
+
state
|
|
10132
|
+
});
|
|
10133
|
+
};
|
|
10134
|
+
}
|
|
10135
|
+
if (authenticated) stream.userId = authenticated.id;
|
|
10136
|
+
return await openAcpSse(stream);
|
|
10137
|
+
}
|
|
10138
|
+
if (request.method === "DELETE") {
|
|
10139
|
+
await deleteAcpConnection(state, connectionId);
|
|
10140
|
+
return emptyResponse(202);
|
|
10141
|
+
}
|
|
10142
|
+
return textResponse("Method Not Allowed", 405);
|
|
10143
|
+
} catch (error) {
|
|
10144
|
+
const conversationId = sessionIdFromCall(call) ?? sessionId;
|
|
10145
|
+
const context = {};
|
|
10146
|
+
if (authenticated) context.userId = authenticated.id;
|
|
10147
|
+
if (connectionId) context.connectionId = connectionId;
|
|
10148
|
+
if (conversationId) context.conversationId = conversationId;
|
|
10149
|
+
options.onError?.(error, "acp.transport.exception", context);
|
|
10150
|
+
return textResponse("ACP transport failed", 500);
|
|
10151
|
+
}
|
|
10152
|
+
};
|
|
10153
|
+
}
|
|
10154
|
+
|
|
10155
|
+
// src/api/acp/authorization-page.ts
|
|
10156
|
+
var ACP_AUTHORIZATION_PATH = "/_junior/acp/auth/:transactionId";
|
|
10157
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
10158
|
+
function escapeHtml(value) {
|
|
10159
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10160
|
+
}
|
|
10161
|
+
function renderResult(agentName, result) {
|
|
10162
|
+
const content = {
|
|
10163
|
+
blocked: {
|
|
10164
|
+
status: 403,
|
|
10165
|
+
title: "Request blocked",
|
|
10166
|
+
message: "Open the sign-in link again and confirm the connection."
|
|
10167
|
+
},
|
|
10168
|
+
busy: {
|
|
10169
|
+
status: 503,
|
|
10170
|
+
title: "Your ACP client has pending output",
|
|
10171
|
+
message: "Reconnect the client, then confirm this connection again."
|
|
10172
|
+
},
|
|
10173
|
+
completed: {
|
|
10174
|
+
status: 200,
|
|
10175
|
+
title: `${agentName} is connected`,
|
|
10176
|
+
message: "You can close this window and return to your ACP client."
|
|
10177
|
+
},
|
|
10178
|
+
conflict: {
|
|
10179
|
+
status: 409,
|
|
10180
|
+
title: "This connection uses another account",
|
|
10181
|
+
message: "Start a new connection in your ACP client and sign in again."
|
|
10182
|
+
},
|
|
10183
|
+
expired: {
|
|
10184
|
+
status: 410,
|
|
10185
|
+
title: "This sign-in request expired",
|
|
10186
|
+
message: "Return to your ACP client and start sign-in again."
|
|
10187
|
+
},
|
|
10188
|
+
invalid: {
|
|
10189
|
+
status: 403,
|
|
10190
|
+
title: "Verification code did not match",
|
|
10191
|
+
message: "Return to your ACP client and enter the code shown there."
|
|
10192
|
+
}
|
|
10193
|
+
}[result];
|
|
10194
|
+
return new Response(
|
|
10195
|
+
`<!DOCTYPE html>
|
|
10196
|
+
<html lang="en">
|
|
10197
|
+
<head>
|
|
10198
|
+
<meta charset="utf-8" />
|
|
10199
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10200
|
+
<title>${escapeHtml(content.title)}</title>
|
|
10201
|
+
</head>
|
|
10202
|
+
<body style="margin:0;background:#000;color:#fff;font-family:ui-sans-serif,system-ui,sans-serif">
|
|
10203
|
+
<main style="min-height:100vh;display:grid;place-items:center;padding:2rem">
|
|
10204
|
+
<section style="max-width:32rem;border-left:4px solid #a78bfa;padding-left:1rem">
|
|
10205
|
+
<h1 style="margin:0 0 .75rem;font-size:2rem">${escapeHtml(content.title)}</h1>
|
|
10206
|
+
<p style="margin:0;color:#b8b8b8;line-height:1.5">${escapeHtml(content.message)}</p>
|
|
10207
|
+
</section>
|
|
10208
|
+
</main>
|
|
10209
|
+
</body>
|
|
10210
|
+
</html>`,
|
|
10211
|
+
{
|
|
10212
|
+
status: content.status,
|
|
10213
|
+
headers: {
|
|
10214
|
+
"cache-control": "no-store",
|
|
10215
|
+
"content-security-policy": "frame-ancestors 'none'; form-action 'self'",
|
|
10216
|
+
"content-type": "text/html; charset=utf-8",
|
|
10217
|
+
"x-frame-options": "DENY"
|
|
10218
|
+
}
|
|
10219
|
+
}
|
|
10220
|
+
);
|
|
10221
|
+
}
|
|
10222
|
+
function renderConfirmation(agentName) {
|
|
10223
|
+
return new Response(
|
|
10224
|
+
`<!DOCTYPE html>
|
|
10225
|
+
<html lang="en">
|
|
10226
|
+
<head>
|
|
10227
|
+
<meta charset="utf-8" />
|
|
10228
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10229
|
+
<title>Connect ${escapeHtml(agentName)}</title>
|
|
10230
|
+
</head>
|
|
10231
|
+
<body style="margin:0;background:#000;color:#fff;font-family:ui-sans-serif,system-ui,sans-serif">
|
|
10232
|
+
<main style="min-height:100vh;display:grid;place-items:center;padding:2rem">
|
|
10233
|
+
<section style="max-width:32rem;border-left:4px solid #a78bfa;padding-left:1rem">
|
|
10234
|
+
<h1 style="margin:0 0 .75rem;font-size:2rem">Connect ${escapeHtml(agentName)}</h1>
|
|
10235
|
+
<p style="margin:0 0 1rem;color:#b8b8b8;line-height:1.5">Only continue if you started sign-in from your ACP client. Enter the verification code shown by the client.</p>
|
|
10236
|
+
<form method="post">
|
|
10237
|
+
<label style="display:block;margin:0 0 1rem">Verification code<br><input name="code" required autocomplete="one-time-code" inputmode="text" style="box-sizing:border-box;margin-top:.5rem;padding:.625rem;width:100%"></label>
|
|
10238
|
+
<button type="submit" style="border:0;border-radius:.375rem;background:#a78bfa;color:#000;cursor:pointer;font:inherit;font-weight:700;padding:.625rem 1rem">Connect</button>
|
|
10239
|
+
</form>
|
|
10240
|
+
</section>
|
|
10241
|
+
</main>
|
|
10242
|
+
</body>
|
|
10243
|
+
</html>`,
|
|
10244
|
+
{
|
|
10245
|
+
headers: {
|
|
10246
|
+
"cache-control": "no-store",
|
|
10247
|
+
"content-security-policy": "frame-ancestors 'none'; form-action 'self'",
|
|
10248
|
+
"content-type": "text/html; charset=utf-8",
|
|
10249
|
+
"x-frame-options": "DENY"
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
);
|
|
10253
|
+
}
|
|
10254
|
+
function requestHasSameOrigin(request) {
|
|
10255
|
+
const origin = request.headers.get("origin");
|
|
10256
|
+
if (!origin) return false;
|
|
10257
|
+
try {
|
|
10258
|
+
return new URL(origin).origin === new URL(request.url).origin;
|
|
10259
|
+
} catch {
|
|
10260
|
+
return false;
|
|
10261
|
+
}
|
|
10262
|
+
}
|
|
10263
|
+
function transactionIdFromRequest(request) {
|
|
10264
|
+
const transactionId = new URL(request.url).pathname.split("/").at(-1);
|
|
10265
|
+
return transactionId && UUID_PATTERN.test(transactionId) ? transactionId : void 0;
|
|
10266
|
+
}
|
|
10267
|
+
async function handleAcpAuthorizationPage(args) {
|
|
10268
|
+
const transactionId = transactionIdFromRequest(args.request);
|
|
10269
|
+
if (!transactionId) return renderResult(args.agentName, "expired");
|
|
10270
|
+
if (args.request.method === "GET") {
|
|
10271
|
+
return renderConfirmation(args.agentName);
|
|
10272
|
+
}
|
|
10273
|
+
if (args.request.method !== "POST" || !requestHasSameOrigin(args.request)) {
|
|
10274
|
+
return renderResult(args.agentName, "blocked");
|
|
10275
|
+
}
|
|
10276
|
+
let userCode;
|
|
10277
|
+
try {
|
|
10278
|
+
const value = (await args.request.formData()).get("code");
|
|
10279
|
+
userCode = typeof value === "string" ? value : void 0;
|
|
10280
|
+
} catch {
|
|
10281
|
+
}
|
|
10282
|
+
if (!userCode?.trim()) return renderResult(args.agentName, "invalid");
|
|
10283
|
+
const result = await completeAcpAuthorization({
|
|
10284
|
+
state: args.state,
|
|
10285
|
+
transactionId,
|
|
10286
|
+
user: args.user,
|
|
10287
|
+
userCode
|
|
10288
|
+
});
|
|
10289
|
+
return renderResult(args.agentName, result);
|
|
10290
|
+
}
|
|
10291
|
+
|
|
10292
|
+
// src/app.ts
|
|
10293
|
+
function resolveAcpBaseURL(dashboard) {
|
|
10294
|
+
const configured = dashboard?.baseURL?.trim() || process.env.JUNIOR_BASE_URL?.trim();
|
|
10295
|
+
if (configured) return configured;
|
|
10296
|
+
const vercelURL = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim() || process.env.VERCEL_URL?.trim();
|
|
10297
|
+
if (!vercelURL) return void 0;
|
|
10298
|
+
return /^https?:\/\//.test(vercelURL) ? vercelURL : `https://${vercelURL}`;
|
|
10299
|
+
}
|
|
10300
|
+
var DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
|
|
10301
|
+
async function defaultWaitUntil() {
|
|
10302
|
+
try {
|
|
10303
|
+
const { waitUntil } = await import("@vercel/functions");
|
|
10304
|
+
return (task) => {
|
|
10305
|
+
const promise = typeof task === "function" ? task() : task;
|
|
10306
|
+
waitUntil(promise);
|
|
10307
|
+
};
|
|
10308
|
+
} catch {
|
|
10309
|
+
return (task) => {
|
|
10310
|
+
const promise = typeof task === "function" ? task() : task;
|
|
10311
|
+
promise.catch(console.error);
|
|
10312
|
+
};
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
async function resolveVirtualConfig() {
|
|
10316
|
+
try {
|
|
10317
|
+
const mod = await import("#junior/config");
|
|
10318
|
+
return {
|
|
10319
|
+
createDashboardApp: mod.createDashboardApp,
|
|
10320
|
+
functionMaxDurationSeconds: mod.functionMaxDurationSeconds,
|
|
10321
|
+
dashboard: mod.dashboard,
|
|
10322
|
+
pluginSet: mod.pluginSet,
|
|
10323
|
+
plugins: mod.plugins,
|
|
10324
|
+
pluginRuntimeRegistrations: mod.pluginRuntimeRegistrations ?? []
|
|
10325
|
+
};
|
|
10326
|
+
} catch (error) {
|
|
10327
|
+
if (!isMissingVirtualConfig(error)) {
|
|
10328
|
+
throw error;
|
|
10329
|
+
}
|
|
10330
|
+
return void 0;
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
function isMissingVirtualConfig(error) {
|
|
10334
|
+
if (!(error instanceof Error)) {
|
|
10335
|
+
return false;
|
|
10336
|
+
}
|
|
10337
|
+
const code = error.code;
|
|
10338
|
+
return (code === "ERR_PACKAGE_IMPORT_NOT_DEFINED" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && error.message.includes("#junior/config");
|
|
10339
|
+
}
|
|
10340
|
+
function hasConfiguredPluginCatalog(config) {
|
|
10341
|
+
if (!config) {
|
|
10342
|
+
return false;
|
|
10343
|
+
}
|
|
10344
|
+
return Boolean(
|
|
10345
|
+
config.inlineManifests?.length || config.packages?.length || Object.keys(config.manifests ?? {}).length
|
|
10346
|
+
);
|
|
10347
|
+
}
|
|
10348
|
+
function warnUnregisteredConfigDefaults(defaults) {
|
|
10349
|
+
for (const key of Object.keys(defaults ?? {})) {
|
|
10350
|
+
if (!pluginCatalogRuntime.isConfigKey(key)) {
|
|
10351
|
+
logWarn("config.default.unregistered", { "app.config.key": key });
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
function pluginPackageNames(config) {
|
|
10356
|
+
return config?.packages ?? [];
|
|
10357
|
+
}
|
|
10358
|
+
function validateBuildIncludesPluginPackages(pluginConfig, virtualConfig) {
|
|
10359
|
+
if (!virtualConfig?.plugins) {
|
|
10360
|
+
return;
|
|
10361
|
+
}
|
|
10362
|
+
const bundled = new Set(pluginPackageNames(virtualConfig.plugins));
|
|
10363
|
+
const missing = pluginPackageNames(pluginConfig).filter(
|
|
10364
|
+
(packageName) => !bundled.has(packageName)
|
|
10365
|
+
);
|
|
10366
|
+
if (missing.length === 0) {
|
|
10367
|
+
return;
|
|
10368
|
+
}
|
|
8420
10369
|
throw new Error(
|
|
8421
10370
|
`createApp() registered plugin package(s) not bundled by juniorNitro(): ${missing.join(", ")}. Point juniorNitro({ plugins: "./plugins" }) at the runtime plugin module or pass the same defineJuniorPlugins(...) set to juniorNitro({ plugins }) and createApp({ plugins }).`
|
|
8422
10371
|
);
|
|
@@ -8652,11 +10601,11 @@ function validateDashboardRouteOwnership(args) {
|
|
|
8652
10601
|
function mountRoutes(app, routes) {
|
|
8653
10602
|
for (const route of routes) {
|
|
8654
10603
|
const handler = (c) => route.handler(c.req.raw);
|
|
8655
|
-
const
|
|
8656
|
-
const explicitMethods =
|
|
10604
|
+
const methods5 = typeof route.method === "string" ? [route.method] : route.method ?? ["ALL"];
|
|
10605
|
+
const explicitMethods = methods5.filter(
|
|
8657
10606
|
(method) => method !== "ALL"
|
|
8658
10607
|
);
|
|
8659
|
-
if (
|
|
10608
|
+
if (methods5.includes("ALL")) {
|
|
8660
10609
|
app.all(route.path, handler);
|
|
8661
10610
|
} else if (explicitMethods.length > 0) {
|
|
8662
10611
|
app.on(explicitMethods, route.path, handler);
|
|
@@ -8790,7 +10739,7 @@ async function createApp(options) {
|
|
|
8790
10739
|
tracePropagation
|
|
8791
10740
|
});
|
|
8792
10741
|
const runtimeServiceOverrides = {
|
|
8793
|
-
|
|
10742
|
+
agentRunner,
|
|
8794
10743
|
sandbox: { tracePropagation }
|
|
8795
10744
|
};
|
|
8796
10745
|
let conversationWorkOptions;
|
|
@@ -8801,37 +10750,41 @@ async function createApp(options) {
|
|
|
8801
10750
|
});
|
|
8802
10751
|
return conversationWorkOptions;
|
|
8803
10752
|
};
|
|
8804
|
-
let
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
const
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
(
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
)
|
|
10753
|
+
let acpRuntime;
|
|
10754
|
+
const getAcpRuntime = () => {
|
|
10755
|
+
if (acpRuntime) return acpRuntime;
|
|
10756
|
+
const work = getConversationWorkOptions();
|
|
10757
|
+
const state = work.state ?? getStateAdapter();
|
|
10758
|
+
acpRuntime = {
|
|
10759
|
+
handleRequest: createAcpHttpHandler({
|
|
10760
|
+
...dashboard && !dashboard.disabled ? { browserAuth: { baseURL: resolveAcpBaseURL(dashboard) } } : void 0,
|
|
10761
|
+
conversations: createAcpConversations({
|
|
10762
|
+
conversationStore: work.conversationStore,
|
|
10763
|
+
eventStore: getConversationEventStore(),
|
|
10764
|
+
queue: work.queue ?? getVercelConversationWorkQueue(),
|
|
10765
|
+
state
|
|
10766
|
+
}),
|
|
10767
|
+
onError: (error, event, attributes) => void logException(error, event, { ...attributes, platform: "acp" }),
|
|
10768
|
+
state,
|
|
10769
|
+
version: JUNIOR_VERSION
|
|
10770
|
+
}),
|
|
10771
|
+
state
|
|
10772
|
+
};
|
|
10773
|
+
return acpRuntime;
|
|
10774
|
+
};
|
|
10775
|
+
const authenticatedRoutes = dashboard && !dashboard.disabled ? [
|
|
10776
|
+
{
|
|
10777
|
+
handler: (request, user) => handleAcpAuthorizationPage({
|
|
10778
|
+
agentName: botConfig.userName,
|
|
10779
|
+
request,
|
|
10780
|
+
state: getAcpRuntime().state,
|
|
10781
|
+
user
|
|
10782
|
+
}),
|
|
10783
|
+
method: ["GET", "POST"],
|
|
10784
|
+
path: ACP_AUTHORIZATION_PATH
|
|
8834
10785
|
}
|
|
10786
|
+
] : [];
|
|
10787
|
+
try {
|
|
8835
10788
|
validateDashboardRouteOwnership({
|
|
8836
10789
|
authenticatedRoutes,
|
|
8837
10790
|
dashboard,
|
|
@@ -8856,8 +10809,12 @@ async function createApp(options) {
|
|
|
8856
10809
|
next
|
|
8857
10810
|
);
|
|
8858
10811
|
});
|
|
10812
|
+
app.on(
|
|
10813
|
+
["GET", "POST", "DELETE"],
|
|
10814
|
+
"/api/acp",
|
|
10815
|
+
(c) => getAcpRuntime().handleRequest(c.req.raw)
|
|
10816
|
+
);
|
|
8859
10817
|
mountRoutes(app, pluginRoutes);
|
|
8860
|
-
mountRoutes(app, adapterRoutes);
|
|
8861
10818
|
mountRoutes(
|
|
8862
10819
|
app,
|
|
8863
10820
|
await createDashboardRouteRegistrations({
|