@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.
Files changed (122) hide show
  1. package/dist/{agent-hooks-SNRBZYAU.js → agent-hooks-NGZQLL5O.js} +13 -11
  2. package/dist/api/acp/auth.d.ts +46 -0
  3. package/dist/api/acp/authorization-page.d.ts +11 -0
  4. package/dist/api/acp/conversations.d.ts +70 -0
  5. package/dist/api/acp/errors.d.ts +6 -0
  6. package/dist/api/acp/route.d.ts +16 -0
  7. package/dist/api/acp/schema.d.ts +115 -0
  8. package/dist/api/acp/sleep.d.ts +2 -0
  9. package/dist/api/acp/state.d.ts +22 -0
  10. package/dist/api/acp/transport.d.ts +113 -0
  11. package/dist/api/code/overview.d.ts +39 -0
  12. package/dist/api/locations/detail.d.ts +1 -1
  13. package/dist/api/people/code.d.ts +22 -0
  14. package/dist/api/schema/code.d.ts +46 -0
  15. package/dist/api/schema/conversation.d.ts +100 -20
  16. package/dist/api/schema/location.d.ts +1 -1
  17. package/dist/api/schema/person.d.ts +1 -1
  18. package/dist/api/schema.d.ts +4 -4
  19. package/dist/api/schema.js +12 -1
  20. package/dist/api.js +358 -76
  21. package/dist/app.d.ts +0 -4
  22. package/dist/app.js +2450 -493
  23. package/dist/{catalog-runtime-3I3JOIIH.js → catalog-runtime-AAEAO2RL.js} +4 -4
  24. package/dist/chat/api-turns/accepted-reply.d.ts +11 -0
  25. package/dist/chat/api-turns/cancellation.d.ts +3 -5
  26. package/dist/chat/api-turns/work.d.ts +4 -9
  27. package/dist/chat/app/conversation-work.d.ts +1 -1
  28. package/dist/chat/app/factory.d.ts +4 -0
  29. package/dist/chat/app/services.d.ts +4 -3
  30. package/dist/chat/conversations/history.d.ts +83 -9
  31. package/dist/chat/conversations/turn-lifecycle.d.ts +4 -3
  32. package/dist/chat/providers/slack/input.d.ts +32 -0
  33. package/dist/chat/{runtime → providers/slack}/processing-reaction.d.ts +6 -8
  34. package/dist/chat/{runtime/slack-resume.d.ts → providers/slack/resume.d.ts} +16 -19
  35. package/dist/chat/providers/slack/runtime.d.ts +1 -10
  36. package/dist/chat/providers/slack/turn.d.ts +11 -24
  37. package/dist/chat/resource-events/store.d.ts +1 -1
  38. package/dist/chat/resource-events/tool-support.d.ts +0 -5
  39. package/dist/chat/runtime/turn-execution.d.ts +7 -2
  40. package/dist/chat/sandbox/egress/credentials.d.ts +3 -3
  41. package/dist/chat/sandbox/egress/session.d.ts +5 -6
  42. package/dist/chat/scheduled-tasks/tool-support.d.ts +5 -5
  43. package/dist/chat/scheduled-tasks/tools/create.d.ts +2 -2
  44. package/dist/chat/scheduled-tasks/tools/delete.d.ts +2 -2
  45. package/dist/chat/scheduled-tasks/tools/list.d.ts +2 -2
  46. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +2 -2
  47. package/dist/chat/scheduled-tasks/tools/update.d.ts +2 -2
  48. package/dist/chat/scheduled-tasks/types.d.ts +1 -1
  49. package/dist/chat/services/provider-retry.d.ts +2 -0
  50. package/dist/chat/services/turn-failure-response.d.ts +4 -0
  51. package/dist/chat/task-execution/paused-turn.d.ts +0 -2
  52. package/dist/chat/task-execution/state.d.ts +2 -2
  53. package/dist/chat/task-execution/store.d.ts +1 -1
  54. package/dist/chat/task-execution/turn-wake.d.ts +12 -0
  55. package/dist/chat/tool-support/action-review.d.ts +1 -1
  56. package/dist/chat/tools/resource-events.d.ts +1 -1
  57. package/dist/chat/tools/search-conversation-events.d.ts +1 -1
  58. package/dist/chat/tools/types.d.ts +1 -1
  59. package/dist/chat/tools/watch-resource-events.d.ts +2 -2
  60. package/dist/{chunk-2EPYYVJC.js → chunk-4HA36IWS.js} +9 -94
  61. package/dist/{chunk-WVIA7V3M.js → chunk-4K25ODAO.js} +27 -1
  62. package/dist/{chunk-V64JCZUV.js → chunk-4S47VHJR.js} +3 -3
  63. package/dist/{chunk-QZV76CM4.js → chunk-6DOAUXLD.js} +231 -198
  64. package/dist/{chunk-M3QYTBQ6.js → chunk-6Q7YZLCL.js} +2 -1
  65. package/dist/{chunk-W46NIB5H.js → chunk-7GAP2XG4.js} +2 -3
  66. package/dist/{chunk-EENQGEIP.js → chunk-7MHNIGHP.js} +1 -1
  67. package/dist/{chunk-AX3UDIMA.js → chunk-ABAX75GG.js} +10 -384
  68. package/dist/{chunk-KTXOGLNE.js → chunk-ATUWN6Z3.js} +2 -2
  69. package/dist/{chunk-3HJ4F4L7.js → chunk-C6TS47CQ.js} +4 -4
  70. package/dist/chunk-D4YV5TDV.js +45 -0
  71. package/dist/{chunk-HUK5XIZL.js → chunk-D63IUQQE.js} +4 -4
  72. package/dist/{chunk-WOKJRLM2.js → chunk-DAPC2HTM.js} +24 -6
  73. package/dist/{chunk-2ADFGSUS.js → chunk-EIJCPJOY.js} +1 -1
  74. package/dist/{chunk-X52ESOBP.js → chunk-EJYP3NQ4.js} +4 -4
  75. package/dist/{chunk-35PM4YY5.js → chunk-EWEH2PJW.js} +6 -6
  76. package/dist/{chunk-W5JMPEBO.js → chunk-FFHXG2FX.js} +13 -11
  77. package/dist/{chunk-FJMTMGBC.js → chunk-FTVMXDNN.js} +1 -1
  78. package/dist/chunk-GFBR4S3Q.js +406 -0
  79. package/dist/{chunk-E3OBGRHN.js → chunk-GJTIZQGJ.js} +1 -1
  80. package/dist/{chunk-IO6KFXYE.js → chunk-HJPC32TQ.js} +12 -50
  81. package/dist/{chunk-RF6J3DIO.js → chunk-INQHUE72.js} +84 -1
  82. package/dist/{chunk-LMNMMESF.js → chunk-LRI5IRGY.js} +19 -44
  83. package/dist/{chunk-6UXDTPEN.js → chunk-LVK4WJKM.js} +1 -1
  84. package/dist/{chunk-AYW7J6K4.js → chunk-MWOH6CIK.js} +178 -148
  85. package/dist/{chunk-LZBPGX5A.js → chunk-OMQ2J6F6.js} +1 -1
  86. package/dist/{chunk-G2B2XZQW.js → chunk-PNYJOEVV.js} +1 -1
  87. package/dist/{chunk-37IQ6DGU.js → chunk-QCT4226Q.js} +3 -3
  88. package/dist/{chunk-R767Y7QV.js → chunk-QZKHHG3B.js} +34 -8
  89. package/dist/{chunk-VOV3SCM3.js → chunk-TVVAMOLS.js} +2 -2
  90. package/dist/{chunk-56XONJPF.js → chunk-U4CTAVWN.js} +1 -1
  91. package/dist/{chunk-4WGFWHWP.js → chunk-UUULGCKK.js} +1 -1
  92. package/dist/{chunk-AHMWPFIW.js → chunk-VH4IU3UC.js} +928 -984
  93. package/dist/chunk-W7GCLNIV.js +58 -0
  94. package/dist/{chunk-24OJZ5OU.js → chunk-WLKWN4CD.js} +22 -12
  95. package/dist/{chunk-T5MTGX2E.js → chunk-X2SS6BF7.js} +201 -210
  96. package/dist/{chunk-SQHFVIMZ.js → chunk-X7NCJQAA.js} +1 -1
  97. package/dist/{chunk-PLZT45VC.js → chunk-Y5G23T2D.js} +5 -5
  98. package/dist/{chunk-3L7CWDQQ.js → chunk-ZIF2S464.js} +1 -1
  99. package/dist/{chunk-QI5JW5AG.js → chunk-ZKKGFUWM.js} +6 -6
  100. package/dist/cli/chat.js +35 -33
  101. package/dist/cli/check.js +5 -5
  102. package/dist/cli/plugins.js +14 -12
  103. package/dist/cli/snapshot-create.js +10 -9
  104. package/dist/cli/upgrade.js +7 -6
  105. package/dist/{db-K2VMNLHQ.js → db-TRJ77FAU.js} +8 -6
  106. package/dist/instrumentation.js +3 -3
  107. package/dist/nitro.js +7 -6
  108. package/dist/{oauth-callback-server-DZZMTKSS.js → oauth-callback-server-XD5SV3MS.js} +30 -27
  109. package/dist/{runner-IIAKWDXI.js → runner-KFX7NG6D.js} +24 -19
  110. package/dist/{skills-EXIQ7BP5.js → skills-J2AH26CW.js} +5 -5
  111. package/dist/spawn-VU3CSRI6.js +41 -0
  112. package/dist/task-runner-5JG3VIIY.js +34 -0
  113. package/dist/{validation-XUGIWBJF.js → validation-O5J4VOPF.js} +5 -5
  114. package/dist/version.js +1 -1
  115. package/dist/{work-G4RLJ634.js → work-ECPQTG3U.js} +24 -19
  116. package/dist/worker-RS3DXUDO.js +24 -0
  117. package/package.json +5 -6
  118. package/dist/app-adapter.d.ts +0 -85
  119. package/dist/chat/api-turns/adapter-conversations.d.ts +0 -15
  120. package/dist/spawn-ZR6OOEAN.js +0 -37
  121. package/dist/task-runner-C52HYCVD.js +0 -32
  122. 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-3HJ4F4L7.js";
3
+ } from "./chunk-C6TS47CQ.js";
4
4
  import {
5
5
  bindSpawnAgent
6
- } from "./chunk-VOV3SCM3.js";
6
+ } from "./chunk-TVVAMOLS.js";
7
7
  import {
8
8
  createAgentInvocationWorker,
9
9
  enqueueAgentInvocation,
10
10
  listPendingAgentInvocationMailboxAppends,
11
11
  routeAgentInvocationWork
12
- } from "./chunk-IO6KFXYE.js";
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
- startSlackProcessingReaction,
37
+ startProcessingReaction,
37
38
  stripLeadingBotMention,
38
39
  wakePausedTurn
39
- } from "./chunk-T5MTGX2E.js";
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-AYW7J6K4.js";
57
- import "./chunk-4WGFWHWP.js";
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-WOKJRLM2.js";
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-W5JMPEBO.js";
93
+ } from "./chunk-FFHXG2FX.js";
89
94
  import {
90
95
  ConversationTurnLifecycleService
91
- } from "./chunk-M3QYTBQ6.js";
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-PLZT45VC.js";
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-V64JCZUV.js";
111
+ } from "./chunk-4S47VHJR.js";
107
112
  import {
108
113
  validatePluginEgressCredentialHooks,
109
114
  validatePluginRegistrations
110
- } from "./chunk-3L7CWDQQ.js";
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-QZV76CM4.js";
163
+ } from "./chunk-6DOAUXLD.js";
159
164
  import {
160
165
  AuthorizationFlowDisabledError,
161
166
  PluginCredentialFailureError
162
- } from "./chunk-35PM4YY5.js";
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-2EPYYVJC.js";
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-QI5JW5AG.js";
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-R767Y7QV.js";
197
+ } from "./chunk-QZKHHG3B.js";
201
198
  import {
202
199
  parseSlackThreadId
203
- } from "./chunk-SQHFVIMZ.js";
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-HUK5XIZL.js";
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-56XONJPF.js";
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-24OJZ5OU.js";
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-37IQ6DGU.js";
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-FJMTMGBC.js";
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-AHMWPFIW.js";
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-AX3UDIMA.js";
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-6UXDTPEN.js";
343
- import "./chunk-X52ESOBP.js";
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-G2B2XZQW.js";
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-LMNMMESF.js";
370
- import "./chunk-2ADFGSUS.js";
371
- import "./chunk-KTXOGLNE.js";
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-E3OBGRHN.js";
375
- import "./chunk-EENQGEIP.js";
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-LZBPGX5A.js";
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-W46NIB5H.js";
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 errorMessage = storedSession?.errorMessage;
710
+ const errorMessage3 = storedSession?.errorMessage;
702
711
  if (dispatchOutcome) {
703
712
  return {
704
- ...errorMessage ? { errorMessage } : void 0,
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 errorMessage = 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.";
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" ? errorMessage : void 0,
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 = options.stripLeadingBotMention(
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 = deps.getThreadId(args.thread, args.message) ?? deps.getRunId(args.thread, args.message);
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 = deps.getChannelId(thread, targetMessage);
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 startSlackProcessingReaction({
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
- deps.logException(postError, args.postFailureEventName, {
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: deps.getThreadId(thread, message),
4379
+ threadId: getThreadId(thread, message),
4371
4380
  actorId: message.author.userId,
4372
- channelId: deps.getChannelId(thread, message),
4373
- runId: deps.getRunId(thread, message)
4381
+ channelId: getChannelId(thread, message),
4382
+ runId: getRunId(thread, message)
4374
4383
  };
4375
4384
  const content = parseContent(message);
4376
- const strippedUserText = deps.stripLeadingBotMention(
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
- deps.logWarn("subscribed_message.reply.skipped", {
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
- deps.logException(error, "agent.turn.steering_flush.failed");
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: deps.getThreadId(thread, message),
4533
+ threadId: getThreadId(thread, message),
4524
4534
  actorId: message.author.userId,
4525
4535
  actorUserName: actorUserName(message),
4526
- channelId: deps.getChannelId(thread, message),
4527
- runId: deps.getRunId(thread, message)
4536
+ channelId: getChannelId(thread, message),
4537
+ runId: getRunId(thread, message)
4528
4538
  })
4529
4539
  });
4530
4540
  };
4531
4541
  try {
4532
- const threadId = deps.getThreadId(thread, message);
4533
- const channelId = deps.getChannelId(thread, message);
4534
- const runId = deps.getRunId(thread, message);
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 deps.withSpan("chat.turn", "chat.turn", context, async () => {
4557
+ await withSpan("chat.turn", "chat.turn", context, async () => {
4548
4558
  await thread.subscribe();
4549
4559
  const queuedMessages = getQueuedMessages(hooks.messageContext, {
4550
- explicitMention: true,
4551
- stripLeadingBotMention: deps.stripLeadingBotMention
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: deps.getThreadId(thread, message),
4619
+ threadId: getThreadId(thread, message),
4610
4620
  actorId: message.author.userId,
4611
4621
  actorUserName: actorUserName(message),
4612
- channelId: deps.getChannelId(thread, message),
4613
- runId: deps.getRunId(thread, message)
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
- deps.logWarn("mention.handler.skipped", {
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
- () => deps.logException(failureCause, "mention.handler.failed")
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: deps.getThreadId(thread, message),
4682
+ threadId: getThreadId(thread, message),
4673
4683
  actorId: message.author.userId,
4674
4684
  actorUserName: actorUserName(message),
4675
- channelId: deps.getChannelId(thread, message),
4676
- runId: deps.getRunId(thread, message)
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: deps.getThreadId(thread, message),
4705
+ threadId: getThreadId(thread, message),
4696
4706
  actorId: message.author.userId,
4697
4707
  actorUserName: actorUserName(message),
4698
- channelId: deps.getChannelId(thread, message),
4699
- runId: deps.getRunId(thread, message)
4708
+ channelId: getChannelId(thread, message),
4709
+ runId: getRunId(thread, message)
4700
4710
  })
4701
4711
  });
4702
4712
  };
4703
4713
  try {
4704
- const threadId = deps.getThreadId(thread, message);
4705
- const channelId = deps.getChannelId(thread, message);
4706
- const runId = deps.getRunId(thread, message);
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 deps.withSpan("chat.turn", "chat.turn", turnContext, async () => {
4726
+ await withSpan("chat.turn", "chat.turn", turnContext, async () => {
4717
4727
  const content = parseContent(message);
4718
- const strippedUserText = deps.stripLeadingBotMention(
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
- explicitMention: Boolean(message.isMention),
4736
- stripLeadingBotMention: deps.stripLeadingBotMention
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: deps.getThreadId(thread, message),
4911
+ threadId: getThreadId(thread, message),
4901
4912
  actorId: message.author.userId,
4902
4913
  actorUserName: actorUserName(message),
4903
- channelId: deps.getChannelId(thread, message),
4904
- runId: deps.getRunId(thread, message)
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
- deps.logWarn("subscribed_message.handler.skipped", {
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
- () => deps.logException(
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: deps.getThreadId(thread, message),
4974
+ threadId: getThreadId(thread, message),
4967
4975
  actorId: message.author.userId,
4968
4976
  actorUserName: actorUserName(message),
4969
- channelId: deps.getChannelId(thread, message),
4970
- runId: deps.getRunId(thread, message)
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
- deps.logException(error, "assistant.thread.initialization.failed");
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
- deps.logException(error, "assistant.context.refresh.failed");
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.replyExecutor?.agentRunner ?? createAgentRunner(executeAgentRun, {
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
- replyExecutor: {
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/turn.ts
5620
- async function persistThreadRuntimeStateWithRetry(thread, patch) {
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 passiveMessages = messages.filter((queued) => {
5627
+ const messagesForContext = messages.filter((queued) => {
5633
5628
  if (queued.explicitMention) {
5634
5629
  return false;
5635
5630
  }
5636
- const slackTs = queuedInstructionActor(queued)?.slackTs;
5631
+ const slackTs = inboundMessageActor(queued)?.slackTs;
5637
5632
  return !slackTs || !conversationContext?.includes(`slack_ts="${slackTs}"`);
5638
5633
  });
5639
- if (passiveMessages.length === 0) {
5634
+ if (messagesForContext.length === 0) {
5640
5635
  return [];
5641
5636
  }
5642
5637
  const lines = [];
5643
- for (const queued of passiveMessages) {
5644
- const actor = queuedInstructionActor(queued);
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 appendRecentThreadMessagesToContext(conversationContext, messages, options) {
5662
- const baseContext = options?.includeConversationContext === false ? void 0 : conversationContext;
5656
+ function appendRecentMessagesToContext(conversationContext, messages) {
5663
5657
  return appendThreadContextMessages(
5664
- baseContext,
5658
+ conversationContext,
5665
5659
  renderRecentThreadMessageLines(conversationContext, messages)
5666
5660
  );
5667
5661
  }
5668
- function queuedInstructionActor(queued) {
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 queuedInstructionProvenance(queued, teamId) {
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 collectTurnAttachments(message, queuedMessages) {
5700
+ function collectAttachments(message, batchedMessages) {
5707
5701
  return [
5708
- ...(queuedMessages ?? []).flatMap((queued) => queued.message.attachments),
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
- deps.services.lookupSlackUser
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
- deps.services.lookupSlackUser
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: queuedInstructionActor(queued),
5909
- provenance: queuedInstructionProvenance(queued, teamId),
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 drainParkedInputToEventLog = async (pairs) => {
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 parkedMessages = [
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 parked turn's own message must not duplicate
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) !== parkedSessionId
5981
+ (queued) => buildDeterministicTurnId(queued.message.id) !== pausedTurnId
5983
5982
  );
5984
- if (parkedMessages.length === 0) {
5983
+ if (messagesForPausedTurn.length === 0) {
5985
5984
  return true;
5986
5985
  }
5987
- const parkedPairs = (await resolveSteeringMessages(parkedMessages)).map((steering) => ({
5986
+ const steeringMessages = (await resolveSteeringMessages(messagesForPausedTurn)).map((steering) => ({
5988
5987
  message: buildSteeringPiMessage(steering),
5989
5988
  provenance: steering.provenance
5990
5989
  }));
5991
- return drainParkedInputToEventLog(parkedPairs);
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.services.getPausedTurnRequest({
6010
+ const pausedTurn = await deps.pausedTurns.get({
6009
6011
  conversationId,
6010
6012
  turnId: activeTurnId
6011
6013
  });
6012
6014
  if (pausedTurn) {
6013
- if (!await appendParkedTurnInput(pausedTurn.turnId)) {
6015
+ if (!await saveMessagesForPausedTurn(pausedTurn.turnId)) {
6014
6016
  throw new TurnInputDeferredError();
6015
6017
  }
6016
6018
  try {
6017
- await deps.services.wakePausedTurn(pausedTurn);
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.services.turnLifecycle.start({
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 = collectTurnAttachments(
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.services.contextCompactor.maybeCompact({
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 batchedInstructions = (await resolveSteeringMessages(
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 drainParkedInputToEventLog(batchedInstructions)) {
6391
+ if (!await saveSteeringMessages({
6392
+ conversationId,
6393
+ messages: batchedSteeringMessages
6394
+ })) {
6391
6395
  shouldPersistFailureState = false;
6392
6396
  throw new TurnInputDeferredError();
6393
6397
  }
6394
- if (batchedInstructions.length > 0) {
6398
+ if (batchedSteeringMessages.length > 0) {
6395
6399
  const presentKeys = new Set(
6396
- (piMessages ?? []).map(parkedInputKey).filter((key) => key !== void 0)
6400
+ (piMessages ?? []).map(steeringMessageKey).filter((key) => key !== void 0)
6397
6401
  );
6398
- const newlyBatched = batchedInstructions.map((entry) => entry.message).filter((batchedMessage) => {
6399
- const key = parkedInputKey(batchedMessage);
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 (newlyBatched.length > 0) {
6403
- piMessages = [...piMessages ?? [], ...newlyBatched];
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 = appendRecentThreadMessagesToContext(
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 outcome = await deps.services.agentRunner.run({
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.services.turnLifecycle.fail({
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.services.wakePausedTurn({
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
- let reply = outcome.result;
6592
- setTags({ modelId: reply.diagnostics.modelId });
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": reply.diagnostics.outcome,
6708
- "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
6709
- "app.ai.tool_error_results": reply.diagnostics.toolErrorCount
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 (reply.diagnostics.outcome === "success" && conversationId) {
6719
+ if (completedResult.diagnostics.outcome === "success" && conversationId) {
6714
6720
  try {
6715
- await deps.services.scheduleSessionCompletedPluginTasks({
6716
- conversationId,
6717
- sessionId: turnId
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 classifiedFailure = getConversationTurnBoundaryError(error);
6758
- const failureCause = classifiedFailure?.cause ?? error;
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 errorMessage;
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
- errorMessage = result.errorMessage;
7186
+ errorMessage3 = result.errorMessage;
7177
7187
  },
7178
7188
  shouldYield: hooks.shouldYield,
7179
7189
  skipBackfill: true
7180
7190
  });
7181
7191
  return {
7182
- ...errorMessage ? { errorMessage } : void 0,
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
- services.replyExecutor.lookupSlackUser
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
- services: services.replyExecutor
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
- getThreadId,
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, errorMessage = "Paused agent run failed while continuing") {
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
- const resumeTurn = options.resumeTurn ?? resumeSlackTurn;
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
- agentRunner: options.agentRunner,
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
- replyContext: {
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 services = {
7892
- ...options.services,
7893
- replyExecutor: {
7894
- ...options.services?.replyExecutor,
7895
- agentRunner: options.agentRunner,
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
- services
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
- scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks,
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
- scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks,
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
- agentRunner: options.agentRunner,
7952
- cancellation: apiTurnCancellation,
7953
- turnLifecycle: services.replyExecutor?.turnLifecycle
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/chat/api-turns/adapter-conversations.ts
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 createAdapterConversations(options) {
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/app.ts
8344
- function resolveAdapterBaseURL(dashboard) {
8345
- const configured = dashboard?.baseURL?.trim() || process.env.JUNIOR_BASE_URL?.trim();
8346
- if (configured) return configured;
8347
- const vercelURL = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim() || process.env.VERCEL_URL?.trim();
8348
- if (!vercelURL) return void 0;
8349
- return /^https?:\/\//.test(vercelURL) ? vercelURL : `https://${vercelURL}`;
8350
- }
8351
- var DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
8352
- async function defaultWaitUntil() {
8353
- try {
8354
- const { waitUntil } = await import("@vercel/functions");
8355
- return (task) => {
8356
- const promise = typeof task === "function" ? task() : task;
8357
- waitUntil(promise);
8358
- };
8359
- } catch {
8360
- return (task) => {
8361
- const promise = typeof task === "function" ? task() : task;
8362
- promise.catch(console.error);
8363
- };
8364
- }
8365
- }
8366
- async function resolveVirtualConfig() {
8367
- try {
8368
- const mod = await import("#junior/config");
8369
- return {
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
- return void 0;
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 code = error.code;
8389
- return (code === "ERR_PACKAGE_IMPORT_NOT_DEFINED" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && error.message.includes("#junior/config");
8390
- }
8391
- function hasConfiguredPluginCatalog(config) {
8392
- if (!config) {
8393
- return false;
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 warnUnregisteredConfigDefaults(defaults) {
8400
- for (const key of Object.keys(defaults ?? {})) {
8401
- if (!pluginCatalogRuntime.isConfigKey(key)) {
8402
- logWarn("config.default.unregistered", { "app.config.key": key });
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
- function pluginPackageNames(config) {
8407
- return config?.packages ?? [];
8408
- }
8409
- function validateBuildIncludesPluginPackages(pluginConfig, virtualConfig) {
8410
- if (!virtualConfig?.plugins) {
8411
- return;
8412
- }
8413
- const bundled = new Set(pluginPackageNames(virtualConfig.plugins));
8414
- const missing = pluginPackageNames(pluginConfig).filter(
8415
- (packageName) => !bundled.has(packageName)
8416
- );
8417
- if (missing.length === 0) {
8418
- return;
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#039;");
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 methods = typeof route.method === "string" ? [route.method] : route.method ?? ["ALL"];
8656
- const explicitMethods = methods.filter(
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 (methods.includes("ALL")) {
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
- replyExecutor: { agentRunner },
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 adapterRoutes = [];
8805
- let authenticatedRoutes = [];
8806
- try {
8807
- const adapterOutputs = await Promise.all(
8808
- (options?.adapters ?? []).map(async (adapter) => {
8809
- const work = getConversationWorkOptions();
8810
- const state = work.state ?? getStateAdapter();
8811
- return await adapter({
8812
- agentName: botConfig.userName,
8813
- baseURL: resolveAdapterBaseURL(dashboard),
8814
- conversations: createAdapterConversations({
8815
- conversationStore: work.conversationStore,
8816
- eventStore: getConversationEventStore(),
8817
- queue: work.queue ?? getVercelConversationWorkQueue(),
8818
- state
8819
- }),
8820
- reportError: (error, event, attributes) => void logException(error, event, attributes),
8821
- state,
8822
- version: JUNIOR_VERSION
8823
- });
8824
- })
8825
- );
8826
- adapterRoutes = adapterOutputs.flatMap((output) => output.routes ?? []);
8827
- authenticatedRoutes = adapterOutputs.flatMap(
8828
- (output) => output.authenticatedRoutes ?? []
8829
- );
8830
- if (authenticatedRoutes.length > 0 && (!dashboard || dashboard.disabled)) {
8831
- throw new Error(
8832
- "createApp() adapters with authenticated routes require an enabled dashboard"
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({